def _get_parsing_plan_for_multifile_children(
            self, obj_on_fs: PersistedObject, desired_type: Type[Any],
            logger: Logger) -> Dict[str, Any]:
        """
        Simply inspects the required type to find the names and types of its constructor arguments.
        Then relies on the inner ParserFinder to parse each of them.

        :param obj_on_fs:
        :param desired_type:
        :param logger:
        :return:
        """

        if is_collection(desired_type, strict=True):
            # if the destination type is 'strictly a collection' (not a subclass of a collection) we know that we can't
            # handle it here, the constructor is not pep484-typed
            raise TypeError(
                'Desired object type \'' + get_pretty_type_str(desired_type) +
                '\' is a collection, '
                'so it cannot be parsed with this default object parser')

        else:
            # First get the file children
            children_on_fs = obj_on_fs.get_multifile_children()

            # Try the type itself
            # try:
            return self.__get_parsing_plan_for_multifile_children(
                obj_on_fs, desired_type, children_on_fs, logger=logger)
Example #2
0
    def _get_parsing_plan_for_multifile_children(self, obj_on_fs: PersistedObject, desired_type: Type[Any],
                                                 logger: Logger) -> Dict[str, Any]:
        """
        Simply simply inspects the required type to find the names and types of its constructor arguments.
        Then relies on the inner ParserFinder to parse each of them.

        :param obj_on_fs:
        :param desired_type:
        :param logger:
        :return:
        """

        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 parsed with this default object parser')

        # First get the file children
        children_on_fs = obj_on_fs.get_multifile_children()

        # -- (a) extract the schema from the class constructor
        constructor_args_types_and_opt = get_constructor_attributes_types(desired_type)

        # -- (b) plan to parse each attribute required by the constructor
        children_plan = dict()  # results will be put in this object

        # --use sorting in order to lead to reproducible results in case of multiple errors
        for attribute_name, att_desc in sorted(constructor_args_types_and_opt.items()):
            attribute_is_mandatory = att_desc[1]
            attribute_type = att_desc[0]

            # get the child
            if attribute_name in children_on_fs.keys():
                child_on_fs = children_on_fs[attribute_name]

                # find a parser
                parser_found = self.parser_finder.build_parser_for_fileobject_and_desiredtype(child_on_fs,
                                                                                              attribute_type,
                                                                                              logger=logger)
                # create a parsing plan
                children_plan[attribute_name] = parser_found.create_parsing_plan(attribute_type, child_on_fs,
                                                                                logger=logger)
            else:
                if attribute_is_mandatory:
                    raise MissingMandatoryAttributeFiles.create(obj_on_fs, desired_type, attribute_name)
                else:
                    # we don't care : optional attribute
                    # dont use warning since it does not show up nicely
                    #print('----- WARNING: Attribute ' + attribute_name + ' was not found on file system. However '
                    #      'it is not mandatory for the constructor of type ' + get_pretty_type_str(desired_type)
                    #      + ', so we\'ll build the object without it...')
                    logger.warning('----- Attribute ' + attribute_name + ' was not found on file system. However '
                                   'it is not mandatory for the constructor of type ' + get_pretty_type_str(desired_type)
                                   + ', so we\'ll build the object without it...')
                    pass
        return children_plan
Example #3
0
    def is_able_to_parse(self, desired_type: Type[Any], desired_ext: str, strict: bool):
        """
        Explicitly declare that we are not able to parse collections

        :param desired_type:
        :param desired_ext:
        :param strict:
        :return:
        """
        if is_collection(desired_type, strict=True):
            return False, None
        else:
            return super(MultifileObjectParser, self).is_able_to_parse(desired_type, desired_ext, strict)
Example #4
0
def _not_able_to_convert_collections(strict: bool, from_type: Type[Any] = None, to_type: Type[Any] = None):
    """
    Explicitly declare that we are not able to parse collections

    :param strict:
    :param from_type:
    :param to_type:
    :return:
    """
    # here strict means 'strictly a collection' (allow subclasses)
    if to_type is not None and is_collection(to_type, strict=True):
        return False
    else:
        return True
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):
        # if the destination type is 'strictly a collection' (not a subclass of a collection) we know that we can't
        # handle it here, the constructor is not pep484-typed
        raise TypeError(
            'Desired object type \'' + get_pretty_type_str(desired_type) +
            '\' is a collection, '
            'so it cannot be created using this generic object creator')
    else:
        # Try the type itself
        # try:
        return _dict_to_object(desired_type,
                               contents_dict,
                               logger=logger,
                               options=options,
                               conversion_finder=conversion_finder,
                               is_dict_of_dicts=is_dict_of_dicts)
