Exemple #1
0
    def corpus_path_to_adapter_path(self, corpus_path: str) -> Optional[str]:
        """Takes a corpus path, figures out the right adapter to use and then return
        an adapter domain path"""
        with logger._enter_scope(self._TAG, self._ctx,
                                 self.corpus_path_to_adapter_path.__name__):
            if not corpus_path:
                logger.error(
                    self._ctx, self._TAG,
                    StorageManager.corpus_path_to_adapter_path.__name__, None,
                    CdmLogCode.ERR_STORAGE_NULL_CORPUS_PATH)
                return None

            result = None

            # Break the corpus path into namespace and ... path
            path_tuple = StorageUtils.split_namespace_path(corpus_path)
            if not path_tuple:
                logger.error(self._ctx, self._TAG,
                             self.corpus_path_to_adapter_path.__name__, None,
                             CdmLogCode.ERR_STORAGE_NULL_CORPUS_PATH)
                return None
            namespace = path_tuple[0] or self.default_namespace

            # Get the adapter registered for this namespace
            namespace_adapter = self.fetch_adapter(namespace)
            if not namespace_adapter:
                logger.error(
                    self._ctx, self._TAG,
                    StorageManager.corpus_path_to_adapter_path.__name__, None,
                    CdmLogCode.ERR_STORAGE_NAMESPACE_NOT_REGISTERED, namespace)
            else:
                # Ask the storage adapter to 'adapt' this path
                result = namespace_adapter.create_adapter_path(path_tuple[1])

            return result
Exemple #2
0
    async def file_status_check_async(self) -> None:
        """Check the modified time for this object and any children."""
        with logger._enter_scope(self._TAG, self.ctx,
                                 self.file_status_check_async.__name__):
            adapter = self.ctx.corpus.storage.fetch_adapter(
                self.in_document.namespace)
            if adapter:
                context = adapter.create_file_query_cache_context()
                try:
                    modified_time = await self.ctx.corpus._fetch_last_modified_time_from_object_async(
                        self)

                    self.last_file_status_check_time = datetime.now(
                        timezone.utc)
                    if not self.last_file_modified_time:
                        self.last_file_modified_time = self._file_system_modified_time

                    # Reload the manifest if it has been updated in the file system.
                    if modified_time and self._file_system_modified_time and modified_time != self._file_system_modified_time:
                        await self._reload_async()
                        self.last_file_modified_time = time_utils._max_time(
                            modified_time, self.last_file_modified_time)
                        self._file_system_modified_time = self.last_file_modified_time

                    for entity in self.entities:
                        await entity.file_status_check_async()

                    for sub_manifest in self.sub_manifests:
                        await sub_manifest.file_status_check_async()

                finally:
                    context.dispose()
Exemple #3
0
    def mount(self, namespace: str, adapter: 'StorageAdapterBase') -> None:
        """registers a namespace and assigns creates a storage adapter for that namespace"""
        with logger._enter_scope(self._TAG, self._ctx, self.mount.__name__):
            if not namespace:
                logger.error(self._ctx, self._TAG,
                             StorageManager.mount.__name__, None,
                             CdmLogCode.ERR_STORAGE_NULL_NAMESPACE)
                return None

            from cdm.objectmodel import CdmFolderDefinition

            if adapter:
                if isinstance(adapter, StorageAdapterBase):
                    adapter.ctx = self._ctx
                self.namespace_adapters[namespace] = adapter
                fd = CdmFolderDefinition(self._ctx, '')
                fd._corpus = self._corpus
                fd.namespace = namespace
                fd.folder_path = '/'
                self._namespace_folders[namespace] = fd
                if namespace in self._system_defined_namespaces:
                    self._system_defined_namespaces.remove(namespace)
            else:
                logger.error(self._ctx, self._TAG,
                             StorageManager.mount.__name__, None,
                             CdmLogCode.ERR_STORAGE_NULL_ADAPTER)
    async def save_as_async(self,
                            new_name: str,
                            save_referenced: bool = False,
                            options: Optional['CopyOptions'] = None) -> bool:
        """saves the document back through the adapter in the requested format
        format is specified via document name/extension based on conventions:
        'model.json' for back compat model, '*.manifest.json' for manifest, '*.json' for cdm defs
        save_referenced (default False) when true will also save any schema defintion documents that are
        linked from the source doc and that have been modified. existing document names are used for those."""
        with logger._enter_scope(self._TAG, self.ctx,
                                 self.save_as_async.__name__):
            options = options if options is not None else CopyOptions()

            index_if_needed = await self._index_if_needed(
                ResolveOptions(
                    wrt_doc=self,
                    directives=self.ctx.corpus.default_resolution_directives))
            if not index_if_needed:
                logger.error(
                    self._TAG, self.ctx,
                    'Failed to index document prior to save {}.'.format(
                        self.name), self.save_as_async.__name__)
                return False

            if new_name == self.name:
                self._is_dirty = False

            return await self.ctx.corpus.persistence._save_document_as_async(
                self, options, new_name, save_referenced)
Exemple #5
0
    async def save_as_async(self,
                            new_name: str,
                            save_referenced: bool = False,
                            options: Optional['CopyOptions'] = None) -> bool:
        """saves the document back through the adapter in the requested format
        format is specified via document name/extension based on conventions:
        'model.json' for back compat model, '*.manifest.json' for manifest, '*.json' for cdm defs
        save_referenced (default False) when true will also save any schema defintion documents that are
        linked from the source doc and that have been modified. existing document names are used for those."""
        with logger._enter_scope(self._TAG, self.ctx,
                                 self.save_as_async.__name__):
            options = options if options is not None else CopyOptions()

            index_if_needed = await self._index_if_needed(
                ResolveOptions(
                    wrt_doc=self,
                    directives=self.ctx.corpus.default_resolution_directives))
            if not index_if_needed:
                logger.error(self.ctx, self._TAG, self.save_as_async.__name__,
                             self.at_corpus_path, CdmLogCode.ERR_INDEX_FAILED,
                             self.name)
                return False

            if new_name == self.name:
                self._is_dirty = False

            # Import here to avoid circular import
            from .cdm_entity_def import CdmEntityDefinition
            from .cdm_manifest_def import CdmManifestDefinition

            if not await self.ctx.corpus.persistence._save_document_as_async(
                    self, options, new_name, save_referenced):
                return False
            # Log the telemetry if the document is a manifest
            if isinstance(self, CdmManifestDefinition):
                for entity in self.entities:
                    if isinstance(entity, CdmLocalEntityDeclarationDefinition):
                        entity.reset_last_file_modified_old_time()
                for relationship in self.relationships:
                    relationship.reset_last_file_modified_old_time()
                logger._ingest_manifest_telemetry(
                    self, self.ctx, CdmDocumentDefinition.__name__,
                    self.save_as_async.__name__, self.at_corpus_path)

            # Log the telemetry of all entities contained in the document
            else:
                for obj in self.definitions:
                    if isinstance(obj, CdmEntityDefinition):
                        logger._ingest_entity_telemetry(
                            obj, self.ctx, CdmDocumentDefinition.__name__,
                            self.save_as_async.__name__, obj.at_corpus_path)

            return True
