コード例 #1
0
    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)
コード例 #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
コード例 #3
0
    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 base type expected for items of the collection,
        and relies on the ParserFinder to find the parsing plan

        :param obj_on_fs:
        :param desired_type:
        :param logger:
        :return:
        """
        # nb of file children
        n_children = len(obj_on_fs.get_multifile_children())

        # first extract base collection type
        subtypes, key_type = _extract_collection_base_type(desired_type)

        if isinstance(subtypes, tuple):
            # -- check the tuple length
            if n_children != len(subtypes):
                raise FolderAndFilesStructureError.create_for_multifile_tuple(
                    obj_on_fs, len(subtypes),
                    len(obj_on_fs.get_multifile_children()))
        else:
            # -- repeat the subtype n times
            subtypes = [subtypes] * n_children

        # -- for each child create a plan with the appropriate parser
        children_plan = OrderedDict()
        # use sorting for reproducible results in case of multiple errors
        for (child_name, child_fileobject), child_typ in zip(
                sorted(obj_on_fs.get_multifile_children().items()), subtypes):
            # -- use the parserfinder to find the plan
            t, child_parser = self.parser_finder.build_parser_for_fileobject_and_desiredtype(
                child_fileobject, child_typ, logger)
            children_plan[child_name] = child_parser.create_parsing_plan(
                t, child_fileobject, logger, _main_call=False)

        return children_plan
コード例 #4
0
    def create(parser: Parser, obj: PersistedObject = None):
        """
        Helper method provided because we actually can't put that in the constructor, it creates a bug in Nose tests
        https://github.com/nose-devs/nose/issues/725

        :param parser:
        :param obj:
        :return:
        """
        if obj is not None:
            return _InvalidParserException('Error ' + str(obj) + ' cannot be parsed using ' + str(parser) + ' since '
                                           + ' this parser does not support ' + obj.get_pretty_file_mode())
        else:
            return _InvalidParserException('Error this parser is neither SingleFile nor MultiFile !')
コード例 #5
0
    def create_parsing_plan(self, desired_type: Type[T], filesystem_object: PersistedObject, logger: Logger,
                            _main_call: bool = True):
        """
        Implements the abstract parent method by using the recursive parsing plan impl. Subclasses wishing to produce
        their own parsing plans should rather override _create_parsing_plan in order to benefit from this same log msg.

        :param desired_type:
        :param filesystem_object:
        :param logger:
        :param _main_call: internal parameter for recursive calls. Should not be changed by the user.
        :return:
        """
        in_root_call = False

        # -- log msg only for the root call, not for the children that will be created by the code below
        if _main_call and (not hasattr(AnyParser.thrd_locals, 'flag_init') or AnyParser.thrd_locals.flag_init == 0):
            # print('Building a parsing plan to parse ' + str(filesystem_object) + ' into a ' +
            #      get_pretty_type_str(desired_type))
            logger.debug('Building a parsing plan to parse [{location}] into a {type}'
                         ''.format(location=filesystem_object.get_pretty_location(append_file_ext=False),
                                   type=get_pretty_type_str(desired_type)))
            AnyParser.thrd_locals.flag_init = 1
            in_root_call = True

        # -- create the parsing plan
        try:
            pp = self._create_parsing_plan(desired_type, filesystem_object, logger, log_only_last=(not _main_call))
        finally:
            # remove threadlocal flag if needed
            if in_root_call:
                AnyParser.thrd_locals.flag_init = 0

        # -- log success only if in root call
        if in_root_call:
            # print('Parsing Plan created successfully')
            logger.debug('Parsing Plan created successfully')

        # -- finally return
        return pp
コード例 #6
0
    def __get_parsing_plan_for_multifile_children(self, obj_on_fs: PersistedObject, desired_type: Type[Any],
                                                  children_on_fs: Dict[str, PersistedObject], 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 children_on_fs:
        :param logger:
        :return:
        """

        # -- (a) collect pep-484 information in the class constructor to be able to understand what is required
        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
                t, 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(
                        t, child_on_fs, logger=logger, _main_call=False)
            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
                    msg = 'NOT FOUND - This optional constructor attribute for type ' \
                          + get_pretty_type_str(desired_type) + ' was not found on file system, but this may be normal'\
                          ' - this message is displayed \'just in case\'.'
                    if logger.isEnabledFor(DEBUG):
                        logger.warning(
                            '(B) ' + obj_on_fs.get_pretty_child_location(
                                attribute_name, blank_parent_part=True) +
                            ': ' + msg)
                    else:
                        logger.warning(
                            'WARNING parsing [{loc}] as a [{typ}]: optional constructor attribute [{att}] '
                            'not found on file system. This may be normal - this message is displayed \'just'
                            ' in case\'.'.format(
                                loc=obj_on_fs.get_pretty_location(
                                    blank_parent_part=False,
                                    append_file_ext=False),
                                typ=get_pretty_type_str(desired_type),
                                att=attribute_name))

        return children_plan
