def _construct_resolved_attributes(
        self, res_opt: 'ResolveOptions', under: Optional['CdmAttributeContext']
    ) -> 'ResolvedAttributeSetBuilder':
        from cdm.resolvedmodel import ResolvedAttributeSetBuilder
        from cdm.utilities import AttributeContextParameters

        rasb = ResolvedAttributeSetBuilder()

        if under:
            acp_att_grp = AttributeContextParameters(
                under=under,
                type=CdmAttributeContextType.ATTRIBUTE_GROUP,
                name=self.get_name(),
                regarding=self,
                include_traits=False)
            under = rasb.ras.create_attribute_context(res_opt, acp_att_grp)

        if self.members:
            for att in self.members:
                acp_att = None
                if under:
                    acp_att = AttributeContextParameters(
                        under=under,
                        type=CdmAttributeContextType.ATTRIBUTE_DEFINITION,
                        name=att.fetch_object_definition_name(),
                        regarding=att,
                        include_traits=False)
                rasb.merge_attributes(
                    att._fetch_resolved_attributes(res_opt, acp_att))
        rasb.ras.attribute_context = under

        # things that need to go away
        rasb.remove_requested_atts()

        return rasb
Exemple #2
0
    def _construct_resolved_attributes(
        self, res_opt: 'ResolveOptions', under: Optional['CdmAttributeContext']
    ) -> 'ResolvedAttributeSetBuilder':
        # find and cache the complete set of attributes
        from cdm.resolvedmodel import ResolvedAttributeSetBuilder
        from cdm.utilities import AttributeContextParameters
        from cdm.objectmodel import CdmEntityDefinition

        rasb = ResolvedAttributeSetBuilder()
        rasb._resolved_attribute_set.attribute_context = under
        definition = self.fetch_object_definition(res_opt)
        if definition:
            acp_ref = None
            if under:
                # ask for a 'pass through' context, that is, no new context at this level
                acp_ref = AttributeContextParameters(
                    under=under, type=CdmAttributeContextType.PASS_THROUGH)
            res_atts = definition._fetch_resolved_attributes(res_opt, acp_ref)
            if res_atts and res_atts._set:
                # res_atts = res_atts.copy()  should not need this copy now that we copy from the cache. lets try!
                rasb.merge_attributes(res_atts)
                rasb.remove_requested_atts()
        else:
            logger.warning(
                self.ctx, self._TAG,
                CdmObjectReference._construct_resolved_traits.__name__,
                self.at_corpus_path, CdmLogCode.WARN_RESOLVE_OBJECT_FAILED,
                self.fetch_object_definition_name())

        return rasb
Exemple #3
0
    def _construct_resolved_attributes(
        self, res_opt: 'ResolveOptions', under: Optional['CdmAttributeContext']
    ) -> 'ResolvedAttributeSetBuilder':
        # find and cache the complete set of attributes
        from cdm.resolvedmodel import ResolvedAttributeSetBuilder
        from cdm.utilities import AttributeContextParameters

        rasb = ResolvedAttributeSetBuilder()
        rasb.ras.attribute_context = under
        definition = self.fetch_object_definition(res_opt)
        if definition:
            acp_ref = None
            if under:
                # ask for a 'pass through' context, that is, no new context at this level
                acp_ref = AttributeContextParameters(
                    under=under, type=CdmAttributeContextType.PASS_THROUGH)
            res_atts = definition._fetch_resolved_attributes(res_opt, acp_ref)
            if res_atts and res_atts._set:
                res_atts = res_atts.copy()
                rasb.merge_attributes(res_atts)
                rasb.remove_requested_atts()
        else:
            def_name = self.fetch_object_definition_name()
            logger.warning(
                self._TAG, self.ctx,
                'unable to resolve an object from the reference \'{}\''.format(
                    def_name))

        return rasb
    def _construct_resolved_attributes(
        self, res_opt: 'ResolveOptions', under: Optional['CdmAttributeContext']
    ) -> 'ResolvedAttributeSetBuilder':
        from cdm.resolvedmodel import ResolvedAttributeSetBuilder
        from cdm.utilities import AttributeContextParameters

        rasb = ResolvedAttributeSetBuilder()
        rasb._resolved_attribute_set.attribute_context = under
        acp_ent = None
        if under:
            acp_ent = AttributeContextParameters(
                under=under,
                type=CdmAttributeContextType.ENTITY,
                name=self.entity_shape.fetch_object_definition_name(),
                regarding=self.entity_shape,
                include_traits=True)

        if self.entity_shape:
            rasb.merge_attributes(
                self.entity_shape._fetch_resolved_attributes(res_opt, acp_ent))

        # things that need to go away
        rasb.remove_requested_atts()

        return rasb
    def _append_projection_attribute_state(self, proj_ctx: 'ProjectionContext', proj_output_set: 'ProjectionAttributeStateSet', \
                                           attr_ctx: 'CdmAttributeContext') -> 'ProjectionAttributeStateSet':
        # Create a new attribute context for the operation
        attr_ctx_op_add_attr_group_param = AttributeContextParameters()
        attr_ctx_op_add_attr_group_param._under = attr_ctx
        attr_ctx_op_add_attr_group_param._type = CdmAttributeContextType.OPERATION_ADD_ATTRIBUTE_GROUP
        attr_ctx_op_add_attr_group_param._name = 'operation/index{}/{}'.format(
            self._index, self.get_name())
        attr_ctx_op_add_attr_group = CdmAttributeContext._create_child_under(
            proj_ctx._projection_directive._res_opt,
            attr_ctx_op_add_attr_group_param)

        # Create a new attribute context for the attribute group we will create
        attr_ctx_attr_group_param = AttributeContextParameters()
        attr_ctx_attr_group_param._under = attr_ctx_op_add_attr_group
        attr_ctx_attr_group_param._type = CdmAttributeContextType.ATTRIBUTE_DEFINITION
        attr_ctx_attr_group_param._name = self.attribute_group_name
        attr_ctx_attr_group = CdmAttributeContext._create_child_under(
            proj_ctx._projection_directive._res_opt, attr_ctx_attr_group_param)

        # Create a new resolve attribute set builder that will be used to combine all the attributes into one set
        rasb = ResolvedAttributeSetBuilder()

        # Iterate through all the projection attribute states generated from the source's resolved attributes
        # Each projection attribute state contains a resolved attribute that it is corresponding to
        for current_PAS in proj_ctx._current_attribute_state_set._states:
            # Create an attribute set build that owns one resolved attribute
            attribute_rasb = ResolvedAttributeSetBuilder()
            attribute_rasb.own_one(current_PAS._current_resolved_attribute)

            # Merge the attribute set containing one attribute with the one holding all the attributes
            rasb.merge_attributes(attribute_rasb.ras)

            # Add each attribute's attribute context to the resolved attribute set attribute context
            attr_ctx_attr_group.contents.append(
                current_PAS._current_resolved_attribute.att_ctx)

        # Create a new resolved attribute that will hold the attribute set containing all the attributes
        res_attr_new = ResolvedAttribute(
            proj_ctx._projection_directive._res_opt, rasb.ras,
            self.attribute_group_name, attr_ctx_attr_group)

        # Create a new projection attribute state pointing to the resolved attribute set that represents the attribute group
        new_PAS = ProjectionAttributeState(self.ctx)
        new_PAS._current_resolved_attribute = res_attr_new
        proj_output_set._add(new_PAS)

        return proj_output_set