Exemple #6
0
    async def file_status_check_async(
        self,
        partition_file_status_check_type: Optional[
            'PartitionFileStatusCheckType'] = PartitionFileStatusCheckType.
        FULL,
        incremental_type: Optional[
            'CdmIncrementalPartitionType'] = CdmIncrementalPartitionType.NONE
    ) -> None:
        """
        Check the modified time for this object and any children.
        """
        with logger._enter_scope(self._TAG, self.ctx,
                                 self.file_status_check_async.__name__):
            adapter = self.ctx.corpus.storage.fetch_adapter(
                self.in_document._namespace)
            if adapter:
                context = adapter.create_file_query_cache_context()
                try:
                    modified_time = await self.ctx.corpus._get_last_modified_time_from_object_async(
                        self)

                    self.last_file_status_check_time = datetime.now(
                        timezone.utc)
                    if not self.last_file_modified_time:
                        self.last_file_modified_time = self._file_system_modified_time

                    # Reload the manifest if it has been updated in the file system.
                    if modified_time and self._file_system_modified_time and modified_time != self._file_system_modified_time:
                        await self._reload_async()
                        self.last_file_modified_time = time_utils._max_time(
                            modified_time, self.last_file_modified_time)
                        self._file_system_modified_time = self.last_file_modified_time

                    for entity in self.entities:
                        from cdm.objectmodel import CdmLocalEntityDeclarationDefinition
                        if isinstance(
                                entity,
                                CdmReferencedEntityDeclarationDefinition):
                            await entity.file_status_check_async()
                        elif isinstance(entity,
                                        CdmLocalEntityDeclarationDefinition):
                            await cast(CdmLocalEntityDeclarationDefinition,
                                       entity).file_status_check_async(
                                           partition_file_status_check_type,
                                           incremental_type)

                    for sub_manifest in self.sub_manifests:
                        await sub_manifest.file_status_check_async()

                finally:
                    context.dispose()
    async def file_status_check_async(self) -> None:
        """Check the modified time for this object and any children."""
        with logger._enter_scope(self._TAG, self.ctx,
                                 self.file_status_check_async.__name__):
            full_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                self.location, self.in_document)
            modified_time = await self.ctx.corpus._get_last_modified_time_from_partition_path_async(
                full_path)

            # Update modified times.
            self.last_file_status_check_time = datetime.now(timezone.utc)
            self.last_file_modified_time = time_utils._max_time(
                modified_time, self.last_file_modified_time)

            await self.report_most_recent_time_async(
                self.last_file_modified_time)
Exemple #8
0
    def create_relative_corpus_path(
            self,
            object_path: str,
            relative_to: Optional['CdmContainerDefinition'] = None):
        """Takes a corpus path (relative or absolute) and creates a valid relative corpus path with namespace.
            object_path: The path that should be made relative, if possible
            relative_to: The object that the path should be made relative with respect to."""
        with logger._enter_scope(self._TAG, self._ctx,
                                 self.create_relative_corpus_path.__name__):
            new_path = self.create_absolute_corpus_path(
                object_path, relative_to)

            namespace_string = relative_to.namespace + ':' if relative_to and relative_to.namespace else ''
            if namespace_string and new_path.startswith(namespace_string):
                new_path = new_path[len(namespace_string):]

                if relative_to and relative_to.folder_path and new_path.startswith(
                        relative_to.folder_path):
                    new_path = new_path[len(relative_to.folder_path):]
            return new_path
Exemple #9
0
    def fetch_root_folder(self, namespace: str) -> 'CdmFolderDefinition':
        """Given the namespace of a registered storage adapter, return the root
        folder containing the sub-folders and documents"""
        with logger._enter_scope(self._TAG, self._ctx,
                                 self.fetch_root_folder.__name__):
            if not namespace:
                logger.error(self._ctx, self._TAG,
                             StorageManager.fetch_root_folder.__name__, None,
                             CdmLogCode.ERR_STORAGE_NULL_NAMESPACE)
                return None

            if namespace in self._namespace_folders:
                return self._namespace_folders[namespace]
            elif self.default_namespace in self._namespace_folders:
                return self._namespace_folders[self.default_namespace]

            logger.error(self._ctx, self._TAG,
                         StorageManager.fetch_root_folder.__name__, None,
                         CdmLogCode.ERR_STORAGE_ADAPTER_NOT_FOUND, namespace)
            return None
Exemple #10
0
    def unmount(self, namespace: str) -> None:
        """unregisters a storage adapter and its root folder"""
        with logger._enter_scope(self._TAG, self._ctx, self.unmount.__name__):
            if not namespace:
                logger.error(self._ctx, self._TAG,
                             StorageManager.unmount.__name__, None,
                             CdmLogCode.ERR_STORAGE_NULL_NAMESPACE)
                return None

            if namespace in self.namespace_adapters:
                self.namespace_adapters.pop(namespace, None)
                self._namespace_folders.pop(namespace, None)
                if namespace in self._system_defined_namespaces:
                    self._system_defined_namespaces.remove(namespace)

                # The special case, use Resource adapter.
                if (namespace == 'cdm'):
                    self.mount(namespace, ResourceAdapter())
            else:
                logger.warning(self._ctx, self._TAG,
                               StorageManager.unmount.__name__, None,
                               CdmLogCode.WARN_STORAGE_REMOVE_ADAPTER_FAILED)
Exemple #11
0
    def adapter_path_to_corpus_path(self, adapter_path: str) -> Optional[str]:
        """Takes a storage adapter domain path, figures out the right adapter to use
        and then return a corpus path"""
        with logger._enter_scope(self._TAG, self._ctx,
                                 self.adapter_path_to_corpus_path.__name__):
            result = None

            # Keep trying adapters until one of them likes what it sees
            if self.namespace_adapters:
                for key, value in self.namespace_adapters.items():
                    result = value.create_corpus_path(adapter_path)
                    if result:
                        # Got one, add the prefix
                        result = '{}:{}'.format(key, result)
                        break

            if not result:
                logger.error(
                    self._ctx, self._TAG,
                    StorageManager.adapter_path_to_corpus_path.__name__, None,
                    CdmLogCode.ERR_STORAGE_INVALID_ADAPTER_PATH, adapter_path)
            return result