コード例 #7
0
    def _parse_multifile(self, desired_type: Type[Union[Dict, List, Set, Tuple]], obj: PersistedObject,
                         parsing_plan_for_children: Dict[str, ParsingPlan], logger: Logger,
                         options: Dict[str, Dict[str, Any]]) \
            -> Union[Dict, List, Set, Tuple]:
        """
        Options may contain a section with id 'MultifileCollectionParser' containing the following options:
        * lazy_parsing: if True, the method will return immediately without parsing all the contents. Instead, the
        returned collection will perform the parsing the first time an item is required.
        * background_parsing: if True, the method will return immediately while a thread parses all the contents in
        the background. Note that users cannot set both lazy_parsing and background_parsing to True at the same time

        :param desired_type:
        :param obj:
        :param parsing_plan_for_children:
        :param logger:
        :param options:
        :return:
        """

        # first get the options and check them
        lazy_parsing = False
        background_parsing = False

        opts = self._get_applicable_options(options)
        for opt_key, opt_val in opts.items():
            if opt_key is 'lazy_parsing':
                lazy_parsing = opt_val
            elif opt_key is 'background_parsing':
                background_parsing = opt_val
            else:
                raise Exception(
                    'Invalid option in MultiFileCollectionParser : ' + opt_key)

        check_var(lazy_parsing, var_types=bool, var_name='lazy_parsing')
        check_var(background_parsing,
                  var_types=bool,
                  var_name='background_parsing')

        if lazy_parsing and background_parsing:
            raise ValueError(
                'lazy_parsing and background_parsing cannot be set to true at the same time'
            )

        if lazy_parsing:
            # build a lazy dictionary
            results = LazyDictionary(
                sorted(list(parsing_plan_for_children.keys())),
                loading_method=lambda x: parsing_plan_for_children[x].execute(
                    logger, options))
            # logger.debug('Assembling a ' + get_pretty_type_str(desired_type) + ' from all children of ' + str(obj)
            #             + ' (lazy parsing: children will be parsed when used) ')
            logger.debug(
                '(P) {loc} : lazy parsing ON, children will be parsed only if/when used'
                .format(loc=obj.get_pretty_location(blank_parent_part=(
                    not GLOBAL_CONFIG.full_paths_in_logs),
                                                    compact_file_ext=True)))

        elif background_parsing:
            # -- TODO create a thread to perform the parsing in the background
            raise ValueError('Background parsing is not yet supported')

        else:
            # Parse right now
            results = OrderedDict()

            # parse all children according to their plan
            # -- use key-based sorting on children to lead to reproducible results
            # (in case of multiple errors, the same error will show up first everytime)
            for child_name, child_plan in sorted(
                    parsing_plan_for_children.items()):
                results[child_name] = child_plan.execute(logger, options)
            # logger.debug('Assembling a ' + get_pretty_type_str(desired_type) + ' from all parsed children of '
            #             + str(obj))

        if issubclass(desired_type, list):
            # return a list facade
            return KeySortedListFacadeForDict(results)
        elif issubclass(desired_type, tuple):
            # return a tuple facade
            return KeySortedTupleFacadeForDict(results)
        elif issubclass(desired_type, set):
            # return a set facade
            return SetFacadeForDict(results)
        elif issubclass(desired_type, dict):
            # return the dict directly
            return results
        else:
            raise TypeError(
                'Cannot build the desired collection out of the multifile children: desired type is not '
                'supported: ' + get_pretty_type_str(desired_type))