Beispiel #1
0
def config_to_dict_of_dict(desired_type: Type[T], config: ConfigParser, logger: Logger,
                           conversion_finder: ConversionFinder, **kwargs) -> DictOfDict:
    """
    Helper method to read a configuration file according to the 'configparser' format, and return it as a dictionary
    of dictionaries [section > [property > value]].

    :param file_object:
    :return:
    """
    # return dict(config)

    # get the base collection type if provided
    base_typ, discarded = _extract_collection_base_type(desired_type, exception_if_none=False)
    # if none, at least declare dict
    base_typ = base_typ or Dict

    # convert the whole config to a dictionary by flattening all sections. If a key is found twice in two different
    # sections an error is raised
    results = dict()
    for section, props in config.items():
        # convert all values of the sub-dictionary
        results[section] = ConversionFinder.convert_collection_values_according_to_pep(props, base_typ,
                                                                                       conversion_finder, logger,
                                                                                       **kwargs)

    return results
def read_dict_from_properties(desired_type: Type[dict], file_object: TextIOBase,
                              logger: Logger, conversion_finder: ConversionFinder, **kwargs) -> Dict[str, Any]:
    """
    Helper method to read a dictionary from a .properties file (java-style) using jprops.
    Since jprops does not provide automatic handling for boolean and numbers, this tries to add the feature.

    :param file_object:
    :return:
    """

    # right now jprops relies on a byte stream. So we convert back our nicely decoded Text stream to a unicode
    # byte stream ! (urgh)
    class Unicoder:
        def __init__(self, file_object):
            self.f = file_object

        def __iter__(self):
            return self

        def __next__(self):
            line = self.f.__next__()
            return line.encode(encoding='utf-8')

    res = jprops.load_properties(Unicoder(file_object))

    # first automatic conversion of strings > numbers
    res = {key: try_parse_num_and_booleans(val) for key, val in res.items()}

    # further convert if required
    return ConversionFinder.convert_collection_values_according_to_pep(res, desired_type, conversion_finder, logger, 
                                                                       **kwargs)
Beispiel #3
0
def merge_all_config_sections_into_a_single_dict(desired_type: Type[T], config: ConfigParser, logger: Logger,
                                                 conversion_finder: ConversionFinder, **kwargs) -> Dict[str, Any]:
    """
    Helper method to convert a 'configparser' into a dictionary [property > value].
    Properties from all sections are collected. If the same key appears in several sections, an
    error will be thrown

    :param file_object:
    :return:
    """

    # convert the whole config to a dictionary by flattening all sections. If a key is found twice in two different
    # sections an error is raised
    results = dict()
    for section, props in config.items():
        for key, value in props.items():
            if key in results.keys():
                # find all sections where it appears
                sections_where_it_appears = [s for s, p in config.items() if key in p.keys()]
                raise MultipleKeyOccurenceInConfigurationError.create(key, sections_where_it_appears)
            else:
                results[key] = value

    return ConversionFinder.convert_collection_values_according_to_pep(results, desired_type, conversion_finder,
                                                                       logger, **kwargs)
Beispiel #4
0
def list_to_tuple(desired_type: Type[T], contents_list: List[str],
                  logger: Logger, conversion_finder: ConversionFinder,
                  **kwargs) -> Tuple:

    # don't create a facade/proxy, since anyway we'll try to convert the values below
    res = tuple(contents_list)

    # convert if required
    return ConversionFinder.convert_collection_values_according_to_pep(
        res, desired_type, conversion_finder, logger, **kwargs)
Beispiel #5
0
def read_dict_or_list_from_json(desired_type: Type[dict],
                                file_object: TextIOBase, logger: Logger,
                                conversion_finder: ConversionFinder,
                                **kwargs) -> Dict[str, Any]:
    """
    Helper method to read a dictionary from a .json file using json library
    :param file_object:
    :return:
    """
    # lazy import in order not to force use of jprops
    import json
    res = json.load(file_object)

    # convert if required
    return ConversionFinder.convert_collection_values_according_to_pep(
        res, desired_type, conversion_finder, logger, **kwargs)
def read_collection_from_yaml(desired_type: Type[Any],
                              file_object: TextIOBase,
                              logger: Logger,
                              conversion_finder: ConversionFinder,
                              fix_imports: bool = True,
                              errors: str = 'strict',
                              **kwargs) -> Any:
    """
    Parses a collection from a yaml file.

    :param desired_type:
    :param file_object:
    :param logger:
    :param fix_imports:
    :param errors:
    :param args:
    :param kwargs:
    :return:
    """
    res = yaml.load(file_object)

    # convert if required
    return ConversionFinder.convert_collection_values_according_to_pep(
        res, desired_type, conversion_finder, logger, **kwargs)
Beispiel #7
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))