Exemple #12
0
    async def populate_manifest_relationships_async(
        self,
        option: CdmRelationshipDiscoveryStyle = CdmRelationshipDiscoveryStyle.
        ALL
    ) -> None:
        """Populate the relationships that the entities in the current manifest are involved in."""
        with logger._enter_scope(
                self._TAG, self.ctx,
                self.populate_manifest_relationships_async.__name__):
            self.relationships.clear()
            rel_cache = set()  # Set[str]

            for ent_dec in self.entities:
                ent_path = await self._get_entity_path_from_declaration(
                    ent_dec, self)
                curr_entity = await self.ctx.corpus.fetch_object_async(
                    ent_path)  # type: Optional[CdmEntityDefinition]

                if curr_entity is None:
                    continue

                # handle the outgoing relationships
                outgoing_rels = self.ctx.corpus.fetch_outgoing_relationships(
                    curr_entity)  # List[CdmE2ERelationship]
                if outgoing_rels:
                    for rel in outgoing_rels:
                        cache_key = rel2_cache_key(rel)
                        if cache_key not in rel_cache and self._is_rel_allowed(
                                rel, option):
                            self.relationships.append(
                                self._localize_rel_to_manifest(rel))
                            rel_cache.add(cache_key)

                incoming_rels = self.ctx.corpus.fetch_incoming_relationships(
                    curr_entity)  # type: List[CdmE2ERelationship]

                if incoming_rels:
                    for in_rel in incoming_rels:
                        # get entity object for current toEntity
                        current_in_base = await self.ctx.corpus.fetch_object_async(
                            in_rel.to_entity,
                            self)  # type: Optional[CdmEntityDefinition]

                        if not current_in_base:
                            continue

                        # create graph of inheritance for to current_in_base
                        # graph represented by an array where entity at i extends entity at i+1
                        to_inheritance_graph = [
                        ]  # type: List[CdmEntityDefinition]
                        while current_in_base:
                            res_opt = ResolveOptions(
                                wrt_doc=current_in_base.in_document)
                            current_in_base = current_in_base.extends_entity.fetch_object_definition(
                                res_opt
                            ) if current_in_base.extends_entity else None
                            if current_in_base:
                                to_inheritance_graph.append(current_in_base)

                        # add current incoming relationship
                        cache_key = rel2_cache_key(in_rel)
                        if cache_key not in rel_cache and self._is_rel_allowed(
                                in_rel, option):
                            self.relationships.append(
                                self._localize_rel_to_manifest(in_rel))
                            rel_cache.add(cache_key)

                        # if A points at B, A's base classes must point at B as well
                        for base_entity in to_inheritance_graph:
                            incoming_rels_for_base = self.ctx.corpus.fetch_incoming_relationships(
                                base_entity)  # type: List[CdmE2ERelationship]

                            if incoming_rels_for_base:
                                for in_rel_base in incoming_rels_for_base:
                                    new_rel = self.ctx.corpus.make_object(
                                        CdmObjectType.E2E_RELATIONSHIP_DEF, '')
                                    new_rel.from_entity = in_rel_base.from_entity
                                    new_rel.from_entity_attribute = in_rel_base.from_entity_attribute
                                    new_rel.to_entity = in_rel.to_entity
                                    new_rel.to_entity_attribute = in_rel.to_entity_attribute

                                    base_rel_cache_key = rel2_cache_key(
                                        new_rel)
                                    if base_rel_cache_key not in rel_cache and self._is_rel_allowed(
                                            new_rel, option):
                                        self.relationships.append(
                                            self._localize_rel_to_manifest(
                                                new_rel))
                                        rel_cache.add(base_rel_cache_key)
            if self.sub_manifests:
                for sub_manifest_def in self.sub_manifests:
                    corpus_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                        sub_manifest_def.definition, self)
                    sub_manifest = await self.ctx.corpus.fetch_object_async(
                        corpus_path)  # type: Optional[CdmManifestDefinition]
                    await sub_manifest.populate_manifest_relationships_async(
                        option)
