import json
import logging
from urlparse import urlparse
from rdflib import Graph
from oldman.core.exception import OMUndeclaredClassNameError, OMExpiredMethodDeclarationTimeSlotError
from oldman.core.parsing.schema.attribute import OMAttributeExtractor
from oldman.core.model.registry import ModelRegistry
from oldman.core.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`.
"""
def __init__(self, schema_graph=None, attr_extractor=None):
self._attr_extractor = attr_extractor if attr_extractor is not None else OMAttributeExtractor()
self._schema_graph = schema_graph
self._operation_functions = {}
self._registry = ModelRegistry()
self._logger = logging.getLogger(__name__)
self._include_reversed_attributes = False
# # Create "anonymous" models
# if schema_graph is not None:
# self._create_anonymous_models()
@property
def include_reversed_attributes(self):
"""Is `True` if at least one of its models use some reversed attributes."""
return self._include_reversed_attributes
@property
def models(self):
"""TODO: describe."""
return self._registry.models
@property
def non_default_models(self):
"""TODO: describe."""
return self._registry.non_default_models
@property
def schema_graph(self):
return self._schema_graph
[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.model.registry.ModelRegistry.find_models_and_types`."""
return self._registry.find_models_and_types(type_set)
[docs] def find_main_model(self, type_set):
"""See :func:`oldman.model.registry.ModelRegistry.find_main_model`."""
return self._registry.find_main_model(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)
def _create_model(self, class_name_or_iri, context_iri_or_payload,
untyped=False, is_default=False, context_file_path=None, **kwargs):
# Only for the DefaultModel
if untyped:
class_iri = None
ancestry = ClassAncestry(class_iri, self._schema_graph)
om_attributes = {}
# Regular models
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)
model = self._instantiate_model(class_name_or_iri, class_iri, ancestry, context_iri_or_payload,
om_attributes, context_file_path, **kwargs)
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
def _instantiate_model(self, class_name_or_iri, class_iri, ancestry, context_iri_or_payload, om_attributes,
local_context, **kwargs):
raise NotImplementedError("To be implemented in sub-classes")
[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)
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