Exemple #6
0
    def _construct_resolved_attributes(
        self, res_opt: 'ResolveOptions', under: Optional['CdmAttributeContext']
    ) -> 'ResolvedAttributeSetBuilder':
        from cdm.resolvedmodel import ResolvedAttributeSetBuilder
        from cdm.utilities import AttributeContextParameters

        rasb = ResolvedAttributeSetBuilder()
        all_under = under  # type: CdmAttributeContext

        if under:
            acp_att_grp = AttributeContextParameters(
                under=under,
                type=CdmAttributeContextType.ATTRIBUTE_GROUP,
                name=self.get_name(),
                regarding=self,
                include_traits=False)
            under = rasb._resolved_attribute_set.create_attribute_context(
                res_opt, acp_att_grp)

        if self.members:
            for att in self.members:
                acp_att = None
                if under:
                    acp_att = AttributeContextParameters(
                        under=under,
                        type=CdmAttributeContextType.ATTRIBUTE_DEFINITION,
                        name=att.fetch_object_definition_name(),
                        regarding=att,
                        include_traits=False)
                ras_from_att = att._fetch_resolved_attributes(
                    res_opt, acp_att)  # type: ResolvedAttributeSet
                # before we just merge, need to handle the case of 'attribute restatement' AKA an entity with an attribute having the same name as an attribute
                # from a base entity. thing might come out with different names, if they do, then any attributes owned by a similar named attribute before
                # that didn't just pop out of that same named attribute now need to go away.
                # mark any attributes formerly from this named attribute that don't show again as orphans
                rasb._resolved_attribute_set.mark_orphans_for_removal(
                    cast(CdmAttributeItem, att).fetch_object_definition_name,
                    ras_from_att)
                rasb.merge_attributes(ras_from_att)

        rasb._resolved_attribute_set.attribute_context = all_under  # context must be the one expected from the caller's pov.

        # things that need to go away
        rasb.remove_requested_atts()

        return rasb
    def _construct_resolved_attributes(
        self,
        res_opt: 'ResolveOptions',
        under: Optional['CdmAttributeContext'] = None
    ) -> 'ResolvedAttributeSetBuilder':
        from cdm.resolvedmodel import AttributeResolutionContext, ResolvedAttribute, ResolvedAttributeSetBuilder, ResolvedTrait
        from cdm.utilities import AttributeContextParameters, AttributeResolutionDirectiveSet

        from .cdm_attribute_resolution_guidance_def import CdmAttributeResolutionGuidanceDefinition
        from .cdm_object import CdmObject

        rasb = ResolvedAttributeSetBuilder()
        ctx_ent = self.entity
        under_att = under
        acp_ent = None
        if under_att:
            # make a context for this attribute that holds the attributes that come up from the entity
            acp_ent = AttributeContextParameters(
                under=under_att,
                type=CdmAttributeContextType.ENTITY,
                name=ctx_ent.fetch_object_definition_name(),
                regarding=ctx_ent,
                include_traits=True)

        rts_this_att = self._fetch_resolved_traits(res_opt)

        # this context object holds all of the info about what needs to happen to resolve these attributes.
        # make a copy and add defaults if missing
        res_guide_with_default = None
        if self.resolution_guidance is not None:
            res_guide_with_default = self.resolution_guidance.copy(res_opt)
        else:
            res_guide_with_default = CdmAttributeResolutionGuidanceDefinition(
                self.ctx)

        res_guide_with_default._update_attribute_defaults(self.name)

        arc = AttributeResolutionContext(res_opt, res_guide_with_default,
                                         rts_this_att)

        # complete cheating but is faster.
        # this purpose will remove all of the attributes that get collected here, so dumb and slow to go get them
        rel_info = self._get_relationship_info(arc.res_opt, arc)

        if rel_info.is_by_ref:
            # make the entity context that a real recursion would have give us
            if under:
                under = rasb.ras.create_attribute_context(res_opt, acp_ent)

            # if selecting from one of many attributes, then make a context for each one
            if under and rel_info.selects_one:
                # the right way to do this is to get a resolved entity from the embedded entity and then
                # look through the attribute context hierarchy for non-nested entityReferenceAsAttribute nodes
                # that seems like a disaster waiting to happen given endless looping, etc.
                # for now, just insist that only the top level entity attributes declared in the ref entity will work
                ent_pick_from = self.entity.fetch_object_definition(res_opt)
                atts_pick = ent_pick_from.attributes
                if ent_pick_from and atts_pick:
                    for attribute in atts_pick:
                        if attribute.object_type == CdmObjectType.ENTITY_ATTRIBUTE_DEF:
                            # a table within a table. as expected with a selects_one attribute
                            # since this is by ref, we won't get the atts from the table, but we do need the traits that hold the key
                            # these are the same contexts that would get created if we recursed
                            # first this attribute
                            acp_ent_att = AttributeContextParameters(
                                under=under,
                                type=CdmAttributeContextType.
                                ATTRIBUTE_DEFINITION,
                                name=attribute.fetch_object_definition_name(),
                                regarding=attribute,
                                include_traits=True)
                            pick_under = rasb.ras.create_attribute_context(
                                res_opt, acp_ent_att)
                            # and the entity under that attribute
                            pick_ent = attribute.entity
                            acp_ent_att_ent = AttributeContextParameters(
                                under=pick_under,
                                type=CdmAttributeContextType.ENTITY,
                                name=pick_ent.fetch_object_definition_name(),
                                regarding=pick_ent,
                                include_traits=True)
                            rasb.ras.create_attribute_context(
                                res_opt, acp_ent_att_ent)

            # if we got here because of the max depth, need to impose the directives to make the trait work as expected
            if rel_info.max_depth_exceeded:
                if not arc.res_opt.directives:
                    arc.res_opt.directives = AttributeResolutionDirectiveSet()
                arc.res_opt.directives.add('referenceOnly')
        else:
            res_link = CdmObject._copy_resolve_options(res_opt)
            res_link.symbol_ref_set = res_opt.symbol_ref_set
            res_link._relationship_depth = rel_info.next_depth
            rasb.merge_attributes(
                self.entity._fetch_resolved_attributes(res_link, acp_ent))

        # from the traits of purpose and applied here, see if new attributes get generated
        rasb.ras.attribute_context = under_att
        rasb.apply_traits(arc)
        rasb.generate_applier_attributes(
            arc, True)  # True = apply the prepared traits to new atts
        # this may have added symbols to the dependencies, so merge them
        res_opt.symbol_ref_set.merge(arc.res_opt.symbol_ref_set)

        # use the traits for linked entity identifiers to record the actual foreign key links
        if rasb.ras and rasb.ras.set and rel_info.is_by_ref:
            for att in rasb.ras.set:
                reqd_trait = att.resolved_traits.find(
                    res_opt, 'is.linkedEntity.identifier')
                if not reqd_trait:
                    continue

                if not reqd_trait.parameter_values:
                    logger.warning(
                        self._TAG, self.ctx,
                        'is.linkedEntity.identifier does not support arguments'
                    )
                    continue

                ent_references = []
                att_references = []

                def add_entity_reference(entity_ref: 'CdmEntityReference',
                                         namespace: str):
                    ent_def = entity_ref.fetch_object_definition(res_opt)
                    required_trait = entity_ref._fetch_resolved_traits(
                        res_opt).find(res_opt, 'is.identifiedBy')
                    if required_trait and ent_def:
                        att_ref = required_trait.parameter_values.fetch_parameter_value(
                            'attribute').value
                        att_name = att_ref.named_reference.split('/')[-1]
                        # path should be absolute and without a namespace
                        relative_ent_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                            ent_def.at_corpus_path, ent_def.in_document)
                        if relative_ent_path.startswith(namespace + ':'):
                            relative_ent_path = relative_ent_path[len(namespace
                                                                      ) + 1:]
                        ent_references.append(relative_ent_path)
                        att_references.append(att_name)

                if rel_info.selects_one:
                    ent_pick_from = self.entity.fetch_object_definition(
                        res_opt)
                    atts_pick = ent_pick_from.attributes if ent_pick_from else None
                    if atts_pick:
                        for attribute in atts_pick:
                            if attribute.object_type == CdmObjectType.ENTITY_ATTRIBUTE_DEF:
                                add_entity_reference(
                                    attribute.entity,
                                    self.in_document.namespace)
                else:
                    add_entity_reference(self.entity,
                                         self.in_document.namespace)

                c_ent = self.ctx.corpus.make_object(
                    CdmObjectType.CONSTANT_ENTITY_DEF)
                c_ent.entity_shape = self.ctx.corpus.make_ref(
                    CdmObjectType.ENTITY_REF, 'entityGroupSet', True)
                c_ent.constant_values = [[
                    entity_ref, att_references[idx]
                ] for idx, entity_ref in enumerate(ent_references)]
                param = self.ctx.corpus.make_ref(CdmObjectType.ENTITY_REF,
                                                 c_ent, False)
                reqd_trait.parameter_values.update_parameter_value(
                    res_opt, 'entityReferences', param)

        # a 'structured' directive wants to keep all entity attributes together in a group
        if arc and arc.res_opt.directives and arc.res_opt.directives.has(
                'structured'):
            ra_sub = ResolvedAttribute(rts_this_att.res_opt, rasb.ras,
                                       self.name, rasb.ras.attribute_context)
            if rel_info.is_array:
                # put a resolved trait on this att group, yuck,
                #  hope I never need to do this again and then need to make a function for this
                tr = self.ctx.corpus.make_object(CdmObjectType.TRAIT_REF,
                                                 'is.linkedEntity.array', True)
                t = tr.fetch_object_definition(res_opt)
                rt = ResolvedTrait(t, None, [], [])
                ra_sub.resolved_traits = ra_sub.resolved_traits.merge(rt, True)
            rasb = ResolvedAttributeSetBuilder()
            rasb.own_one(ra_sub)

        return rasb