Exemple #13
0
    async def create_resolved_entity_async(
            self,
            new_ent_name: str,
            res_opt: Optional['ResolveOptions'] = None,
            folder: 'CdmFolderDefinition' = None,
            new_doc_name: str = None) -> 'CdmEntityDefinition':
        """Create a resolved copy of the entity."""
        with logger._enter_scope(self._TAG, self.ctx,
                                 self.create_resolved_entity_async.__name__):
            if not res_opt:
                res_opt = ResolveOptions(
                    self, self.ctx.corpus.default_resolution_directives)

            if not res_opt.wrt_doc:
                logger.error(self._TAG, self.ctx,
                             'No WRT document was supplied.',
                             self.create_resolved_entity_async.__name__)
                return None

            if not new_ent_name:
                logger.error(self._TAG, self.ctx, 'No Entity Name provided.',
                             self.create_resolved_entity_async.__name__)
                return None

            folder = folder or self.in_document.folder
            file_name = new_doc_name or new_ent_name + PersistenceLayer.CDM_EXTENSION
            orig_doc = self.in_document.at_corpus_path

            # Don't overwite the source document.
            target_at_corpus_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                folder.at_corpus_path, folder) + file_name
            if StringUtils.equals_with_ignore_case(target_at_corpus_path,
                                                   orig_doc):
                logger.error(
                    self._TAG, self.ctx,
                    'Attempting to replace source entity\'s document \'{}\''.
                    format(target_at_corpus_path),
                    self.create_resolved_entity_async.__name__)
                return None

            if not await res_opt.wrt_doc._index_if_needed(res_opt, True):
                logger.error(self._TAG, self.ctx,
                             'Couldn\'t index source document.',
                             self.create_resolved_entity_async.__name__)
                return None

            # Make the top level attribute context for this entity.
            # for this whole section where we generate the attribute context tree and get resolved attributes
            # set the flag that keeps all of the parent changes and document dirty from from happening
            was_resolving = self.ctx.corpus._is_currently_resolving
            self.ctx.corpus._is_currently_resolving = True
            ent_name = new_ent_name
            ctx = self.ctx
            att_ctx_ent = ctx.corpus.make_object(
                CdmObjectType.ATTRIBUTE_CONTEXT_DEF, ent_name,
                True)  # type: CdmAttributeContext
            att_ctx_ent.ctx = ctx
            att_ctx_ent.in_document = self.in_document

            # Cheating a bit to put the paths in the right place.
            acp = AttributeContextParameters()
            acp._under = att_ctx_ent
            acp._type = CdmAttributeContextType.ATTRIBUTE_GROUP
            acp._name = 'attributeContext'

            att_ctx_ac = CdmAttributeContext._create_child_under(res_opt, acp)
            acp_ent = AttributeContextParameters()
            acp_ent._under = att_ctx_ac
            acp_ent._type = CdmAttributeContextType.ENTITY
            acp_ent._name = ent_name
            acp_ent._regarding = ctx.corpus.make_ref(CdmObjectType.ENTITY_REF,
                                                     self.get_name(), True)

            # Use this whenever we need to keep references pointing at things that were already found.
            # Used when 'fixing' references by localizing to a new document.
            res_opt_copy = res_opt.copy()
            res_opt_copy._save_resolutions_on_copy = True

            # Resolve attributes with this context. the end result is that each resolved attribute points to the level of
            # the context where it was created.
            ras = self._fetch_resolved_attributes(res_opt_copy, acp_ent)

            if ras is None:
                self.resolving_attributes = False
                return None

            # Create a new copy of the attribute context for this entity.
            # TODO: all_att_ctx type ideally should be ordered set
            all_att_ctx = []  # type: List[CdmAttributeContext]
            new_node = cast('CdmAttributeContext',
                            att_ctx_ent.copy_node(res_opt))
            att_ctx_ent = att_ctx_ent._copy_attribute_context_tree(
                res_opt, new_node, ras, all_att_ctx, 'resolvedFrom')
            att_ctx = cast(
                'CdmAttributeContext',
                cast('CdmAttributeContext',
                     att_ctx_ent.contents[0]).contents[0])

            self.ctx.corpus._is_currently_resolving = was_resolving

            # make a new document in given folder if provided or the same folder as the source entity
            folder.documents.remove(file_name)
            doc_res = folder.documents.append(file_name)
            # add a import of the source document
            orig_doc = self.ctx.corpus.storage.create_relative_corpus_path(
                orig_doc, doc_res)  # just in case we missed the prefix
            doc_res.imports.append(orig_doc, "resolvedFrom")

            # make the empty entity
            ent_resolved = doc_res.definitions.append(ent_name)
            # set the context to the copy of the tree. fix the docs on the context nodes
            ent_resolved.attribute_context = att_ctx

            def visit_callback(obj: 'CdmObject', path: str) -> bool:
                obj.in_document = doc_res
                return False

            att_ctx.visit('{}/attributeContext/'.format(ent_name),
                          visit_callback, None)

            # add the traits of the entity
            rts_ent = self._fetch_resolved_traits(res_opt)
            for rt in rts_ent.rt_set:
                trait_ref = CdmObject._resolved_trait_to_trait_ref(
                    res_opt_copy, rt)
                ent_resolved.exhibits_traits.append(trait_ref)

            # The attributes have been named, shaped, etc. for this entity so now it is safe to go and make each attribute
            # context level point at these final versions of attributes.
            att_path_to_order = {}  # type: Dict[str, int]

            def point_context_at_resolved_atts(ras_sub: 'ResolvedAttributeSet',
                                               path: str) -> None:
                for ra in ras_sub._set:
                    ra_ctx_in_ent = []  # type: List[CdmAttributeContext]
                    ra_ctx_set = ras_sub.rattr_to_attctxset.get(ra)

                    # find the correct attctx for this copy, intersect these two sets
                    # (interate over the shortest list)
                    if len(all_att_ctx) < len(ra_ctx_set):
                        for curr_att_ctx in all_att_ctx:
                            if curr_att_ctx in ra_ctx_set:
                                ra_ctx_in_ent.append(curr_att_ctx)
                    else:
                        for curr_att_ctx in ra_ctx_set:
                            if curr_att_ctx in all_att_ctx:
                                ra_ctx_in_ent.append(curr_att_ctx)
                    for ra_ctx in ra_ctx_in_ent:
                        #  there might be more than one explanation for where and attribute came from when things get merges as they do
                        # This won't work when I add the structured attributes to avoid name collisions.
                        att_ref_path = path + ra.resolved_name
                        if isinstance(ra.target, CdmObject):
                            if not att_ref_path in att_path_to_order:
                                att_ref = self.ctx.corpus.make_object(
                                    CdmObjectType.ATTRIBUTE_REF, att_ref_path,
                                    True)  # type: CdmObjectReference
                                # only need one explanation for this path to the insert order
                                att_path_to_order[
                                    att_ref.named_reference] = ra.insert_order
                                ra_ctx.contents.append(att_ref)
                        else:
                            att_ref_path += '/members/'
                            point_context_at_resolved_atts(
                                cast('ResolvedAttributeSet', ra.target),
                                att_ref_path)

            point_context_at_resolved_atts(ras, ent_name + '/hasAttributes/')

            # generated attribute structures may end up with 0 attributes after that. prune them
            def clean_sub_group(sub_item, under_generated) -> bool:
                if sub_item.object_type == CdmObjectType.ATTRIBUTE_REF:
                    return True  # not empty

                ac = sub_item  # type: CdmAttributeContext

                if ac.type == CdmAttributeContextType.GENERATED_SET:
                    under_generated = True
                if not ac.contents:
                    return False  # empty

                # look at all children, make a set to remove
                to_remove = []  # type: List[CdmAttributeContext]
                for sub_sub in ac.contents:
                    if not clean_sub_group(sub_sub, under_generated):
                        potential_target = under_generated
                        if not potential_target:
                            # cast is safe because we returned false meaning empty and not a attribute ref
                            # so is this the set holder itself?
                            potential_target = sub_sub.type == CdmAttributeContextType.GENERATED_SET
                        if potential_target:
                            to_remove.append(sub_sub)
                for to_die in to_remove:
                    ac.contents.remove(to_die)
                return bool(ac.contents)

            clean_sub_group(att_ctx, False)

            # Create an all-up ordering of attributes at the leaves of this tree based on insert order. Sort the attributes
            # in each context by their creation order and mix that with the other sub-contexts that have been sorted.
            def get_order_num(item: 'CdmObject') -> int:
                if item.object_type == CdmObjectType.ATTRIBUTE_CONTEXT_DEF:
                    return order_contents(item)
                if item.object_type == CdmObjectType.ATTRIBUTE_REF:
                    return att_path_to_order[item.named_reference]
                # put the mystery item on top.
                return -1

            def order_contents(under: 'CdmAttributeContext') -> int:
                if under._lowest_order is None:
                    under._lowest_order = -1
                    if len(under.contents) == 1:
                        under._lowest_order = get_order_num(under.contents[0])
                    else:

                        def get_and_update_order(item1: 'CdmObject',
                                                 item2: 'CdmObject'):
                            left_order = get_order_num(item1)
                            right_order = get_order_num(item2)
                            if left_order != -1 and (
                                    under._lowest_order == -1
                                    or left_order < under._lowest_order):
                                under._lowest_order = left_order
                            if right_order != -1 and (
                                    under._lowest_order == -1
                                    or right_order < under._lowest_order):
                                under._lowest_order = right_order
                            return left_order - right_order

                        under.contents.sort(
                            key=functools.cmp_to_key(get_and_update_order))
                return under._lowest_order

            order_contents(att_ctx)

            # Resolved attributes can gain traits that are applied to an entity when referenced since these traits are
            # described in the context, it is redundant and messy to list them in the attribute. So, remove them. Create and
            # cache a set of names to look for per context. There is actually a hierarchy to this. All attributes from the
            # base entity should have all traits applied independently of the sub-context they come from. Same is true of
            # attribute entities. So do this recursively top down.
            ctx_to_trait_names = {
            }  # type: Dict[CdmAttributeContext, Set[str]]

            def collect_context_traits(
                    sub_att_ctx: 'CdmAttributeContext',
                    inherited_trait_names: Set[str]) -> None:
                trait_names_here = set(inherited_trait_names)
                traits_here = sub_att_ctx.exhibits_traits
                if traits_here:
                    for tat in traits_here:
                        trait_names_here.add(tat.named_reference)

                ctx_to_trait_names[sub_att_ctx] = trait_names_here
                for cr in sub_att_ctx.contents:
                    if cr.object_type == CdmObjectType.ATTRIBUTE_CONTEXT_DEF:
                        # do this for all types?
                        collect_context_traits(cast('CdmAttributeContext', cr),
                                               trait_names_here)

            collect_context_traits(att_ctx, set())

            # add the attributes, put them in attribute groups if structure needed.
            res_att_to_ref_path = {}  # type: Dict[ResolvedAttribute, str]

            def add_attributes(
                    ras_sub: 'ResolvedAttributeSet', container:
                'Union[CdmEntityDefinition, CdmAttributeGroupDefinition]',
                    path: str) -> None:
                for ra in ras_sub._set:
                    att_path = path + ra.resolved_name
                    # use the path of the context associated with this attribute to find the new context that matches on path.
                    ra_ctx_set = ras_sub.rattr_to_attctxset[ra]
                    # find the correct att_ctx for this copy.
                    # ra_ctx = next((ac for ac in all_att_ctx if ac in ra_ctx_set), None) # type: CdmAttributeContext

                    if len(all_att_ctx) < len(ra_ctx_set):
                        for curr_att_ctx in all_att_ctx:
                            if curr_att_ctx in ra_ctx_set:
                                ra_ctx = curr_att_ctx
                                break
                    else:
                        for curr_att_ctx in ra_ctx_set:
                            if curr_att_ctx in all_att_ctx:
                                ra_ctx = curr_att_ctx
                                break

                    if isinstance(ra.target, ResolvedAttributeSet) and cast(
                            'ResolvedAttributeSet', ra.target)._set:
                        # this is a set of attributes. Make an attribute group to hold them.
                        att_grp = self.ctx.corpus.make_object(
                            CdmObjectType.ATTRIBUTE_GROUP_DEF, ra.resolved_name
                        )  # type: CdmAttributeGroupDefinition
                        att_grp.attribute_context = self.ctx.corpus.make_object(
                            CdmObjectType.ATTRIBUTE_CONTEXT_REF,
                            ra_ctx.at_corpus_path, True)
                        # take any traits from the set and make them look like traits exhibited by the group.
                        avoid_set = ctx_to_trait_names[ra_ctx]
                        rts_att = ra.resolved_traits
                        for rt in rts_att.rt_set:
                            if not rt.trait.ugly:  # Don't mention your ugly traits.
                                if not avoid_set or rt.trait_name not in avoid_set:  # Avoid the ones from the context.
                                    trait_ref = CdmObject._resolved_trait_to_trait_ref(
                                        res_opt_copy, rt)
                                    cast('CdmObjectDefinition',
                                         att_grp).exhibits_traits.append(
                                             trait_ref,
                                             isinstance(trait_ref, str))

                        # wrap it in a reference and then recurse with this as the new container.
                        att_grp_ref = self.ctx.corpus.make_object(
                            CdmObjectType.ATTRIBUTE_GROUP_REF,
                            None)  # type: CdmAttributeGroupReference
                        att_grp_ref.explicit_reference = att_grp
                        container._add_attribute_def(att_grp_ref)
                        # isn't this where ...
                        add_attributes(cast('ResolvedAttributeSet',
                                            ra.target), att_grp,
                                       att_path + '/(object)/members/')
                    else:
                        att = self.ctx.corpus.make_object(
                            CdmObjectType.TYPE_ATTRIBUTE_DEF, ra.resolved_name
                        )  # type: CdmTypeAttributeDefinition
                        att.attribute_context = self.ctx.corpus.make_object(
                            CdmObjectType.ATTRIBUTE_CONTEXT_REF,
                            ra_ctx.at_corpus_path, True)
                        avoid_set = ctx_to_trait_names[ra_ctx]
                        rts_att = ra.resolved_traits
                        for rt in rts_att.rt_set:
                            if not rt.trait.ugly:  # Don't mention your ugly traits.
                                if not avoid_set or rt.trait_name not in avoid_set:  # Avoid the ones from the context.
                                    trait_ref = CdmObject._resolved_trait_to_trait_ref(
                                        res_opt_copy, rt)
                                    cast('CdmTypeAttributeDefinition',
                                         att).applied_traits.append(
                                             trait_ref,
                                             isinstance(trait_ref, str))

                        # none of the dataformat traits have the bit set that will make them turn into a property
                        # this is intentional so that the format traits make it into the resolved object
                        # but, we still want a guess as the data format, so get it and set it.
                        implied_data_format = att.data_format
                        if implied_data_format:
                            att.data_format = implied_data_format

                        container._add_attribute_def(att)
                        res_att_to_ref_path[ra] = att_path

            add_attributes(ras, ent_resolved, ent_name + '/hasAttributes/')

            # Any resolved traits that hold arguments with attribute refs should get 'fixed' here.
            def replace_trait_att_ref(tr: 'CdmTraitReference',
                                      entity_hint: str) -> None:
                if tr.arguments:
                    for arg in tr.arguments:
                        v = arg._unresolved_value or arg.value
                        # Is this an attribute reference?
                        if v and isinstance(v, CdmObject) and cast(
                                'CdmObject',
                                v).object_type == CdmObjectType.ATTRIBUTE_REF:
                            # Only try this if the reference has no path to it (only happens with intra-entity att refs).
                            att_ref = cast('CdmAttributeReference', v)
                            if att_ref.named_reference and att_ref.named_reference.find(
                                    '/') == -1:
                                if not arg._unresolved_value:
                                    arg._unresolved_value = arg.value
                                # Give a promise that can be worked out later.
                                # Assumption is that the attribute must come from this entity.
                                new_att_ref = self.ctx.corpus.make_object(
                                    CdmObjectType.ATTRIBUTE_REF,
                                    entity_hint + '/(resolvedAttributes)/' +
                                    att_ref.named_reference, True)
                                # inDocument is not propagated during resolution, so set it here
                                new_att_ref.in_document = arg.in_document
                                arg.set_value(new_att_ref)

            # Fix entity traits.
            if ent_resolved.exhibits_traits:
                for et in ent_resolved.exhibits_traits:
                    replace_trait_att_ref(et, new_ent_name)

            # Fix context traits.
            def fix_context_traits(sub_att_ctx: 'CdmAttributeContext',
                                   entity_hint: str) -> None:
                traits_here = sub_att_ctx.exhibits_traits
                if traits_here:
                    for tr in traits_here:
                        replace_trait_att_ref(tr, entity_hint)

                for cr in sub_att_ctx.contents:
                    if cr.object_type == CdmObjectType.ATTRIBUTE_CONTEXT_DEF:
                        # If this is a new entity context, get the name to pass along.
                        sub_sub_att_ctx = cast('CdmAttributeContext', cr)
                        sub_entity_hint = entity_hint
                        if sub_sub_att_ctx.type == CdmAttributeContextType.ENTITY:
                            sub_entity_hint = sub_sub_att_ctx.definition.named_reference
                        # Do this for all types.
                        fix_context_traits(sub_sub_att_ctx, sub_entity_hint)

            fix_context_traits(att_ctx, new_ent_name)

            # And the attribute traits.
            ent_atts = ent_resolved.attributes
            if ent_atts:
                for attribute in ent_atts:
                    att_traits = attribute.applied_traits
                    if att_traits:
                        for tr in att_traits:
                            replace_trait_att_ref(tr, new_ent_name)

            # We are about to put this content created in the context of various documents (like references to attributes
            # from base entities, etc.) into one specific document. All of the borrowed refs need to work. so, re-write all
            # string references to work from this new document. The catch-22 is that the new document needs these fixes done
            # before it can be used to make these fixes. The fix needs to happen in the middle of the refresh trigger the
            # document to refresh current content into the resolved OM.
            cast(
                'CdmAttributeContext', att_ctx
            ).parent = None  # Remove the fake parent that made the paths work.
            res_opt_new = res_opt.copy()
            res_opt_new.wrt_doc = doc_res
            res_opt_new._localize_references_for = doc_res
            if not await doc_res.refresh_async(res_opt_new):
                logger.error(self._TAG, self.ctx,
                             'Failed to index the resolved document.',
                             self.create_resolved_entity_async.__name__)
                return None

            # Get a fresh ref.
            ent_resolved = cast(
                'CdmEntityDefinition',
                doc_res._fetch_object_from_document_path(
                    ent_name, res_opt_new))

            self.ctx.corpus._res_ent_map[
                self.at_corpus_path] = ent_resolved.at_corpus_path

            return ent_resolved
