def loader_options(self): """ Dictionary mapping each entity class to configure a loader for to a list of (possibly nested) entity attribute names. """ all_keys = set(self.__attr_map.keys()) # Go through the collected keys and through out all keys which are # subkeys of others to eliminate redundancy. for key in sorted(self.__attr_map): for idx in range(1, len(key)): sub_key = key[:-idx] if sub_key in all_keys: all_keys.remove(sub_key) if provides_member_resource(self._context): # If the context is a member, we need to configure the loaders # for the entity class belonging to each of its resource # attributes. Only nested keys collected from the representer # configuration need to be configured (and the corresponding # nested entity attribute needs to be shortened). loader_option_map = defaultdict(list) for key in all_keys: entity_attr_names = self.__attr_map[key] if len(entity_attr_names) > 1: ent_attr_name = entity_attr_names[0] nested_attr_name = '.'.join(entity_attr_names[1:]) opts = loader_option_map[ent_attr_name] opts.append(nested_attr_name) # Translate to entity classes as keys. This is tricky as the # keys in the loader option map can itself be nested attributes. for ent_attr_name, nested_attr_names in loader_option_map.items(): ent_attr_name_tokens = ent_attr_name.split('.') ent_cls = get_entity_class(self._context) ent_cls_attr = getattr(ent_cls, ent_attr_name_tokens[0]) ent_cls = ent_cls_attr.property.mapper.entity if len(ent_attr_name_tokens) > 1: prefix = '.'.join(ent_attr_name_tokens[1:]) loader_option_map[ent_cls] = \ ["%s.%s" % (prefix, token) for token in nested_attr_names] else: loader_option_map[ent_cls] = nested_attr_names del loader_option_map[ent_attr_name] else: # If the context is a collection, we need to configure the # loader for its entity class. loader_option_map = {get_entity_class(self._context) : ['.'.join(self.__attr_map[key]) for key in all_keys]} return loader_option_map
def visit_member(self, attribute_key, attribute, member_node, member_data, is_link_node, parent_data, index=None): if is_link_node: url = member_node.get_url() rc = url_to_resource(url) entity = rc.get_entity() else: entity_cls = get_entity_class(member_node.mapping.mapped_class) entity_data = {} nested_entity_data = {} for attr, value in member_data.iteritems(): if '.' in attr.entity_name: nested_entity_data[attr.entity_name] = value else: entity_data[attr.entity_name] = value entity = entity_cls.create_from_data(entity_data) # Set nested attribute values. # FIXME: lazy loading of nested attributes is not supported. for nested_attr, value in nested_entity_data.iteritems(): tokens = nested_attr.split('.') parent = reduce(getattr, tokens[:-1], entity) if not parent is None: setattr(parent, tokens[-1], value) if not index is None: # Collection member. Store in parent data with index as key. parent_data[index] = entity elif len(attribute_key) == 0: # Top level. Store root entity and create resource. mapped_cls = member_node.mapping.mapped_class self.__resource = mapped_cls.create_from_entity(entity) else: # Nested member. Store in parent data with attribute as key. parent_data[attribute] = entity
def inspect(entity_class, attribute_name): attr_tokens = attribute_name.split('.') infos = [] for idx, attr_token in enumerate(attr_tokens): do_append = True try: attr = get_domain_class_attributes(entity_class)[attr_token] except KeyError: if attr_token != 'slug': # If we encounter a non-resource attribute, we assume # an embedded document (which implies that the last # token was a terminal). infos[-1][-1] = \ '.'.join([infos[-1][-1]] + attr_tokens[idx:]) # Unfortunately, we can not infer the type of embedded # attributes. infos[-1][1] = None do_append = False else: # The 'slug' attribute is special as it is not a properly # declared resource attribute but needs to be queryable. attr_kind = RESOURCE_ATTRIBUTE_KINDS.TERMINAL attr_type = str else: attr_kind = attr.kind if attr_kind != RESOURCE_ATTRIBUTE_KINDS.TERMINAL: entity_class = get_entity_class(attr.attr_type) attr_type = entity_class else: attr_type = attr.attr_type if do_append: infos.append([attr_kind, attr_type, attr_token]) return infos
def __clone(self, entity, cache): clone = object.__new__(entity.__class__) # We add the clone with its ID set to the cache *before* we load it # so that circular references will work. clone.id = entity.id cache.add(clone) state = EntityState.get_state_data(entity) id_attr = None for attr, value in iteritems_(state): if attr.entity_attr == 'id': id_attr = attr continue attr_type = attr.attr_type if attr.kind != RESOURCE_ATTRIBUTE_KINDS.TERMINAL \ and not self.__repository.is_registered_resource(attr_type): # Prevent loading of entities from other repositories. # FIXME: Doing this here is inconsistent, since e.g. the RDB # session does not perform this kind of check. continue elif attr.kind == RESOURCE_ATTRIBUTE_KINDS.MEMBER \ and not value is None: ent_cls = get_entity_class(attr_type) new_value = self.load(ent_cls, value) state[attr] = new_value elif attr.kind == RESOURCE_ATTRIBUTE_KINDS.COLLECTION \ and len(value) > 0: value_type = type(value) new_value = value_type.__new__(value_type) if issubclass(value_type, MutableSequence): add_op = new_value.append elif issubclass(value_type, MutableSet): add_op = new_value.add else: raise ValueError('Do not know how to clone value of type ' '%s for resource attribute %s.' % (type(new_value), attr)) ent_cls = get_entity_class(attr_type) for child in value: child_clone = self.load(ent_cls, child) add_op(child_clone) state[attr] = new_value # We set the ID already above. if not id_attr is None: del state[id_attr] EntityState.set_state_data(clone, state) return clone
def __collect(self, resource): ent_cls = get_entity_class(resource) coll_cls = get_collection_class(resource) cache = EntityCacheMap() agg = StagingAggregate(ent_cls, cache) coll = coll_cls.create_from_aggregate(agg) coll.add(resource) return dict([(get_member_class(ent_cls), coll.get_root_collection(ent_cls)) for ent_cls in cache.keys()])
def create_staging_collection(resource): """ Helper function to create a staging collection for the given registered resource. :param resource: registered resource :type resource: class implementing or instance providing or subclass of a registered resource interface. """ ent_cls = get_entity_class(resource) coll_cls = get_collection_class(resource) agg = StagingAggregate(ent_cls) return coll_cls.create_from_aggregate(agg)
def set_collection_parent(self, resource, parent): """ Sets the parent of the specified root collection to the given object (typically a service object). :param resource: Registered resource. :raises ValueError: If no root collection has been created for the given registered resource. """ ent_cls = get_entity_class(resource) root_coll = self.__cache.get(ent_cls) if root_coll is None: raise ValueError('No root collection available for resource.') root_coll.__parent__ = parent
def get_collection(self, resource): if not self.__is_initialized: raise RuntimeError('Repository needs to be initialized.') ent_cls = get_entity_class(resource) root_coll = self.__cache.get(ent_cls) if root_coll is None: # Create a new root aggregate. root_agg = self.__agg_cls.create(ent_cls, self.session_factory, self) # Create a new root collection. coll_cls = get_collection_class(resource) root_coll = coll_cls.create_from_aggregate(root_agg) self.__cache[ent_cls] = root_coll return root_coll.clone()
def __get__(self, entity, entity_class): if not entity is None: ref_val = entity.__mongo_refs__[self.__attr.entity_attr] attr_entity_class = get_entity_class(self.__attr.attr_type) if isinstance(ref_val, list): # FIXME: Assuming list here. value = [transform_outgoing(attr_entity_class, self.__db.dereference(el)) for el in ref_val] else: value = transform_outgoing(attr_entity_class, self.__db.dereference(ref_val)) setattr(entity, self.__attr.entity_attr, value) else: value = self return value
def __load_entities(self, entity_class, is_top_level): # Check if we have an entity loader configured. loader = self.configuration['cache_loader'] if not loader is None: cache = self.__cache_map[entity_class] for ent in loader(entity_class): if ent.id is None: ent.id = new_entity_id() cache.add(ent) # To fully initialize the cache, we also need to load collections # that are not linked to from any of the entities just loaded. if is_top_level: for reg_rc in self.registered_resources: reg_ent_cls = get_entity_class(reg_rc) if not reg_ent_cls in self.__cache_map: self.__load_entities(reg_ent_cls, False)
def _initialize(self): if not is_engine_initialized(self.name): engine = self.__make_engine() set_engine(self.name, engine) else: engine = get_engine(self.name) db_name = self._config['db_name'] self.__db = operator.getitem(engine, db_name) if db_name == 'test': # Reset the test database. for coll_name in \ self.__db.collection_names(include_system_collections=False): self.__db.drop_collection(coll_name) # Set up the class registry. for rc in self.registered_resources: ent_cls = get_entity_class(rc) if not MongoClassRegistry.is_registered(ent_cls): MongoClassRegistry.register(ent_cls, self.__db)
def get_collection(self, resource): """ Get a clone of the root collection for the given registered resource. :param resource: Registered resource. :raises RuntimeError: If the repository has not been initialized yet. """ if not self.__is_initialized: raise RuntimeError('Repository needs to be initialized.') ent_cls = get_entity_class(resource) root_coll = self.__cache.get(ent_cls) if root_coll is None: coll_cls = get_collection_class(resource) agg = self.__agg_cls.create(ent_cls, self.session_factory) root_coll = coll_cls.create_from_aggregate(agg) self.__cache[ent_cls] = root_coll clone = root_coll.clone() clone.__repository__ = self return clone
def __init__(self, entity, name=None, relationship=None): """ Constructor: :param str name: Unique name of the member within the collection :param entity: Associated entity (domain object). :type entity: Object implementing an interface derived from :class:`everest.entities.interfaces.IEntity`. """ if self.__class__ is Member: raise NotImplementedError('Abstract class') if not isinstance(entity, get_entity_class(self)): raise ValueError( 'Invalid entity class "%s" for %s resource class.' % (entity.__class__.__name__, self.__class__.__name__)) super(Member, self).__init__(relationship=relationship) self.__entity = entity self.__name = name # Add the rel="self" link. self.add_link(Link(self, "self"))
def _convert_to_entity(self): init_map = {} nested_map = {} mapped_class = self._data.mapping.mapped_class for attr in \ self.__mapping.attribute_iterator(mapped_class=mapped_class, key=self.__attribute_key): try: val = self._get_proxied_attribute_value(attr) except AttributeError: continue else: attr_name = attr.entity_attr if not '.' in attr_name: init_map[attr_name] = val else: nested_map[attr_name] = val ent_cls = get_entity_class(mapped_class) entity = ent_cls.create_from_data(init_map) for nested_name, nested_value in iteritems_(nested_map): set_nested_attribute(entity, nested_name, nested_value) return entity
def visit(self, path, attribute, source, target): is_root = attribute is None if is_root: # Visiting the root. ent_class = get_entity_class(self._rc_class) else: ent_class = get_entity_class(attribute.attr_type) parent = path.parent if source is None: # No source - REMOVE. entity = target.get_entity() if not is_root: rel = self.__get_relationship(parent, attribute) rel.remove(entity) if not self.__remove_callback is None: if self.__pass_path_to_callbacks: args = (ent_class, entity, path) else: args = (ent_class, entity) cmd = self.RemoveCallback(self.__remove_callback, args) self.__commands.append(cmd) else: if target is None: # No target - ADD. entity = source.get_entity() if not is_root: if path.relation_operation == RELATION_OPERATIONS.ADD: # If the parent is created new, the constructor # will most likely set the child attribute. add_opts = dict(safe=True) else: add_opts = dict() rel = self.__get_relationship(parent, attribute) rel.add(entity, **add_opts) if not self.__add_callback is None: if self.__pass_path_to_callbacks: args = (ent_class, entity, path) else: args = (ent_class, entity) cmd = self.AddCallback(self.__add_callback, args) self.__commands.append(cmd) else: # Both source and target - UPDATE. entity = target.get_entity() if not self.__update_callback is None: upd_av_map = dict(source.update_attribute_value_items) if self.__pass_path_to_callbacks: args = (ent_class, upd_av_map, entity, path) else: args = (ent_class, upd_av_map, entity) cmd = self.UpdateCallback(self.__update_callback, args) self.__commands.append(cmd) if not is_root: # The relationship with the old value has already been # severed, so we only need to ADD here. rel = self.__get_relationship(parent, attribute) rel.add(entity) if is_root: self.root = entity else: self.__commands.append(rel)
def _get_entity_type(self): return get_entity_class(self._data.mapping.mapped_class)
def test_get_entity_class(self): self.assert_true(get_entity_class(IFoo), FooEntity)
def wrap(ent, *args): if isinstance(ent, type) and IEntity in implemented_by(ent): ent_cls = ent else: ent_cls = get_entity_class(ent) return func(ent_cls, *args)
def get_root_aggregate(self, rc): ent_cls = get_entity_class(rc) return StagingAggregate(ent_cls, cache=self.__cache_map)