Source code for oldman.model.manager

import json
import logging
from urlparse import urlparse

from rdflib import Graph
from oldman.model.converter import ModelConversionManager, EquivalentModelConverter

from oldman.model.model import Model, ClientModel
from oldman.exception import OMUndeclaredClassNameError, OMExpiredMethodDeclarationTimeSlotError
from oldman.iri import PrefixedUUIDIriGenerator, IncrementalIriGenerator, BlankNodeIriGenerator
from oldman.parsing.schema.attribute import OMAttributeExtractor
from oldman.parsing.operation import HydraOperationExtractor
from oldman.vocabulary import HYDRA_COLLECTION_IRI, HYDRA_PAGED_COLLECTION_IRI, HTTP_POST
from oldman.model.operation import append_to_hydra_collection, append_to_hydra_paged_collection
from oldman.model.registry import ModelRegistry
from oldman.model.ancestry import ClassAncestry


[docs]class ModelManager(object): """ TODO: update this documentation The `model_manager` creates and registers :class:`~oldman.model.Model` objects. Internally, it owns a :class:`~oldman.resource.registry.ModelRegistry` object. :param schema_graph: :class:`rdflib.Graph` object containing all the schema triples. :param data_store: :class:`~oldman.store.datastore.DataStore` object. :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 oper_extractor: TODO: describe. :param declare_default_operation_functions: TODO: describe. """ def __init__(self, schema_graph=None, attr_extractor=None, oper_extractor=None, declare_default_operation_functions=True): self._attr_extractor = attr_extractor if attr_extractor is not None else OMAttributeExtractor() self._operation_extractor = oper_extractor if oper_extractor is not None else HydraOperationExtractor() self._schema_graph = schema_graph self._operation_functions = {} self._registry = ModelRegistry() self._logger = logging.getLogger(__name__) self._include_reversed_attributes = False # TODO: examine their relevance if declare_default_operation_functions: self.declare_operation_function(append_to_hydra_collection, HYDRA_COLLECTION_IRI, HTTP_POST) self.declare_operation_function(append_to_hydra_paged_collection, HYDRA_PAGED_COLLECTION_IRI, HTTP_POST) # # Create "anonymous" models # if schema_graph is not None: # self._create_anonymous_models() @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
@property
[docs] def models(self): """TODO: describe.""" return self._registry.models
@property
[docs] def non_default_models(self): """TODO: describe.""" return self._registry.non_default_models
[docs] def has_default_model(self): return self._registry.default_model is not None
[docs] def declare_operation_function(self, func, class_iri, http_method): """ TODO: comment """ if self._registry.has_specific_models(): raise OMExpiredMethodDeclarationTimeSlotError(u"Operation declaration cannot occur after model creation.") http_method = http_method.upper() if class_iri in self._operation_functions: if http_method in self._methods[class_iri]: self._logger.warn(u"Operation %s of %s is overloaded." % (http_method, class_iri)) self._operation_functions[class_iri][http_method] = func else: self._operation_functions[class_iri] = {http_method: func}
[docs] def find_models_and_types(self, type_set): """See :func:`oldman.resource.registry.ModelRegistry.find_models_and_types`.""" return self._registry.find_models_and_types(type_set)
[docs] def find_descendant_models(self, top_ancestor_name_or_iri): """TODO: explain. Includes the top ancestor. """ return self._registry.find_descendant_models(top_ancestor_name_or_iri)
[docs] def create_model(self, class_name_or_iri, context_iri_or_payload, data_store, iri_prefix=None, iri_fragment=None, iri_generator=None, untyped=False, incremental_iri=False, is_default=False, context_file_path=None): """Creates a :class:`~oldman.model.Model` object. TODO: remove data_store from the constructor! 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_iri_or_payload: `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. :param context_file_path: TODO: describe. """ # Only for the DefaultModel if untyped: class_iri = None ancestry = ClassAncestry(class_iri, self._schema_graph) om_attributes = {} else: context_file_path_or_payload = context_file_path if context_file_path is not None \ else context_iri_or_payload class_iri = _extract_class_iri(class_name_or_iri, context_file_path_or_payload) ancestry = ClassAncestry(class_iri, self._schema_graph) om_attributes = self._attr_extractor.extract(class_iri, ancestry.bottom_up, context_file_path_or_payload, self._schema_graph) if iri_generator is not None: id_generator = iri_generator elif iri_prefix is not None: if incremental_iri: id_generator = IncrementalIriGenerator(iri_prefix, data_store, class_iri, fragment=iri_fragment) else: id_generator = PrefixedUUIDIriGenerator(iri_prefix, fragment=iri_fragment) else: id_generator = BlankNodeIriGenerator() operations = self._operation_extractor.extract(ancestry, self._schema_graph, self._operation_functions) model = Model(class_name_or_iri, class_iri, ancestry.bottom_up, context_iri_or_payload, om_attributes, id_generator, operations=operations, local_context=context_file_path) self._add_model(model, is_default=is_default) # Reversed attributes awareness if not self._include_reversed_attributes: self._include_reversed_attributes = model.has_reversed_attributes # Anonymous classes derived from hydra:Link properties self._create_anonymous_models(model, context_file_path, data_store) return model
[docs] def get_model(self, class_name_or_iri): return self._registry.get_model(class_name_or_iri)
def _add_model(self, model, is_default=False): self._registry.register(model, is_default=is_default) def _create_anonymous_models(self, model, context_iri_or_payload, data_store): """ These classes are typically derived from hydra:Link. Their role is just to support some operations. """ classes = {attr.om_property.link_class_iri for attr in model.om_attributes.values()}.difference({None}) for cls_iri in classes: if self._registry.get_model(cls_iri) is None: self.create_model(cls_iri, context_iri_or_payload, data_store)
[docs]class ClientModelManager(ModelManager): """Client ModelManager. Has access to the `resource_manager`. In charge of the conversion between and store and client models. """ def __init__(self, resource_manager, **kwargs): ModelManager.__init__(self, **kwargs) self._resource_manager = resource_manager self._conversion_manager = ModelConversionManager() @property
[docs] def resource_manager(self): return self._resource_manager
[docs] def import_model(self, store_model, data_store, is_default=False): """ Imports a store model. Creates the corresponding client model. """ if is_default: # Default model client_model = self.get_model(None) else: client_model = ClientModel.copy_store_model(self._resource_manager, store_model) # Hierarchy registration self._registry.register(client_model, is_default=False) # Converter converter = EquivalentModelConverter(client_model, store_model) self._conversion_manager.register_model_converter(client_model, store_model, data_store, converter)
[docs] def convert_store_resources(self, store_resources): """Returns converted client resources. """ return self._conversion_manager.convert_store_to_client_resources(store_resources, self._resource_manager)
[docs] def convert_client_resource(self, client_resource): """Returns converted store resources. """ return self._conversion_manager.convert_client_to_store_resource(client_resource)
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