Exemple #14
0
    async def create_resolved_manifest_async(
        self,
        new_manifest_name: str,
        new_entity_document_name_format: Optional[str],
        directives: Optional[AttributeResolutionDirectiveSet] = None
    ) -> Optional['CdmManifestDefinition']:
        """Creates a resolved copy of the manifest.
        new_entity_document_name_format specifies a pattern to use when creating documents for resolved entities.
        The default is "resolved/{n}.cdm.json" to avoid a document name conflict with documents in the same folder as
        the manifest. Every instance of the string {n} is replaced with the entity name from the source manifest. Any
        sub-folders described by the pattern should exist in the corpus prior to calling this function.
        """
        with logger._enter_scope(self._TAG, self.ctx,
                                 self.create_resolved_manifest_async.__name__):
            if self.entities is None:
                return None

            if not self.folder:
                logger.error(self.ctx, self._TAG,
                             self.create_resolved_manifest_async.__name__,
                             self.at_corpus_path,
                             CdmLogCode.ERR_RESOLVE_MANIFEST_FAILED,
                             self.manifest_name)
                return None

            if new_entity_document_name_format is None:
                new_entity_document_name_format = '{f}resolved/{n}.cdm.json'
            elif new_entity_document_name_format == '':  # for back compat
                new_entity_document_name_format = '{n}.cdm.json'
            elif '{n}' not in new_entity_document_name_format:  # for back compat
                new_entity_document_name_format = new_entity_document_name_format + '/{n}.cdm.json'

            source_manifest_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                self.at_corpus_path, self)
            source_manifest_folder_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                self.folder.at_corpus_path, self)

            resolved_manifest_path_split = new_manifest_name.rfind('/') + 1
            resolved_manifest_folder = None
            if resolved_manifest_path_split > 0:
                resolved_manifest_path = new_manifest_name[
                    0:resolved_manifest_path_split]
                new_folder_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                    resolved_manifest_path, self)
                resolved_manifest_folder = await self.ctx.corpus.fetch_object_async(
                    new_folder_path)  # type: CdmFolderDefinition
                if resolved_manifest_folder is None:
                    logger.error(self.ctx, self._TAG,
                                 self.create_resolved_manifest_async.__name__,
                                 self.at_corpus_path,
                                 CdmLogCode.ERR_RESOLVE_FOLDER_NOT_FOUND,
                                 new_folder_path)
                    return None
                new_manifest_name = new_manifest_name[
                    resolved_manifest_path_split:]
            else:
                resolved_manifest_folder = self.owner

            logger.debug(self.ctx, self._TAG,
                         self.create_resolved_manifest_async.__name__,
                         self.at_corpus_path,
                         'resolving manifest {}'.format(source_manifest_path))

            # using the references present in the resolved entities, get an entity
            # create an imports doc with all the necessary resolved entity references and then resolve it
            # sometimes they might send the docname, that makes sense a bit, don't include the suffix in the name
            if new_manifest_name.lower().endswith('.manifest.cdm.json'):
                new_manifest_name = new_manifest_name[0:(
                    len(new_manifest_name) - len('.manifest.cdm.json'))]
            resolved_manifest = CdmManifestDefinition(self.ctx,
                                                      new_manifest_name)

            # bring over any imports in this document or other bobbles
            resolved_manifest.schema = self.schema
            resolved_manifest.explanation = self.explanation
            resolved_manifest.document_version = self.document_version
            for imp in self.imports:
                resolved_manifest.imports.append(imp.copy())

            # add the new document to the folder
            if resolved_manifest_folder.documents.append(
                    resolved_manifest) is None:
                # when would this happen?
                return None

            for entity in self.entities:
                ent_def = await self._get_entity_from_reference(entity, self)

                if not ent_def:
                    logger.error(self.ctx, self._TAG,
                                 self.create_resolved_manifest_async.__name__,
                                 self.at_corpus_path,
                                 CdmLogCode.ERR_RESOLVE_ENTITY_REF_ERROR)
                    return None

                if not ent_def.in_document.folder:
                    logger.error(self.ctx, self._TAG,
                                 self.create_resolved_manifest_async.__name__,
                                 self.at_corpus_path,
                                 CdmLogCode.ERR_DOC_IS_NOT_FOLDERformat,
                                 ent_def.entity_name)
                    return None

                # get the path from this manifest to the source entity. this will be the {f} replacement value
                source_entity_full_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                    ent_def.in_document.folder.at_corpus_path, self)
                f = ''
                if source_entity_full_path.startswith(
                        source_manifest_folder_path):
                    f = source_entity_full_path[len(source_manifest_folder_path
                                                    ):]

                new_document_full_path = new_entity_document_name_format.replace(
                    '{n}', ent_def.entity_name).replace('{f}', f)
                new_document_full_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                    new_document_full_path, self)

                new_document_path_split = new_document_full_path.rfind('/') + 1
                new_document_path = new_document_full_path[
                    0:new_document_path_split]
                new_document_name = new_document_full_path[
                    new_document_path_split:]

                # make sure the new folder exists
                folder = await self.ctx.corpus.fetch_object_async(
                    new_document_path)  # type: CdmFolderDefinition
                if not folder:
                    logger.error(self.ctx, self._TAG,
                                 self.create_resolved_manifest_async.__name__,
                                 self.at_corpus_path,
                                 CdmLogCode.ERR_RESOLVE_FOLDER_NOT_FOUND,
                                 new_document_path)
                    return None

                # next create the resolved entity.
                with_directives = directives if directives is not None else self.ctx.corpus.default_resolution_directives
                res_opt = ResolveOptions(ent_def.in_document,
                                         with_directives.copy())

                logger.debug(
                    self.ctx, self._TAG,
                    self.create_resolved_manifest_async.__name__,
                    self.at_corpus_path,
                    'resolving entity {} to document {}'.format(
                        source_entity_full_path, new_document_full_path))

                resolved_entity = await ent_def.create_resolved_entity_async(
                    ent_def.entity_name, res_opt, folder, new_document_name)
                if not resolved_entity:
                    # fail all resolution, if any one entity resolution fails
                    return None

                result = entity.copy(res_opt)
                if result.object_type == CdmObjectType.LOCAL_ENTITY_DECLARATION_DEF:
                    relative_entity_path = self.ctx.corpus.storage.create_relative_corpus_path(
                        resolved_entity.at_corpus_path, resolved_manifest)
                    result.entity_path = relative_entity_path or result.at_corpus_path

                resolved_manifest.entities.append(result)

            logger.debug(self.ctx, self._TAG,
                         self.create_resolved_manifest_async.__name__,
                         self.at_corpus_path, 'calculating relationships')
            # Calculate the entity graph for just this manifest.
            await self.ctx.corpus.calculate_entity_graph_async(
                resolved_manifest)
            # Stick results into the relationships list for the manifest.
            await resolved_manifest.populate_manifest_relationships_async(
                CdmRelationshipDiscoveryStyle.EXCLUSIVE)

            # needed until Matt's changes with collections where I can propigate
            resolved_manifest._is_dirty = True
            return resolved_manifest
    async def file_status_check_async(self) -> None:
        """Check the modified time for this object and any children."""
        with logger._enter_scope(self._TAG, self.ctx,
                                 self.file_status_check_async.__name__):
            namespace = None
            adapter = None

            # make sure the root is a good full corpus path.
            root_cleaned = (self.root_location[:-1] if self.root_location
                            and self.root_location.endswith('/') else
                            self.root_location) or ''
            root_corpus = self.ctx.corpus.storage.create_absolute_corpus_path(
                root_cleaned, self.in_document)

            try:
                # Remove namespace from path
                path_tuple = StorageUtils.split_namespace_path(root_corpus)
                if not path_tuple:
                    logger.error(
                        self.ctx, self._TAG, CdmDataPartitionPatternDefinition.
                        file_status_check_async.__name__, self.at_corpus_path,
                        CdmLogCode.ERR_STORAGE_NULL_CORPUS_PATH)
                    return

                namespace = path_tuple[0]
                adapter = self.ctx.corpus.storage.fetch_adapter(namespace)

                if adapter is None:
                    logger.error(
                        self.ctx, self._TAG, CdmDataPartitionPatternDefinition.
                        file_status_check_async.__name__, self.at_corpus_path,
                        CdmLogCode.ERR_DOC_ADAPTER_NOT_FOUND,
                        self.in_document.name)

                # get a list of all corpus_paths under the root.
                file_info_list = await adapter.fetch_all_files_async(
                    path_tuple[1])
            except Exception as e:
                file_info_list = None
                logger.warning(
                    self.ctx, self._TAG, CdmDataPartitionPatternDefinition.
                    file_status_check_async.__name__, self.at_corpus_path,
                    CdmLogCode.WARN_PARTITION_FILE_FETCH_FAILED, root_corpus,
                    e)

            if file_info_list is not None and namespace is not None:
                # remove root of the search from the beginning of all paths so anything in the root is not found by regex.
                file_info_list = [(namespace + ':' + fi)[len(root_corpus):]
                                  for fi in file_info_list]

                if isinstance(self.owner, CdmLocalEntityDeclarationDefinition):
                    local_ent_dec_def_owner = cast(
                        'CdmLocalEntityDeclarationDefinition', self.owner)
                    # if both are present log warning and use glob pattern, otherwise use regularExpression
                    if self.glob_pattern and not self.glob_pattern.isspace(
                    ) and self.regular_expression and not self.regular_expression.isspace(
                    ):
                        logger.warning(
                            self.ctx, self._TAG,
                            CdmDataPartitionPatternDefinition.
                            file_status_check_async.__name__,
                            self.at_corpus_path,
                            CdmLogCode.WARN_PARTITION_GLOB_AND_REGEX_PRESENT,
                            self.glob_pattern, self.regular_expression)
                    regular_expression = self.glob_pattern_to_regex(
                        self.glob_pattern
                    ) if self.glob_pattern and not self.glob_pattern.isspace(
                    ) else self.regular_expression

                    try:
                        reg = regex.compile(regular_expression)
                    except Exception as e:
                        logger.error(
                            self.ctx, self._TAG,
                            CdmDataPartitionPatternDefinition.
                            file_status_check_async.__name__,
                            self.at_corpus_path,
                            CdmLogCode.ERR_VALDN_INVALID_EXPRESSION,
                            'glob pattern' if self.glob_pattern
                            and not self.glob_pattern.isspace() else
                            'regular expression',
                            self.glob_pattern if self.glob_pattern
                            and not self.glob_pattern.isspace() else
                            self.regular_expression, e)

                    if reg:
                        # a set to check if the data partition exists
                        data_partition_path_set = set()
                        if local_ent_dec_def_owner.data_partitions is not None:
                            for data_partition in local_ent_dec_def_owner.data_partitions:
                                data_partition_location_full_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                                    data_partition.location, self.in_document)
                                data_partition_path_set.add(
                                    data_partition_location_full_path)

                        incremental_partition_path_hash_set = set()
                        if local_ent_dec_def_owner.data_partitions is not None:
                            for incremental_partition in local_ent_dec_def_owner.incremental_partitions:
                                incremental_partition_location_full_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                                    incremental_partition.location,
                                    self.in_document)
                                incremental_partition_path_hash_set.add(
                                    incremental_partition_location_full_path)

                        for fi in file_info_list:
                            m = reg.fullmatch(fi)
                            if m:
                                # create a map of arguments out of capture groups.
                                args = defaultdict(
                                    list)  # type: Dict[str, List[str]]
                                i_param = 0
                                for i in range(1, reg.groups + 1):
                                    captures = m.captures(i)
                                    if captures and self.parameters and i_param < len(
                                            self.parameters):
                                        # to be consistent with other languages, if a capture group captures
                                        # multiple things, only use the last thing that was captured
                                        single_capture = captures[-1]

                                        current_param = self.parameters[
                                            i_param]
                                        args[current_param].append(
                                            single_capture)
                                        i_param += 1
                                    else:
                                        break

                                # put the original but cleaned up root back onto the matched doc as the location stored in the partition.
                                location_corpus_path = root_cleaned + fi
                                full_path = root_corpus + fi
                                # Remove namespace from path
                                path_tuple = StorageUtils.split_namespace_path(
                                    full_path)
                                if not path_tuple:
                                    logger.error(
                                        self.ctx, self._TAG,
                                        CdmDataPartitionPatternDefinition.
                                        file_status_check_async.__name__,
                                        self.at_corpus_path, CdmLogCode.
                                        ERR_STORAGE_NULL_CORPUS_PATH)
                                    return
                                last_modified_time = await adapter.compute_last_modified_time_async(
                                    path_tuple[1])

                                if self.is_incremental and full_path not in incremental_partition_path_hash_set:
                                    local_ent_dec_def_owner._create_partition_from_pattern(
                                        location_corpus_path,
                                        self.exhibits_traits, args,
                                        self.specialized_schema,
                                        last_modified_time, True, self.name)
                                    incremental_partition_path_hash_set.add(
                                        full_path)

                                if not self.is_incremental and full_path not in data_partition_path_set:
                                    local_ent_dec_def_owner._create_partition_from_pattern(
                                        location_corpus_path,
                                        self.exhibits_traits, args,
                                        self.specialized_schema,
                                        last_modified_time)
                                    data_partition_path_set.add(full_path)

                    # update modified times.
            self.last_file_status_check_time = datetime.now(timezone.utc)
