Beispiel #1
0
def dict_to_object(desired_type: Type[T], contents_dict: Dict[str, Any], logger: Logger,
                   options: Dict[str, Dict[str, Any]], conversion_finder: ConversionFinder = None,
                   is_dict_of_dicts: bool = False) -> T:
    """
    Utility method to create an object from a dictionary of constructor arguments. Constructor arguments that dont have
    the correct type are intelligently converted if possible

    :param desired_type:
    :param contents_dict:
    :param logger:
    :param options:
    :param conversion_finder:
    :param is_dict_of_dicts:
    :return:
    """
    check_var(desired_type, var_types=type, var_name='obj_type')
    check_var(contents_dict, var_types=dict, var_name='contents_dict')

    if is_collection(desired_type, strict=True):
        raise TypeError('Desired object type \'' + get_pretty_type_str(desired_type) + '\' is a collection, '
                        'so it cannot be created using this generic object creator')

    constructor_args_types_and_opt = get_constructor_attributes_types(desired_type)

    try:
        # for each attribute, convert the types of its parsed values if required
        dict_for_init = dict()
        for attr_name, provided_attr_value in contents_dict.items():

            # check if this attribute name is required by the constructor
            if attr_name in constructor_args_types_and_opt.keys():

                # check the theoretical type wanted by the constructor
                attr_type_required = constructor_args_types_and_opt[attr_name][0]

                if not is_dict_of_dicts:
                    if isinstance(attr_type_required, type):
                        # this will not fail if type information is not present;the attribute will only be used 'as is'
                        full_attr_name = get_pretty_type_str(desired_type) + '.' + attr_name

                        dict_for_init[attr_name] = ConversionFinder.try_convert_value(conversion_finder, full_attr_name,
                                                                                      provided_attr_value,
                                                                                      attr_type_required, logger,
                                                                                      options)

                    else:
                        warning('Constructor for type <' + get_pretty_type_str(desired_type) + '> has no PEP484 Type '
                                'hint, trying to use the parsed value in the dict directly')
                        dict_for_init[attr_name] = provided_attr_value

                else:
                    # in that mode, the attribute value itself is a dict, so the attribute needs to be built from that
                    # dict first
                    if isinstance(provided_attr_value, dict):
                        # recurse : try to build this attribute from the dictionary provided. We need to know the type
                        # for this otherwise we wont be able to call the constructor :)
                        if attr_type_required is Parameter.empty or not isinstance(attr_type_required, type):
                            raise TypeInformationRequiredError.create_for_object_attributes(desired_type, attr_name)
                        else:
                            # we can build the attribute from the sub-dict
                            dict_for_init[attr_name] = dict_to_object(attr_type_required, provided_attr_value,
                                                                      logger, options,
                                                                      conversion_finder=conversion_finder)

                    else:
                        raise ValueError('Error while trying to build object of type ' + str(desired_type) + ' from a '
                                         'dictionary of dictionaries. Entry \'' + attr_name + '\' is not a dictionary')
            else:
                if is_dict_of_dicts and attr_name is 'DEFAULT':
                    # -- tolerate but ignore - this is probably due to a configparser
                    # warning('Property name \'' + attr_name + '\' is not an attribute of the object constructor. <'
                    #         + get_pretty_type_str(desired_type) + '> constructor attributes are : '
                    #         + list(set(constructor_args_types.keys()) - {'self'}) + '. However it is named DEFAULT')
                    pass
                else:
                    # the dictionary entry does not correspond to a valid attribute of the object
                    raise InvalidAttributeNameForConstructorError.create(desired_type,
                                                                         list(set(constructor_args_types_and_opt.keys()) - {'self'}),
                                                                         attr_name)

        # create the object using its constructor
        try:
            return desired_type(**dict_for_init)
        except Exception as e:
            # Wrap into an Exception
            raise ObjectInstantiationException.create(desired_type, dict_for_init, e)

    except TypeError as e:
        raise CaughtTypeErrorDuringInstantiation.create(desired_type, contents_dict, e)
def convert_collection_values_according_to_pep(coll_to_convert: Union[Dict, List, Set, Tuple],
                                               desired_type: Type[Union[Dict, List, Set, Tuple]],
                                               conversion_finder: ConversionFinder, logger: Logger, **kwargs) \
        -> Union[Dict, List, Set, Tuple]:
    """
    Helper method to convert the values of a collection into the required (pep-declared) value type in desired_type.
    If desired_type does not explicitly mention a type for its values, the collection will be returned as is, otherwise
    a  copy will be created and filled with conversions of the values, performed by the provided conversion_finder

    :param coll_to_convert:
    :param desired_type:
    :param conversion_finder:
    :param logger:
    :param kwargs:
    :return:
    """
    base_desired_type = get_base_generic_type(desired_type)

    if issubclass(base_desired_type, Mapping) or issubclass(
            base_desired_type, dict):
        # get the base collection type if provided
        base_typ, discarded = _extract_collection_base_type(
            desired_type, exception_if_none=False)

        if base_typ is None:
            # nothing is required in terms of dict values: consider that it is correct
            return coll_to_convert
        else:
            # TODO resuse appropriate container type (not necessary a dict) according to type of coll_to_convert
            # there is a specific type required for the dict values.
            res = dict()
            # convert if required
            for key, val in coll_to_convert.items():
                res[key] = ConversionFinder.try_convert_value(
                    conversion_finder,
                    '',
                    val,
                    base_typ,
                    logger,
                    options=kwargs)
            return res

    elif issubclass(base_desired_type, Sequence) or issubclass(
            base_desired_type, list):
        # get the base collection type if provided
        base_typ, discarded = _extract_collection_base_type(
            desired_type, exception_if_none=False)

        if base_typ is None:
            # nothing is required in terms of dict values: consider that it is correct
            return coll_to_convert
        else:
            # TODO resuse appropriate container type (not necessary a list) according to type of coll_to_convert
            # there is a specific type required for the list values. convert if required
            return [
                ConversionFinder.try_convert_value(conversion_finder,
                                                   '',
                                                   val,
                                                   base_typ,
                                                   logger,
                                                   options=kwargs)
                for val in coll_to_convert
            ]

    elif issubclass(base_desired_type, AbstractSet) or issubclass(
            base_desired_type, set):
        # get the base collection type if provided
        base_typ, discarded = _extract_collection_base_type(
            desired_type, exception_if_none=False)

        if base_typ is None:
            # nothing is required in terms of dict values: consider that it is correct
            return coll_to_convert
        else:
            # TODO resuse appropriate container type (not necessary a set) according to type of coll_to_convert
            # there is a specific type required for the list values. convert if required
            return {
                ConversionFinder.try_convert_value(conversion_finder,
                                                   '',
                                                   val,
                                                   base_typ,
                                                   logger,
                                                   options=kwargs)
                for val in coll_to_convert
            }
    else:
        raise TypeError(
            'Cannot convert collection values, expected type is not a supported collection '
            '(dict, list, set, Mapping, Sequence, AbstractSet)! : ' +
            str(desired_type))