def __init__(self, model_manager, cache_region=None, accept_iri_generation_configuration=True, support_sparql=False): self._model_manager = model_manager self._logger = logging.getLogger(__name__) self._resource_cache = ResourceCache(cache_region) self._name = str(uuid4()) self._stores[self._name] = self self._accept_iri_generation_configuration = accept_iri_generation_configuration self._support_sparql=support_sparql if not self._model_manager.has_default_model(): self._model_manager.create_model(DEFAULT_MODEL_PREFIX + self._name, {u"@context": {}}, self, untyped=True, iri_prefix=u"http://localhost/.well-known/genid/%s/" % self._name, is_default=True)
class DataStore(object): """A :class:`~oldman.store.datastore.DataStore` object manages CRUD operations on :class:`~oldman.resource.Resource` objects. In the future, non-CRUD operations may also be supported. Manages the cache (:class:`~oldman.resource.cache.ResourceCache` object) of :class:`~oldman.resource.Resource` object. A :class:`~oldman.resource.manager.ResourceManager` object must be assigned after instantiation of this object. :param model_manager: TODO: describe!!! :param cache_region: :class:`dogpile.cache.region.CacheRegion` object. This object must already be configured. Defaults to `None` (no cache). See :class:`~oldman.store.cache.ResourceCache` for further details. :param accept_iri_generation_configuration: If False, the IRI generator cannot be configured by the user: it is imposed by the data store. Default to `False`. """ _stores = {} def __init__(self, model_manager, cache_region=None, accept_iri_generation_configuration=True, support_sparql=False): self._model_manager = model_manager self._logger = logging.getLogger(__name__) self._resource_cache = ResourceCache(cache_region) self._name = str(uuid4()) self._stores[self._name] = self self._accept_iri_generation_configuration = accept_iri_generation_configuration self._support_sparql=support_sparql if not self._model_manager.has_default_model(): self._model_manager.create_model(DEFAULT_MODEL_PREFIX + self._name, {u"@context": {}}, self, untyped=True, iri_prefix=u"http://localhost/.well-known/genid/%s/" % self._name, is_default=True) @property def model_manager(self): """The :class:`~oldman.model.manager.ModelManager` object. TODO: update Necessary for creating new :class:`~oldman.resource.Resource` objects and accessing to :class:`~oldman.model.Model` objects. """ return self._model_manager @property def resource_cache(self): """:class:`~oldman.resource.cache.ResourceCache` object.""" return self._resource_cache @classmethod def get_store(cls, name): """Gets a :class:`~oldman.store.datastore.DataStore` object by its name. :param name: store name. :return: A :class:`~oldman.resource.manager.ModelManager` object. """ return cls._stores.get(name) @property def name(self): """Randomly generated name. Useful for serializing resources.""" return self._name def support_sparql_filtering(self): """Returns `True` if the datastore supports SPARQL queries (no update). Note that in such a case, the :func:`~oldman.store.datastore.DataStore.sparql_filter` method is expected to be implemented. """ return self._support_sparql def get(self, id=None, types=None, hashless_iri=None, eager_with_reversed_attributes=True, **kwargs): """Gets the first :class:`~oldman.resource.Resource` object matching the given criteria. The `kwargs` dict can contains regular attribute key-values. When `id` is given, types are then checked. An :exc:`~oldman.exception.OMClassInstanceError` is raised if the resource is not instance of these classes. **Other criteria are not checked**. :param id: IRI of the resource. Defaults to `None`. :param types: IRIs of the RDFS classes filtered resources must be instance of. Defaults to `None`. :param hashless_iri: Hash-less IRI of filtered resources. Defaults to `None`. :param eager_with_reversed_attributes: Allow to Look eagerly for reversed RDF properties. May cause some overhead for some :class:`~oldman.resource.Resource` objects that do not have reversed attributes. Defaults to `True`. :return: A :class:`~oldman.resource.Resource` object or `None` if no resource has been found. """ types = set(types) if types is not None else set() if id is not None: resource = self._get_by_id(id) if not types.issubset(resource.types): missing_types = types.difference(resource.types) raise OMClassInstanceError(u"%s found, but is not instance of %s" % (id, missing_types)) if len(kwargs) > 0: self._logger.warn(u"get(): id given so attributes %s are just ignored" % kwargs.keys()) return resource elif hashless_iri is None and len(kwargs) == 0: return self._get_first_resource_found() elif hashless_iri is not None: resources = self.filter(types=types, hashless_iri=hashless_iri, **kwargs) return self._select_resource_from_hashless_iri(hashless_iri, list(resources)) # First found resources = self.filter(types=types, hashless_iri=hashless_iri, limit=1, **kwargs) for resource in resources: return resource return None def filter(self, types=None, hashless_iri=None, limit=None, eager=False, pre_cache_properties=None, **kwargs): """Finds the :class:`~oldman.resource.Resource` objects matching the given criteria. The `kwargs` dict can contains: 1. regular attribute key-values ; 2. the special attribute `id`. If given, :func:`~oldman.store.datastore.DataStore.get` is called. :param types: IRIs of the RDFS classes filtered resources must be instance of. Defaults to `None`. :param hashless_iri: Hash-less IRI of filtered resources. Defaults to `None`. :param limit: Upper bound on the number of solutions returned (e.g. SPARQL LIMIT). Positive integer. Defaults to `None`. :param eager: If `True` loads all the Resource objects within the minimum number of queries (e.g. one single SPARQL query). Defaults to `False` (lazy). :param pre_cache_properties: List of RDF ObjectProperties to pre-cache eagerly. Their values (:class:`~oldman.resource.Resource` objects) are loaded and added to the cache. Defaults to `[]`. If given, `eager` must be `True`. Disabled if there is no cache. :return: A generator (if lazy) or a list (if eager) of :class:`~oldman.resource.Resource` objects. """ if not eager and pre_cache_properties is not None: raise AttributeError(u"Eager properties are incompatible with lazyness. Please set eager to True.") id = kwargs.pop("id") if "id" in kwargs else None type_iris = types if types is not None else [] if id is not None: return self.get(id=id, types=types, hashless_iri=hashless_iri, **kwargs) if len(type_iris) == 0 and len(kwargs) > 0: raise OMAttributeAccessError(u"No type given in filter() so attributes %s are ambiguous." % kwargs.keys()) return self._filter(type_iris, hashless_iri, limit, eager, pre_cache_properties, **kwargs) def sparql_filter(self, query): """Finds the :class:`~oldman.resource.Resource` objects matching a given query. Raises an :class:`~oldman.exception.UnsupportedDataStorageFeatureException` exception if the SPARQL protocol is not supported by the concrete data_store. :param query: SPARQL SELECT query where the first variable assigned corresponds to the IRIs of the resources that will be returned. :return: A generator of :class:`~oldman.resource.Resource` objects. """ raise UnsupportedDataStorageFeatureException("This datastore %s does not support the SPARQL protocol." % self.__class__.__name__) def save(self, resource, attributes, former_types): """End-users should not call it directly. Call :func:`oldman.Resource.save()` instead. :param resource: :class:`~oldman.resource.Resource` object. :param attributes: Ordered list of :class:`~oldman.attribute.OMAttribute` objects. :param former_types: List of RDFS class IRIs previously saved. """ id = self._save_resource_attributes(resource, attributes, former_types) resource.receive_id(id) # Cache self._resource_cache.set_resource(resource) def delete(self, resource, attributes, former_types): """End-users should not call it directly. Call :func:`oldman.Resource.delete()` instead. :param resource: :class:`~oldman.resource.Resource` object. :param attributes: Ordered list of :class:`~oldman.attribute.OMAttribute` objects. :param former_types: List of RDFS class IRIs previously saved. """ self._save_resource_attributes(resource, attributes, former_types) # Cache self._resource_cache.remove_resource(resource) def exists(self, resource_iri): """ Tests if the IRI of the resource is present in the data_store. May raise an :class:`~oldman.exception.UnsupportedDataStorageFeatureException` exception. :param resource_iri: IRI of the :class:`~oldman.resource.Resource` object. :return: `True` if exists. """ raise UnsupportedDataStorageFeatureException("This datastore %s cannot test the existence of an IRI." % self.__class__.__name__) def generate_instance_number(self, class_iri): """ Generates a new incremented number for a given RDFS class IRI. May raise an :class:`~oldman.exception.UnsupportedDataStorageFeatureException` exception. :param class_iri: RDFS class IRI. :return: Incremented number. """ raise UnsupportedDataStorageFeatureException("This datastore %s does not generate instance numbers." % self.__class__.__name__) def reset_instance_counter(self, class_iri): """ Reset the counter related to a given RDFS class. For test purposes **only**. :param class_iri: RDFS class IRI. """ raise UnsupportedDataStorageFeatureException("This datastore %s does not manage instance counters." % self.__class__.__name__) def check_and_repair_counter(self, class_iri): """ Checks the counter of a given RDFS class and repairs (inits) it if needed. :param class_iri: RDFS class IRI. """ raise UnsupportedDataStorageFeatureException("This datastore %s does not manage instance counters." % self.__class__.__name__) def create_model(self, class_name_or_iri, context_iri_or_payload, iri_generator=None, iri_prefix=None, iri_fragment=None, incremental_iri=False, context_file_path=None): """TODO: comment. Convenience function """ if not self._accept_iri_generation_configuration: if iri_generator or iri_prefix or iri_fragment or incremental_iri: # TODO: find a better exception raise Exception("The generator is imposed by the datastore, it cannot" "be configured by the user.") else: iri_generator = self._create_iri_generator(class_name_or_iri) self._model_manager.create_model(class_name_or_iri, context_iri_or_payload, self, iri_generator=iri_generator, iri_prefix=iri_prefix, iri_fragment=iri_fragment, incremental_iri=incremental_iri, context_file_path=context_file_path) def _get_first_resource_found(self): raise UnsupportedDataStorageFeatureException("This datastore %s cannot get a resource at random." % self.__class__.__name__) def _get_by_id(self, id): raise UnsupportedDataStorageFeatureException("This datastore %s cannot get a resource from its IRI." % self.__class__.__name__) def _filter(self, type_iris, hashless_iri, limit, eager, pre_cache_properties, **kwargs): raise UnsupportedDataStorageFeatureException("This datastore %s does not support filtering queries." % self.__class__.__name__) def _save_resource_attributes(self, resource, attributes): """ TODO: describe :param resource: :param attributes: :return: the ID of resource (useful when the IRI was a temporary one (e.g. a skolemized IRI). """ raise UnsupportedDataStorageFeatureException("This datastore %s cannot update resources (read-only)." % self.__class__.__name__) def _new_resource_object(self, id, resource_graph): resource = StoreResource.load_from_graph(self._model_manager, self, id, resource_graph, is_new=False) self.resource_cache.set_resource(resource) return resource def _select_resource_from_hashless_iri(self, hashless_iri, resources): if len(resources) == 0: raise OMObjectNotFoundError(u"No resource with hash-less iri %s" % hashless_iri) elif len(resources) > 1: for r in resources: if r.id == hashless_iri: return r # TODO: avoid such arbitrary selection self._logger.warn(u"Multiple resources have the same base_uri: %s\n. " u"The first one is selected." % resources) return resources[0] def _create_iri_generator(self, class_name_or_iri): raise UnsupportedDataStorageFeatureException("This datastore %s does create IRI generators." % self.__class__.__name__)