Exemple #16
0
    def create_absolute_corpus_path(self,
                                    object_path: str,
                                    obj: 'CdmObject' = None) -> Optional[str]:
        """Takes a corpus path (relative or absolute) and creates a valid absolute
        path with namespace"""
        with logger._enter_scope(self._TAG, self._ctx,
                                 self.create_absolute_corpus_path.__name__):
            if not object_path:
                logger.error(
                    self._ctx, self._TAG,
                    StorageManager.create_absolute_corpus_path.__name__, None,
                    CdmLogCode.ERR_PATH_NULL_OBJECT_PATH)
                return None

            if self._contains_unsupported_path_format(object_path):
                # Already called status_rpt when checking for unsupported path format.
                return None

            path_tuple = StorageUtils.split_namespace_path(object_path)
            if not path_tuple:
                logger.error(
                    self._ctx, self._TAG,
                    StorageManager.create_absolute_corpus_path.__name__, None,
                    CdmLogCode.ERR_PATH_NULL_OBJECT_PATH)
                return None
            final_namespace = ''

            prefix = None
            namespace_from_obj = None

            if obj and hasattr(obj, 'namespace') and hasattr(
                    obj, 'folder_path'):
                prefix = obj.folder_path
                namespace_from_obj = obj.namespace
            elif obj and obj.in_document:
                prefix = obj.in_document.folder_path
                namespace_from_obj = obj.in_document.namespace

            if prefix and self._contains_unsupported_path_format(prefix):
                # Already called status_rpt when checking for unsupported path format.
                return None

            if prefix and prefix[-1] != '/':
                logger.warning(
                    self._ctx, self._TAG,
                    StorageManager.create_absolute_corpus_path.__name__, None,
                    CdmLogCode.WARN_STORAGE_EXPECTED_PATH_PREFIX, prefix)
                prefix += '/'

            # check if this is a relative path
            if path_tuple[1][0] != '/':
                if not obj:
                    # relative path and no other info given, assume default and root
                    prefix = '/'

                if path_tuple[0] and path_tuple[0] != namespace_from_obj:
                    logger.error(
                        self._ctx, self._TAG,
                        StorageManager.create_absolute_corpus_path.__name__,
                        None, CdmLogCode.ERR_STORAGE_NAMESPACE_MISMATCH,
                        path_tuple[0])
                    return None

                path_tuple = (path_tuple[0], prefix + path_tuple[1])
                final_namespace = namespace_from_obj or path_tuple[
                    0] or self.default_namespace
            else:
                final_namespace = path_tuple[
                    0] or namespace_from_obj or self.default_namespace

            return '{}:{}'.format(
                final_namespace,
                path_tuple[1]) if final_namespace else path_tuple[1]