Exemple #8
0
class CdmEntityDefinition(CdmObjectDefinition, CdmReferencesEntities):
    def __init__(self, ctx: 'CdmCorpusContext', name: str, extends_entity: Optional['CdmEntityReference']) -> None:
        super().__init__(ctx)

        # the entity attribute Context.
        self.attribute_context = None  # type: Optional[CdmAttributeContext]

        # the entity entity name.
        self.entity_name = name  # type: str

        # the entity extended by this entity.
        self.extends_entity = extends_entity  # type: Optional[CdmEntityReference]

        # the resolution guidance for attributes taken from the entity extended by this entity
        self.extends_entity_resolution_guidance = None  # type: Optional[CdmAttributeResolutionGuidanceDefinition]

        # --- internal ---
        self._rasb = None  # type: Optional[ResolvedAttributeSetBuilder]
        self._resolving_entity_references = False  # type: bool
        self._attributes = CdmCollection(self.ctx, self, CdmObjectType.TYPE_ATTRIBUTE_DEF)  # type: CdmCollection
        self._ttpm = None  # type: Optional[TraitToPropertyMap]

        self._TAG = CdmEntityDefinition.__name__

    @property
    def attributes(self) -> 'CdmCollection[CdmAttributeItem]':
        """the entity attributes."""
        return self._attributes

    @property
    def object_type(self) -> 'CdmObjectType':
        return CdmObjectType.ENTITY_DEF

    @property
    def source_name(self) -> str:
        return cast(str, self._trait_to_property_map._fetch_property_value('sourceName'))

    @source_name.setter
    def source_name(self, val: str) -> None:
        self._trait_to_property_map._update_property_value('sourceName', val)

    @property
    def description(self) -> str:
        return cast(str, self._trait_to_property_map._fetch_property_value('description'))

    @description.setter
    def description(self, val: str) -> None:
        self._trait_to_property_map._update_property_value('description', val)

    @property
    def display_name(self) -> str:
        return cast(str, self._trait_to_property_map._fetch_property_value('displayName'))

    @display_name.setter
    def display_name(self, val: str) -> None:
        self._trait_to_property_map._update_property_value('displayName', val)

    @property
    def version(self) -> str:
        return cast(str, self._trait_to_property_map._fetch_property_value('version'))

    @version.setter
    def version(self, val: str) -> None:
        self._trait_to_property_map._update_property_value('version', val)

    @property
    def cdm_schemas(self) -> List[str]:
        return cast(List[str], self._trait_to_property_map._fetch_property_value('cdmSchemas'))

    @cdm_schemas.setter
    def cdm_schemas(self, val: List[str]) -> None:
        self._trait_to_property_map._update_property_value('cdmSchemas', val)

    @property
    def primary_key(self) -> str:
        return cast(str, self._trait_to_property_map._fetch_property_value('primaryKey'))

    @property
    def _trait_to_property_map(self) -> 'TraitToPropertyMap':
        from cdm.utilities import TraitToPropertyMap

        if not self._ttpm:
            self._ttpm = TraitToPropertyMap(self)
        return self._ttpm

    def _add_attribute_def(self, attribute_def: 'CdmAttributeItem') -> 'CdmAttributeItem':
        self.attributes.append(attribute_def)
        return attribute_def

    def _construct_resolved_attributes(self, res_opt: 'ResolveOptions', under: Optional['CdmAttributeContext'] = None) -> 'ResolvedAttributeSetBuilder':
        # find and cache the complete set of attributes
        # attributes definitions originate from and then get modified by subsequent re-defintions from (in this order):
        # an extended entity, traits applied to extended entity, exhibited traits of main entity,
        # the (datatype or entity) used as an attribute, traits applied to that datatype or entity,
        # the relationsip of the attribute, the attribute definition itself and included attribute groups,
        #  any traits applied to the attribute.
        from cdm.resolvedmodel import ResolvedAttributeSetBuilder
        from cdm.utilities import AttributeContextParameters

        self._rasb = ResolvedAttributeSetBuilder()
        self._rasb.ras.attribute_context = under

        if self.extends_entity:
            ext_ref = self.extends_entity
            extends_ref_under = None
            acp_ext_ent = None

            if under:
                acp_ext = AttributeContextParameters(
                    under=under,
                    type=CdmAttributeContextType.ENTITY_REFERENCE_EXTENDS,
                    name='extends',
                    regarding=None,
                    include_traits=False)
                extends_ref_under = self._rasb.ras.create_attribute_context(res_opt, acp_ext)

            if ext_ref.explicit_reference and ext_ref.fetch_object_definition(res_opt).object_type == CdmObjectType.PROJECTION_DEF:
                # A Projection

                ext_ref_obj_def = ext_ref.fetch_object_definition(res_opt)
                if extends_ref_under:
                    acp_ext_ent = AttributeContextParameters(
                        under=extends_ref_under,
                        type=CdmAttributeContextType.PROJECTION,
                        name=ext_ref_obj_def.get_name(),
                        regarding=ext_ref,
                        include_traits=False
                    )

                proj_directive = ProjectionDirective(res_opt, self, ext_ref)
                proj_def = ext_ref_obj_def
                proj_ctx = proj_def._construct_projection_context(proj_directive, extends_ref_under)

                self._rasb.ras = proj_def._extract_resolved_attributes(proj_ctx)
            else:
                # An Entity Reference

                if extends_ref_under:
                    acp_ext_ent = AttributeContextParameters(
                        under=extends_ref_under,
                        type=CdmAttributeContextType.ENTITY,
                        name=ext_ref.named_reference if ext_ref.named_reference else ext_ref.explicit_reference.get_name(),
                        regarding=ext_ref,
                        include_traits=False
                    )

                # save moniker, extended entity may attach a different moniker that we do not
                # want to pass along to getting this entities attributes
                old_moniker = res_opt._from_moniker

                self._rasb.merge_attributes(self.extends_entity._fetch_resolved_attributes(res_opt, acp_ext_ent))

                if not res_opt._check_attribute_count(self._rasb.ras._resolved_attribute_count):
                    logger.error(self._TAG, self.ctx, 'Maximum number of resolved attributes reached for the entity: {}.'.format(self.entity_name))
                    return None

                if self.extends_entity_resolution_guidance:
                    # some guidance was given on how to integrate the base attributes into the set. apply that guidance
                    rts_base = self._fetch_resolved_traits(res_opt)  # type: ResolvedTraitSet

                    # this context object holds all of the info about what needs to happen to resolve these attributes.
                    # make a copy and set defaults if needed
                    res_guide = self.extends_entity_resolution_guidance.copy(res_opt)  # CdmAttributeResolutionGuidanceDefinition
                    res_guide._update_attribute_defaults(res_guide.fetch_object_definition_name())
                    # holds all the info needed by the resolver code
                    arc = AttributeResolutionContext(res_opt, res_guide, rts_base)  # type: AttributeResolutionContext

                    self._rasb.generate_applier_attributes(arc, False)  # true = apply the prepared traits to new atts

                # reset to the old moniker
                res_opt._from_moniker = old_moniker

        self._rasb.mark_inherited()
        self._rasb.ras.attribute_context = under

        if self.attributes:
            for att in self.attributes:
                acp_att = None
                if under:
                    acp_att = AttributeContextParameters(
                        under=under,
                        type=CdmAttributeContextType.ATTRIBUTE_DEFINITION,
                        name=att.fetch_object_definition_name(),
                        regarding=att,
                        include_traits=False)
                self._rasb.merge_attributes(att._fetch_resolved_attributes(res_opt, acp_att))

                if not res_opt._check_attribute_count(self._rasb.ras._resolved_attribute_count):
                    logger.error(self._TAG, self.ctx, 'Maximum number of resolved attributes reached for the entity: {}.'.format(self.entity_name))
                    return None

        self._rasb.mark_order()
        self._rasb.ras.attribute_context = under

        # things that need to go away
        self._rasb.remove_requested_atts()

        return self._rasb

    def _construct_resolved_traits(self, rtsb: 'ResolvedTraitSetBuilder', res_opt: 'ResolveOptions') -> None:
        from cdm.resolvedmodel import ResolvedTraitSet

        base = self.extends_entity
        if base:
            # merge in all from base class
            rtsb.merge_traits(base._fetch_resolved_traits(res_opt))

        if self.attributes:
            rts_elevated = ResolvedTraitSet(res_opt)
            for att in self.attributes:
                rts_att = att._fetch_resolved_traits(res_opt)
                if rts_att and rts_att.has_elevated:
                    rts_elevated = rts_elevated.merge_set(rts_att, True)
            rtsb.merge_traits(rts_elevated)

        self._construct_resolved_traits_def(None, rtsb, res_opt)

    def copy(self, res_opt: Optional['ResolveOptions'] = None, host: Optional['CdmEntityDefinition'] = None) -> 'CdmEntityDefinition':
        if not res_opt:
            res_opt = ResolveOptions(wrt_doc=self, directives=self.ctx.corpus.default_resolution_directives)

        if not host:
            copy = CdmEntityDefinition(self.ctx, self.entity_name, None)
        else:
            copy = host
            copy.ctx = self.ctx
            copy.entity_name = self.entity_name
            copy.attributes.clear()

        copy.extends_entity = cast('CdmEntityReference', self.extends_entity.copy(res_opt)) if self.extends_entity else None
        copy.extends_entity_resolution_guidance = self.extends_entity_resolution_guidance.copy(res_opt) if self.extends_entity_resolution_guidance else None
        copy.attribute_context = cast('CdmAttributeContext', self.attribute_context.copy(res_opt)) if self.attribute_context else None

        for att in self.attributes:
            copy.attributes.append(att)

        self._copy_def(res_opt, copy)

        return copy

    # TODO: Refactor and split this function to be more structured.
    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."""

        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 = CdmObject._copy_resolve_options(res_opt)
        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:
                    refs = ra_ctx.contents

                    #  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
                            refs.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 = CdmObject._copy_resolve_options(res_opt)
        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

    def _count_inherited_attributes(self, res_opt: Optional['ResolveOptions'] = None) -> int:
        """Return the count of attibutes inherited by this entity."""
        res_opt = res_opt if res_opt is not None else ResolveOptions(wrt_doc=self)

        # Ensures that cache exits.
        self._fetch_resolved_attributes(res_opt)
        return self._rasb.inherited_mark

    def _fetch_attributes_with_traits(self, res_opt: 'ResolveOptions', query_for: Union['TraitSpec', Iterable['TraitSpec']]) -> 'ResolvedAttributeSet':
        """Return a set of the entity attributes with traits."""
        ras = self._fetch_resolved_attributes(res_opt)
        return ras.fetch_attributes_with_traits(res_opt, query_for) if ras is not None else None

    def get_name(self) -> str:
        return self.entity_name

    def _fetch_property(self, property_name: str) -> Any:
        """returns the value direclty assigned to a property (ignore value from traits)."""
        return self._trait_to_property_map._fetch_property_value(property_name, True)

    def fetch_resolved_entity_references(self, res_opt: 'ResolveOptions') -> 'ResolvedEntityReferenceSet':
        # this whole resolved entity ref goo will go away when resolved documents are done.
        # for now, it breaks if structured att sets get made.
        from cdm.resolvedmodel import ResolvedEntityReferenceSet
        from cdm.utilities import AttributeResolutionDirectiveSet

        from .cdm_object import CdmObject

        was_previously_resolving = self.ctx.corpus._is_currently_resolving
        self.ctx.corpus._is_currently_resolving = True

        if not res_opt:
            res_opt = ResolveOptions(self, self.ctx.corpus.default_resolution_directives)

        res_opt = CdmObject._copy_resolve_options(res_opt)
        res_opt.directives = AttributeResolutionDirectiveSet(set(['normalized', 'referenceOnly']))

        ctx = self.ctx
        ent_ref_set_cache = ctx.fetch_cache(self, res_opt, 'entRefSet')
        if not ent_ref_set_cache:
            ent_ref_set_cache = ResolvedEntityReferenceSet(res_opt)

            if not self._resolving_entity_references:
                self._resolving_entity_references = True
                # get from any base class and then 'fix' those to point here instead.
                ext_ref = self.extends_entity
                if ext_ref:
                    ext_def = cast('CdmEntityDefinition', ext_ref.fetch_object_definition(res_opt))
                    if ext_def:
                        inherited = ext_def.fetch_resolved_entity_references(res_opt)
                        if inherited:
                            for res in inherited.rer_set:
                                res = res.copy()
                                res.referencing.entity = self
                                ent_ref_set_cache.rer_set.append(res)
                if self.attributes:
                    for attribute in self.attributes:
                        # if any refs come back from attributes, they don't know who we are, so they don't set the entity
                        sub = attribute.fetch_resolved_entity_references(res_opt)
                        if sub:
                            for res in sub.rer_set:
                                res.referencing.entity = self

                            ent_ref_set_cache.add(sub)
                ctx.update_cache(self, res_opt, 'entRefSet', ent_ref_set_cache)
                self._resolving_entity_references = False

        self.ctx.corpus._is_currently_resolving = was_previously_resolving
        return ent_ref_set_cache

    def is_derived_from(self, base: str, res_opt: Optional['ResolveOptions'] = None) -> bool:
        res_opt = res_opt if res_opt is not None else ResolveOptions(wrt_doc=self, directives=self.ctx.corpus.default_resolution_directives)
        return self._is_derived_from_def(res_opt, self.extends_entity, self.get_name(), base)

    async def _query_on_traits_async(self, query_spec: Union[str, object]) -> Iterable[object]:
        """Query the manifest for a set of entities that match an input query
        query_spec = a JSON object (or a string that can be parsed into one) of the form
        {"entityName":"", "attributes":[{see QueryOnTraits for CdmEntityDefinition for details}]}
        returns null for 0 results or an array of json objects, each matching the shape of
        the input query, with entity and attribute names filled in"""
        return None

    def validate(self) -> bool:
        if not bool(self.entity_name):
            logger.error(self._TAG, self.ctx, Errors.validate_error_string(self.at_corpus_path, ['entity_name']))
            return False
        return True

    def visit(self, path_from: str, pre_children: 'VisitCallback', post_children: 'VisitCallback') -> bool:
        path = ''
        if self.ctx.corpus._block_declared_path_changes is False:
            path = self._declared_path
            if not path:
                path = path_from + self.entity_name
                self._declared_path = path

        if pre_children and pre_children(self, path):
            return False

        if self.extends_entity and self.extends_entity.visit(path + '/extendsEntity/', pre_children, post_children):
            return True

        if self._visit_def(path, pre_children, post_children):
            return True

        if self.attribute_context and self.attribute_context.visit(path + '/attributeContext/', pre_children, post_children):
            return True

        if self.attributes and self.attributes._visit_array(path + '/hasAttributes/', pre_children, post_children):
            return True

        if post_children and post_children(self, path):
            return True

        return False
    def _construct_resolved_attributes(
        self,
        res_opt: 'ResolveOptions',
        under: Optional['CdmAttributeContext'] = None
    ) -> 'ResolvedAttributeSetBuilder':
        from cdm.resolvedmodel import AttributeResolutionContext, ResolvedAttribute, ResolvedAttributeSetBuilder, ResolvedTrait
        from cdm.utilities import AttributeContextParameters, AttributeResolutionDirectiveSet

        from .cdm_object import CdmObject

        rasb = ResolvedAttributeSetBuilder()
        ctx_ent = self.entity
        under_att = under
        acp_ent = None

        if not res_opt._in_circular_reference:
            arc = self._fetch_att_res_context(res_opt)

            # complete cheating but is faster.
            # this purpose will remove all of the attributes that get collected here, so dumb and slow to go get them
            rel_info = self._get_relationship_info(arc.res_opt, arc)
            res_opt._depth_info.current_depth = rel_info.next_depth
            res_opt._depth_info.max_depth_exceeded = rel_info.max_depth_exceeded
            res_opt._depth_info.max_depth = rel_info.max_depth

            ctx_ent_obj_def = ctx_ent.fetch_object_definition(res_opt)

            if ctx_ent_obj_def and ctx_ent_obj_def.object_type == CdmObjectType.PROJECTION_DEF:
                # A Projection

                # if the max depth is exceeded it should not try to execute the projection
                if not res_opt._depth_info.max_depth_exceeded:
                    proj_directive = ProjectionDirective(
                        res_opt, self, ctx_ent)
                    proj_def = ctx_ent_obj_def
                    proj_ctx = proj_def._construct_projection_context(
                        proj_directive, under)
                    rasb._resolved_attribute_set = proj_def._extract_resolved_attributes(
                        proj_ctx, under_att)
            else:
                # An Entity Reference

                if under_att:
                    # make a context for this attribute that holds the attributes that come up from the entity
                    acp_ent = AttributeContextParameters(
                        under=under_att,
                        type=CdmAttributeContextType.ENTITY,
                        name=ctx_ent.fetch_object_definition_name(),
                        regarding=ctx_ent,
                        include_traits=True)

                if rel_info.is_by_ref:
                    # make the entity context that a real recursion would have give us
                    if under:
                        under = rasb._resolved_attribute_set.create_attribute_context(
                            res_opt, acp_ent)

                    # if selecting from one of many attributes, then make a context for each one
                    if under and rel_info.selects_one:
                        # the right way to do this is to get a resolved entity from the embedded entity and then
                        # look through the attribute context hierarchy for non-nested entityReferenceAsAttribute nodes
                        # that seems like a disaster waiting to happen given endless looping, etc.
                        # for now, just insist that only the top level entity attributes declared in the ref entity will work
                        ent_pick_from = self.entity.fetch_object_definition(
                            res_opt)
                        atts_pick = ent_pick_from.attributes
                        if ent_pick_from and atts_pick:
                            for attribute in atts_pick:
                                if attribute.object_type == CdmObjectType.ENTITY_ATTRIBUTE_DEF:
                                    # a table within a table. as expected with a selects_one attribute
                                    # since this is by ref, we won't get the atts from the table, but we do need the traits that hold the key
                                    # these are the same contexts that would get created if we recursed
                                    # first this attribute
                                    acp_ent_att = AttributeContextParameters(
                                        under=under,
                                        type=CdmAttributeContextType.
                                        ATTRIBUTE_DEFINITION,
                                        name=attribute.
                                        fetch_object_definition_name(),
                                        regarding=attribute,
                                        include_traits=True)
                                    pick_under = rasb._resolved_attribute_set.create_attribute_context(
                                        res_opt, acp_ent_att)
                                    # and the entity under that attribute
                                    pick_ent = attribute.entity
                                    pick_ent_type = CdmAttributeContextType.PROJECTION if pick_ent.fetch_object_definition(
                                        res_opt
                                    ).object_type == CdmObjectType.PROJECTION_DEF else CdmAttributeContextType.ENTITY
                                    acp_ent_att_ent = AttributeContextParameters(
                                        under=pick_under,
                                        type=pick_ent_type,
                                        name=pick_ent.
                                        fetch_object_definition_name(),
                                        regarding=pick_ent,
                                        include_traits=True)
                                    rasb._resolved_attribute_set.create_attribute_context(
                                        res_opt, acp_ent_att_ent)

                    # if we got here because of the max depth, need to impose the directives to make the trait work as expected
                    if rel_info.max_depth_exceeded:
                        if not arc.res_opt.directives:
                            arc.res_opt.directives = AttributeResolutionDirectiveSet(
                            )
                        arc.res_opt.directives.add('referenceOnly')
                else:
                    res_link = res_opt.copy()
                    res_link._symbol_ref_set = res_opt._symbol_ref_set
                    rasb.merge_attributes(
                        self.entity._fetch_resolved_attributes(
                            res_link, acp_ent))

                    # need to pass up max_depth_exceeded if it was hit
                    if res_link._depth_info.max_depth_exceeded:
                        res_opt._depth_info = res_link._depth_info._copy()

                # from the traits of purpose and applied here, see if new attributes get generated
                rasb._resolved_attribute_set.attribute_context = under_att
                rasb.apply_traits(arc)
                rasb.generate_applier_attributes(
                    arc, True)  # True = apply the prepared traits to new atts
                # this may have added symbols to the dependencies, so merge them
                res_opt._symbol_ref_set._merge(arc.res_opt._symbol_ref_set)

                # use the traits for linked entity identifiers to record the actual foreign key links
                if rasb._resolved_attribute_set and rasb._resolved_attribute_set._set and rel_info.is_by_ref:
                    for att in rasb._resolved_attribute_set._set:
                        reqd_trait = att.resolved_traits.find(
                            res_opt, 'is.linkedEntity.identifier')
                        if not reqd_trait:
                            continue

                        if not reqd_trait.parameter_values:
                            logger.warning(
                                self._ctx, self._TAG,
                                CdmEntityAttributeDefinition.
                                _construct_resolved_attributes.__name__,
                                self.at_corpus_path, CdmLogCode.
                                WARN_IDENTIFIER_ARGUMENTS_NOT_SUPPORTED)
                            continue

                        ent_references = []
                        att_references = []

                        def add_entity_reference(
                                entity_ref: 'CdmEntityReference',
                                namespace: str):
                            ent_def = entity_ref.fetch_object_definition(
                                res_opt)
                            required_trait = entity_ref._fetch_resolved_traits(
                                res_opt).find(res_opt, 'is.identifiedBy')
                            if required_trait and ent_def:
                                att_ref = required_trait.parameter_values.fetch_parameter_value(
                                    'attribute').value
                                att_name = att_ref.named_reference.split(
                                    '/')[-1]
                                absolute_ent_path = self.ctx.corpus.storage.create_absolute_corpus_path(
                                    ent_def.at_corpus_path,
                                    ent_def.in_document)
                                ent_references.append(absolute_ent_path)
                                att_references.append(att_name)

                        if rel_info.selects_one:
                            ent_pick_from = self.entity.fetch_object_definition(
                                res_opt)
                            atts_pick = ent_pick_from.attributes if ent_pick_from else None
                            if atts_pick:
                                for attribute in atts_pick:
                                    if attribute.object_type == CdmObjectType.ENTITY_ATTRIBUTE_DEF:
                                        add_entity_reference(
                                            attribute.entity,
                                            self.in_document.namespace)
                        else:
                            add_entity_reference(self.entity,
                                                 self.in_document.namespace)

                        c_ent = self.ctx.corpus.make_object(
                            CdmObjectType.CONSTANT_ENTITY_DEF)
                        c_ent.entity_shape = self.ctx.corpus.make_ref(
                            CdmObjectType.ENTITY_REF, 'entityGroupSet', True)
                        c_ent.constant_values = [[
                            entity_ref, att_references[idx]
                        ] for idx, entity_ref in enumerate(ent_references)]
                        param = self.ctx.corpus.make_ref(
                            CdmObjectType.ENTITY_REF, c_ent, False)
                        reqd_trait.parameter_values.update_parameter_value(
                            res_opt, 'entityReferences', param)

                # a 'structured' directive wants to keep all entity attributes together in a group
                if arc and arc.res_opt.directives and arc.res_opt.directives.has(
                        'structured'):
                    # make one resolved attribute with a name from this entityAttribute that contains the set
                    # of atts we just put together.
                    ra_sub = ResolvedAttribute(arc.traits_to_apply.res_opt,
                                               rasb._resolved_attribute_set,
                                               self.name, under_att)
                    if rel_info.is_array:
                        # put a resolved trait on this att group
                        # hope I never need to do this again and then need to make a function for this
                        tr = self.ctx.corpus.make_object(
                            CdmObjectType.TRAIT_REF, 'is.linkedEntity.array',
                            True)
                        t = tr.fetch_object_definition(res_opt)
                        rt = ResolvedTrait(t, None, [], [])
                        ra_sub.resolved_traits = ra_sub.resolved_traits.merge(
                            rt, True)
                    depth = rasb._resolved_attribute_set._depth_traveled
                    rasb = ResolvedAttributeSetBuilder()
                    rasb._resolved_attribute_set.attribute_context = ra_sub.att_ctx  # this got set to null with the new builder
                    rasb.own_one(ra_sub)
                    rasb._resolved_attribute_set._depth_traveled = depth

        # how ever they got here, mark every attribute from this entity attribute as now being 'owned' by this entityAtt
        rasb._resolved_attribute_set._set_attribute_ownership(self.name)
        rasb._resolved_attribute_set._depth_traveled += 1

        return rasb