import json
import logging
from urlparse import urlparse
from rdflib import Graph
from oldman.model import Model
from oldman.resource import Resource
from oldman.exception import OMUndeclaredClassNameError, OMExpiredMethodDeclarationTimeSlotError, OMError
from oldman.iri import PrefixedUUIDIriGenerator, IncrementalIriGenerator, BlankNodeIriGenerator
from oldman.parsing.schema.attribute import OMAttributeExtractor
from .registry import ModelRegistry
from .ancestry import ClassAncestry
DEFAULT_MODEL_NAME = "Thing"
[docs]class ResourceManager(object):
"""The `resource_manager` is the central object of this OLDM.
It gives access to the :class:`~oldman.store.datastore.DataStore` object
and creates :class:`~oldman.model.Model` objects.
It also creates, retrieves and caches :class:`~oldman.resource.Resource` objects.
Internally, it owns a :class:`~oldman.management.registry.ModelRegistry` object.
:param schema_graph: :class:`rdflib.Graph` object containing all the schema triples.
:param data_store: :class:`~oldman.store.datastore.DataStore` object. Supports CRUD operations on
:class:`~oldman.resource.Resource` objects.
:param attr_extractor: :class:`~oldman.parsing.attribute.OMAttributeExtractor` object that
will extract :class:`~oldman.attribute.OMAttribute` for generating
new :class:`~oldman.model.Model` objects.
Defaults to a new instance of :class:`~oldman.parsing.attribute.OMAttributeExtractor`.
:param manager_name: Name of this manager. Defaults to `"default"`. This name must be unique.
"""
_managers = {}
def __init__(self, schema_graph, data_store, attr_extractor=None,
manager_name="default"):
self._attr_extractor = attr_extractor if attr_extractor is not None else OMAttributeExtractor()
self._schema_graph = schema_graph
self._data_store = data_store
self._methods = {}
self._registry = ModelRegistry()
self._logger = logging.getLogger(__name__)
self._name = manager_name
if manager_name in self._managers:
raise OMError(u"Manager name %s is already allocated" % manager_name)
self._managers[manager_name] = self
self._include_reversed_attributes = False
# Registered with the "None" key
self._create_model(DEFAULT_MODEL_NAME, {u"@context": {}}, untyped=True,
iri_prefix=u"http://localhost/.well-known/genid/default/", is_default=True)
# Register it
self._data_store.manager = self
@property
[docs] def data_store(self):
""":class:`~oldman.store.datastore.DataStore` object. Supports CRUD operations on
`:class:`~oldman.resource.Resource` objects`.
"""
return self._data_store
@property
[docs] def name(self):
"""Name of this manager.
The manager can be retrieved from its name by calling the
class method :func:`~oldman.management.manager.ResourceManager.get_manager`.
"""
return self._name
@property
[docs] def include_reversed_attributes(self):
"""Is `True` if at least one of its models use some reversed attributes."""
return self._include_reversed_attributes
@classmethod
[docs] def get_manager(cls, name):
"""Gets a :class:`~oldman.management.manager.ResourceManager` object by its name.
:param name: manager name.
:return: A :class:`~oldman.management.manager.ResourceManager` object.
"""
return cls._managers.get(name)
[docs] def declare_method(self, method, name, class_iri):
"""Attaches a method to the :class:`~oldman.resource.Resource` objects that are instances of a given RDFS class.
Like in Object-Oriented Programming, this method can be overwritten by attaching a homonymous
method to a class that has a higher inheritance priority (such as a sub-class).
To benefit from this method (or an overwritten one), :class:`~oldman.resource.Resource` objects
must be associated to a :class:`~oldman.model.Model` that corresponds to the RDFS class or to one of its
subclasses.
This method can only be used before the creation of any model (except the default one).
:param method: Python function that takes as first argument a :class:`~oldman.resource.Resource` object.
:param name: Name assigned to this method.
:param class_iri: Targetted RDFS class. If not overwritten, all the instances
(:class:`~oldman.resource.Resource` objects) should inherit this method.
"""
if self._registry.has_specific_models():
raise OMExpiredMethodDeclarationTimeSlotError(u"Method declaration cannot occur after model creation.")
if class_iri in self._methods:
if name in self._methods[class_iri]:
self._logger.warn(u"Method %s of %s is overloaded." % (name, class_iri))
self._methods[class_iri][name] = method
else:
self._methods[class_iri] = {name: method}
[docs] def create_model(self, class_name_or_iri, context, iri_generator=None, iri_prefix=None,
iri_fragment=None, incremental_iri=False):
"""Creates a :class:`~oldman.model.Model` object.
To create it, they are three elements to consider:
1. Its class IRI which can be retrieved from `class_name_or_iri`;
2. Its JSON-LD context for mapping :class:`~oldman.attribute.OMAttribute` values to RDF triples;
3. The :class:`~oldman.iri.IriGenerator` object that generates IRIs from new
:class:`~oldman.resource.Resource` objects.
The :class:`~oldman.iri.IriGenerator` object is either:
* directly given: `iri_generator`;
* created from the parameters `iri_prefix`, `iri_fragment` and `incremental_iri`.
:param class_name_or_iri: IRI or JSON-LD term of a RDFS class.
:param context: `dict`, `list` or `IRI` that represents the JSON-LD context .
:param iri_generator: :class:`~oldman.iri.IriGenerator` object. If given, other `iri_*` parameters are
ignored.
:param iri_prefix: Prefix of generated IRIs. Defaults to `None`.
If is `None` and no `iri_generator` is given, a :class:`~oldman.iri.BlankNodeIriGenerator` is created.
:param iri_fragment: IRI fragment that is added at the end of generated IRIs. For instance, `"me"`
adds `"#me"` at the end of the new IRI. Defaults to `None`. Has no effect if `iri_prefix` is not given.
:param incremental_iri: If `True` an :class:`~oldman.iri.IncrementalIriGenerator` is created instead of a
:class:`~oldman.iri.RandomPrefixedIriGenerator`. Defaults to `False`.
Has no effect if `iri_prefix` is not given.
:return: A new :class:`~oldman.model.Model` object.
"""
return self._create_model(class_name_or_iri, context, iri_generator=iri_generator, iri_prefix=iri_prefix,
iri_fragment=iri_fragment, incremental_uri=incremental_iri)
def _create_model(self, class_name_or_iri, context, iri_prefix=None, iri_fragment=None,
iri_generator=None, untyped=False, incremental_uri=False, is_default=False):
# Only for the DefaultModel
if untyped:
class_iri = None
ancestry = ClassAncestry(class_iri, self._schema_graph)
om_attributes = {}
else:
class_iri = _extract_class_iri(class_name_or_iri, context)
ancestry = ClassAncestry(class_iri, self._schema_graph)
om_attributes = self._attr_extractor.extract(class_iri, ancestry.bottom_up, context,
self._schema_graph, self)
if iri_generator is not None:
id_generator = iri_generator
elif iri_prefix is not None:
if incremental_uri:
id_generator = IncrementalIriGenerator(iri_prefix, self._data_store,
class_iri, fragment=iri_fragment)
else:
id_generator = PrefixedUUIDIriGenerator(iri_prefix, fragment=iri_fragment)
else:
id_generator = BlankNodeIriGenerator()
methods = {}
for m_dict in [self._methods.get(t, {}) for t in ancestry.top_down]:
methods.update(m_dict)
model = Model(self, class_name_or_iri, class_iri, ancestry.bottom_up, context, om_attributes,
id_generator, methods=methods)
self._registry.register(model, is_default=is_default)
# Reversed attributes awareness
if not self._include_reversed_attributes:
self._include_reversed_attributes = model.has_reversed_attributes
return model
[docs] def new(self, id=None, types=None, hashless_iri=None, **kwargs):
"""Creates a new :class:`~oldman.resource.Resource` object **without saving it** in the `data_store`.
The `kwargs` dict can contains regular attribute key-values that will be assigned to
:class:`~oldman.attribute.OMAttribute` objects.
:param id: IRI of the new resource. Defaults to `None`.
If not given, the IRI is generated by the IRI generator of the main model.
:param types: IRIs of RDFS classes the resource is instance of. Defaults to `None`.
Note that these IRIs are used to find the models of the resource
(see :func:`~oldman.management.manager.ResourceManager.find_models_and_types` for more details).
:param hashless_iri: hash-less IRI that MAY be considered when generating an IRI for the new resource.
Defaults to `None`. Ignored if `id` is given.
:return: A new :class:`~oldman.resource.Resource` object.
"""
if (types is None or len(types) == 0) and len(kwargs) == 0:
name = id if id is not None else ""
self._logger.info(u"""New resource %s has no type nor attribute.
As such, nothing is stored in the data graph.""" % name)
return Resource(self, id=id, types=types, hashless_iri=hashless_iri, **kwargs)
[docs] def create(self, id=None, types=None, hashless_iri=None, **kwargs):
"""Creates a new resource and save it in the `data_store`.
See :func:`~oldman.management.manager.ResourceManager.new` for more details.
"""
return self.new(id=id, types=types, hashless_iri=hashless_iri, **kwargs).save()
[docs] def get(self, id=None, types=None, hashless_iri=None, eager_with_reversed_attributes=True, **kwargs):
"""See :func:`oldman.store.datastore.DataStore.get`."""
return self._data_store.get(id=id, types=types, hashless_iri=hashless_iri,
eager_with_reversed_attributes=eager_with_reversed_attributes, **kwargs)
[docs] def filter(self, types=None, hashless_iri=None, limit=None, eager=False, pre_cache_properties=None, **kwargs):
"""See :func:`oldman.store.datastore.DataStore.filter`."""
return self._data_store.filter(types=types, hashless_iri=hashless_iri, limit=limit, eager=eager,
pre_cache_properties=pre_cache_properties, **kwargs)
[docs] def sparql_filter(self, query):
"""See :func:`oldman.store.datastore.DataStore.sparql_filter`."""
return self._data_store.sparql_filter(query)
[docs] def find_models_and_types(self, type_set):
"""See :func:`oldman.management.registry.ModelRegistry.find_models_and_types`."""
return self._registry.find_models_and_types(type_set)
def _extract_class_iri(class_name, context):
"""Extracts the class IRI as the type of a blank node."""
g = Graph().parse(data=json.dumps({u"@type": class_name}),
context=context, format="json-ld")
class_iri = unicode(g.objects().next())
# Check the URI
result = urlparse(class_iri)
if result.scheme == u"file":
raise OMUndeclaredClassNameError(u"Deduced URI %s is not a valid HTTP URL" % class_iri)
return class_iri