Exemple #17
0
    async def populate_manifest_relationships_async(
        self,
        option: CdmRelationshipDiscoveryStyle = CdmRelationshipDiscoveryStyle.
        ALL
    ) -> None:
        """Populate the relationships that the entities in the current manifest are involved in."""
        with logger._enter_scope(
                self._TAG, self.ctx,
                self.populate_manifest_relationships_async.__name__):
            self.relationships.clear()
            rel_cache = set()  # Set[str]

            if self.entities:
                # Indexes on this manifest before calling `AddElevatedTraitsAndRelationships`
                # and calls `RefreshAsync` after adding all imports and traits to relationships
                out_res_opt = ResolveOptions(self)
                await self._index_if_needed(out_res_opt, True)

                for ent_dec in self.entities:
                    ent_path = await self._get_entity_path_from_declaration(
                        ent_dec, self)
                    curr_entity = await self.ctx.corpus.fetch_object_async(
                        ent_path)  # type: Optional[CdmEntityDefinition]

                    if not isinstance(curr_entity, CdmEntityDefinition):
                        logger.error(
                            self.ctx, self._TAG, self.
                            populate_manifest_relationships_async.__name__,
                            self.at_corpus_path, CdmLogCode.ERR_INVALID_CAST,
                            ent_path, 'CdmEntityDefinition')
                        continue
                    if curr_entity is None:
                        continue

                    # handle the outgoing relationships
                    outgoing_rels = self.ctx.corpus.fetch_outgoing_relationships(
                        curr_entity)  # List[CdmE2ERelationship]
                    if outgoing_rels:
                        for rel in outgoing_rels:
                            cache_key = rel.create_cache_key()
                            if cache_key not in rel_cache and self._is_rel_allowed(
                                    rel, option):
                                await self._add_elevated_traits_and_relationships(
                                    rel)
                                rel_cache.add(cache_key)

                    incoming_rels = self.ctx.corpus.fetch_incoming_relationships(
                        curr_entity)  # type: List[CdmE2ERelationship]

                    if incoming_rels:
                        for in_rel in incoming_rels:
                            # get entity object for current toEntity
                            current_in_base = await self.ctx.corpus.fetch_object_async(
                                in_rel.to_entity,
                                self)  # type: Optional[CdmEntityDefinition]

                            if not isinstance(current_in_base,
                                              CdmEntityDefinition):
                                logger.error(
                                    self.ctx, self._TAG,
                                    self.calculate_entity_graph_async.__name__,
                                    self.at_corpus_path,
                                    CdmLogCode.ERR_INVALID_CAST,
                                    in_rel.to_entity, 'CdmEntityDefinition')
                                continue
                            if not current_in_base:
                                continue

                            # create graph of inheritance for to current_in_base
                            # graph represented by an array where entity at i extends entity at i+1
                            to_inheritance_graph = [
                            ]  # type: List[CdmEntityDefinition]
                            while current_in_base:
                                res_opt = ResolveOptions(
                                    wrt_doc=current_in_base.in_document)
                                current_in_base = current_in_base.extends_entity.fetch_object_definition(
                                    res_opt
                                ) if current_in_base.extends_entity else None
                                if current_in_base:
                                    to_inheritance_graph.append(
                                        current_in_base)

                            # add current incoming relationship
                            cache_key = in_rel.create_cache_key()
                            if cache_key not in rel_cache and self._is_rel_allowed(
                                    in_rel, option):
                                await self._add_elevated_traits_and_relationships(
                                    in_rel)
                                rel_cache.add(cache_key)

                            # if A points at B, A's base classes must point at B as well
                            for base_entity in to_inheritance_graph:
                                incoming_rels_for_base = self.ctx.corpus.fetch_incoming_relationships(
                                    base_entity
                                )  # type: List[CdmE2ERelationship]

                                if incoming_rels_for_base:
                                    for in_rel_base in incoming_rels_for_base:
                                        new_rel = self.ctx.corpus.make_object(
                                            CdmObjectType.E2E_RELATIONSHIP_DEF,
                                            '')
                                        new_rel.from_entity = in_rel_base.from_entity
                                        new_rel.from_entity_attribute = in_rel_base.from_entity_attribute
                                        new_rel.to_entity = in_rel.to_entity
                                        new_rel.to_entity_attribute = in_rel.to_entity_attribute

                                        base_rel_cache_key = new_rel.create_cache_key(
                                        )
                                        if base_rel_cache_key not in rel_cache and self._is_rel_allowed(
                                                new_rel, option):
                                            await self._add_elevated_traits_and_relationships(
                                                new_rel)
                                            rel_cache.add(base_rel_cache_key)

                # Calls RefreshAsync on this manifest to resolve purpose traits in relationships
                # after adding all imports and traits by calling `AddElevatedTraitsAndRelationships`
                await self.refresh_async(out_res_opt)

            if self.sub_manifests:
                for sub_manifest_def in self.sub_manifests:
                    corpus_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                        sub_manifest_def.definition, self)
                    sub_manifest = await self.ctx.corpus.fetch_object_async(
                        corpus_path)  # type: Optional[CdmManifestDefinition]
                    if not isinstance(sub_manifest, CdmManifestDefinition):
                        logger.error(
                            self.ctx, self._TAG, self.
                            populate_manifest_relationships_async.__name__,
                            self.at_corpus_path, CdmLogCode.ERR_INVALID_CAST,
                            corpus_path, 'CdmManifestDefinition')
                        continue
                    await sub_manifest.populate_manifest_relationships_async(
                        option)