Example #6
0
def test_is_collection(typ, strict, expected):
    assert is_collection(typ, strict=strict) == expected
Example #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 __is_valid_for_dict_to_object_conversion(strict_mode: bool,
                                             from_type: Type,
                                             to_type: Type) -> bool:
    """
    Returns true if the provided types are valid for dict_to_object conversion

    Explicitly declare that we are not able to parse collections nor able to create an object from a dictionary if the
    object's constructor is non correctly PEP484-specified.

    None should be treated as a Joker here (but we know that never from_type and to_type will be None at the same time)

    :param strict_mode:
    :param from_type:
    :param to_type:
    :return:
    """
    # right now we're stuck with the default logger..
    logr = default_logger

    if to_type is None or is_any_type(to_type):
        # explicitly handle the 'None' (joker) or 'any' type
        return True

    elif is_collection(to_type, strict=True):
        # if the destination type is 'strictly a collection' (not a subclass of a collection) we know that we can't
        # handle it here, the constructor is not pep484-typed
        return False

    else:
        # (1) Try the type itself
        try:
            # can we find enough pep-484 information in the constructor to be able to understand what is required ?
            get_constructor_attributes_types(to_type)
            return True
        except TypeInformationRequiredError as main_e:
            # # failed: we cant guess the required types of constructor arguments
            # if strict_mode:
            #     # Warning and return NO
            #     if should_display_warnings_for(to_type):
            #         logr.warn('Object constructor signature for type {} does not allow parsyfiles to '
            #                    'automatically create instances from dict content. Caught {}: {}'
            #                    ''.format(get_pretty_type_str(to_type), type(main_e).__name__, main_e))
            #     return False
            #
            # # non-strict mode: (2) Check if any subclasses exist
            # subclasses = get_all_subclasses(to_type)
            # if len(subclasses) > GLOBAL_CONFIG.dict_to_object_subclass_limit:
            #     logr.warn('WARNING: Type {} has {} subclasses, only {} will be tried by parsyfiles when attempting to '
            #               'create it from a subclass. You can raise this limit by setting the appropriate option with '
            #               '`parsyfiles_global_config()`'
            #               ''.format(to_type, len(subclasses), GLOBAL_CONFIG.dict_to_object_subclass_limit))
            #
            # # Then for each subclass also try (with a configurable limit in nb of subclasses)
            # for subclass in subclasses[0:GLOBAL_CONFIG.dict_to_object_subclass_limit]:
            #     try:
            #         get_constructor_attributes_types(subclass)
            #         # OK, but issue warning for the root type still
            #         if should_display_warnings_for(to_type):
            #             logr.warn('WARNING: Object constructor signature for type {} does not allow parsyfiles to '
            #                       'automatically create instances from dict content, but it can for at least one of '
            #                       'its subclasses ({}) so it might be ok for you. Caught {}: {}'
            #                       ''.format(get_pretty_type_str(to_type), get_pretty_type_str(subclass),
            #                                  type(main_e).__name__, main_e))
            #         return True
            #     except TypeInformationRequiredError as e:
            #         # failed: we cant guess the required types of constructor arguments
            #         if should_display_warnings_for(to_type):
            #             logr.warn('WARNING: Object constructor signature for type {} does not allow parsyfiles to '
            #                       'automatically create instances from dict content. Caught {}: {}'
            #                       ''.format(subclass, type(e).__name__, e))
            #
            # # Nothing succeeded

            if should_display_warnings_for(to_type):
                logr.warn(
                    'WARNING: Object constructor signature for type {} does not allow parsyfiles to '
                    'automatically create instances from dict content. Caught {}: {}'
                    ''.format(get_pretty_type_str(to_type),
                              type(main_e).__name__, main_e))

            return False