from __future__ import print_function
import rdflib as R
import logging
from six import with_metaclass
from yarom.graphObject import (GraphObject,
GraphObjectQuerier,
ZeroOrMoreTQLayer)
from yarom.mappedProperty import MappedPropertyClass
from yarom.variable import Variable
from yarom.propertyValue import PropertyValue
from yarom.propertyMixins import (DatatypePropertyMixin,
UnionPropertyMixin)
from yarom.mapper import FCN
from .data import DataUser
from .contextualize import (Contextualizable, ContextualizableClass,
contextualize_helper,
decontextualize_helper)
from .context import Context
from .statement import Statement
from .inverse_property import InversePropertyMixin
from .rdf_query_util import goq_hop_scorer, load
from .rdf_go_modifiers import SubClassModifier
import itertools
from lazy_object_proxy import Proxy
L = logging.getLogger(__name__)
[docs]class ContextMappedPropertyClass(MappedPropertyClass, ContextualizableClass):
def __init__(self, *args, **kwargs):
super(ContextMappedPropertyClass, self).__init__(*args, **kwargs)
self._cmpc_context = None
@property
def context(self):
return object.__getattribute__(self, '_cmpc_context')
@context.setter
def context(self, newc):
if self._cmpc_context is not None and self._cmpc_context != newc:
raise Exception('Contexts cannot be reassigned for a class')
self._cmpc_context = newc
[docs]class ContextualizedPropertyValue(PropertyValue):
@property
def context(self):
return None
class _ContextualizableLazyProxy(Proxy, Contextualizable):
""" Contextualizes its factory for execution """
def contextualize(self, context):
assert isinstance(self.__factory__, Contextualizable)
self.__factory__ = self.__factory__.contextualize(context)
return self
def __repr__(self):
return '{}({})'.format(FCN(type(self)), repr(self.__factory__))
class _StatementContextRDFObjectFactory(Contextualizable):
__slots__ = ('context', 'statement')
def __init__(self, statement):
self.context = None
self.statement = statement
def contextualize(self, context):
temp = _StatementContextRDFObjectFactory(self.statement)
temp.context = context
return temp
def __call__(self):
if self.context is None:
raise ValueError("No context has been set for this proxy")
return self.statement.context.contextualize(self.context).rdf_object
def __repr__(self):
return '{}({})'.format(FCN(type(self)), repr(self.statement))
[docs]class RealSimpleProperty(with_metaclass(ContextMappedPropertyClass,
DataUser, Contextualizable)):
multiple = False
link = R.URIRef("property")
linkName = "property"
base_namespace = R.Namespace("http://openworm.org/entities/")
def __init__(self, owner, **kwargs):
super(RealSimpleProperty, self).__init__(**kwargs)
self._v = []
self.owner = owner
self._hdf = dict()
self.filling = False
def contextualize_augment(self, context):
self._hdf[context] = None
res = contextualize_helper(context, self)
if res is not self:
cowner = context(res.owner)
res.add_attr_override('owner', cowner)
return res
[docs] def decontextualize(self):
self._hdf[self.context] = None
return decontextualize_helper(self)
def has_value(self):
for x in self._v:
if x.context == self.context:
return True
return False
def has_defined_value(self):
hdf = self._hdf.get(self.context)
if hdf is not None:
return hdf
for x in self._v:
if x.context == self.context and x.object.defined:
self._hdf[self.context] = True
return True
return False
def set(self, v):
if v is None:
raise ValueError('It is not permitted to declare a property to have value the None')
if not hasattr(v, 'idl'):
v = ContextualizedPropertyValue(v)
if not self.multiple:
self.clear()
stmt = self._insert_value(v)
if self.context is not None:
self.context.add_statement(stmt)
return stmt
[docs] def clear(self):
""" Clears values set *in all contexts* """
self._hdf = dict()
for x in self._v:
assert self in x.object.owner_properties
x.object.owner_properties.remove(self)
self._v.remove(x)
@property
def defined_values(self):
return tuple(x.object for x in self._v
if x.object.defined and x.context == self.context)
@property
def values(self):
return tuple(self._values_helper())
def _values_helper(self):
for x in self._v:
if x.context == self.context:
# XXX: decontextualzing default context here??
if self.context is not None:
yield self.context(x.object)
elif isinstance(x.object, Contextualizable):
yield x.object.decontextualize()
else:
yield x.object
@property
def rdf(self):
if self.context is not None:
return self.context.rdf_graph()
else:
return super(RealSimpleProperty, self).rdf
@property
def identifier(self):
return self.link
def fill(self):
self.filling = True
try:
self.clear()
for val in self.get():
self.set(val)
fill = getattr(val, 'fill', True)
filling = getattr(val, 'filling', True)
if fill and not filling:
fill()
finally:
self.filling = False
[docs] def get(self):
if self.rdf is None:
return ()
results = None
owner = self.owner
if owner.defined:
self._ensure_fresh_po_cache()
results = set()
for pred, obj in owner.po_cache.cache:
if pred == self.link:
results.add(obj)
else:
v = Variable("var" + str(id(self)))
self._insert_value(v)
def _zomifier(rdf_type):
if rdf_type and getattr(self, 'value_rdf_type', None) == rdf_type:
return SubClassModifier(rdf_type)
g = ZeroOrMoreTQLayer(_zomifier, self.rdf)
results = GraphObjectQuerier(v, g, parallel=False,
hop_scorer=goq_hop_scorer)()
self._remove_value(v)
return results
def _insert_value(self, v):
stmt = Statement(self.owner, self, v, self.context)
self._hdf[self.context] = None
self._v.append(stmt)
if self not in v.owner_properties:
v.owner_properties.append(self)
return stmt
def _remove_value(self, v):
assert self in v.owner_properties
self._hdf[self.context] = None
v.owner_properties.remove(self)
self._v.remove(Statement(self.owner, self, v, self.context))
def _ensure_fresh_po_cache(self):
owner = self.owner
ident = owner.identifier
graph_index = self.conf.get('rdf.graph.change_counter', None)
if graph_index is None or owner.po_cache is None or owner.po_cache.cache_index != graph_index:
owner.po_cache = POCache(graph_index, frozenset(self.rdf.predicate_objects(ident)))
def unset(self, v):
self._remove_value(v)
def __call__(self, *args, **kwargs):
return _get_or_set(self, *args, **kwargs)
def __repr__(self):
fcn = FCN(type(self))
return '{}(owner={})'.format(fcn, repr(self.owner))
def one(self):
return next(iter(self.get()), None)
def onedef(self):
for x in self._v:
if x.object.defined and x.context == self.context:
return x.object
return None
@classmethod
def on_mapper_add_class(cls, mapper):
cls.rdf_type = cls.base_namespace[cls.__name__]
cls.rdf_namespace = R.Namespace(cls.rdf_type + "/")
return cls
@property
def defined_statements(self):
return tuple(x for x in self._v
if x.object.defined and x.subject.defined)
@property
def statements(self):
return self.rdf.quads((self.owner.idl, self.link, None, None))
[docs]class POCache(tuple):
""" The predicate-object cache object """
_map = dict(cache_index=0, cache=1)
def __new__(cls, cache_index, cache):
return super(POCache, cls).__new__(cls, (cache_index, cache))
def __getattr__(self, n):
return self[POCache._map[n]]
class _ContextualizingPropertySetMixin(object):
def set(self, v):
if isinstance(v, _ContextualizableLazyProxy):
v = v.contextualize(self.context)
return super(_ContextualizingPropertySetMixin, self).set(v)
class OPResolver(object):
def __init__(self, context):
self._ctx = context
def id2ob(self, ident, typ):
from .rdf_query_util import oid
return oid(ident, typ, self._ctx)
@property
def type_resolver(self):
from .dataObject import _Resolver
return _Resolver.get_instance().type_resolver
@property
def deserializer(self):
from .dataObject import _Resolver
return _Resolver.get_instance().deserializer
@property
def base_type(self):
from .dataObject import _Resolver
return _Resolver.get_instance().base_type
class PropertyCountMixin(object):
def count(self):
return sum(1 for _ in super(PropertyCountMixin, self).get())
[docs]class ObjectProperty(InversePropertyMixin,
_ContextualizingPropertySetMixin,
PropertyCountMixin,
RealSimpleProperty):
def __init__(self, resolver=None, *args, **kwargs):
super(ObjectProperty, self).__init__(*args, **kwargs)
def contextualize_augment(self, context):
res = super(ObjectProperty, self).contextualize_augment(context)
if self is not res:
res.add_attr_override('resolver', OPResolver(context))
return res
def set(self, v):
if not isinstance(v, GraphObject):
raise Exception(
"An ObjectProperty only accepts GraphObject instances. Got a " +
str(type(v)) + " a.k.a. " +
" or ".join(str(x) for x in type(v).__bases__))
return super(ObjectProperty, self).set(v)
[docs] def get(self):
idents = super(ObjectProperty, self).get()
r = load(self.rdf, idents=idents, context=self.context,
target_type=self.value_rdf_type)
return itertools.chain(self.defined_values, r)
@property
def statements(self):
return itertools.chain(self.defined_statements,
(Statement(self.owner,
self,
self.id2ob(x[2]),
Context(ident=x[3]))
for x in super(ObjectProperty, self).statements))
[docs]class DatatypeProperty(DatatypePropertyMixin, PropertyCountMixin, RealSimpleProperty):
[docs] def get(self):
r = super(DatatypeProperty, self).get()
s = set()
unhashables = []
for x in self.defined_values:
val = self.resolver.deserializer(x.idl)
try:
s.add(val)
except TypeError as e:
unhashables.append(val)
L.info('Unhashable type: %s', e)
return itertools.chain(r, s, unhashables)
def onedef(self):
x = super(DatatypeProperty, self).onedef()
return self.resolver.deserializer(x.identifier) if x is not None else x
@property
def statements(self):
return itertools.chain(self.defined_statements,
(Statement(self.owner,
self,
self.resolver.deserializer(x[2]),
Context(ident=x[3]))
for x in super(DatatypeProperty, self).statements))
[docs]class UnionProperty(_ContextualizingPropertySetMixin,
InversePropertyMixin,
UnionPropertyMixin,
PropertyCountMixin,
RealSimpleProperty):
""" A Property that can handle either DataObjects or basic types """
[docs] def get(self):
r = super(UnionProperty, self).get()
s = set()
for x in self.defined_values:
if isinstance(x, R.Literal):
s.add(self.resolver.deserializer(x.idl))
return itertools.chain(r, s)
def _get_or_set(self, *args, **kwargs):
""" If arguments are given ``set`` method is called. Otherwise, the ``get``
method is called. If the ``multiple`` member is set to ``True``, then a
Python set containing the associated values is returned. Otherwise, a
single bare value is returned.
"""
# XXX: checking this in advance because I'm paranoid, I guess
assert hasattr(self, 'set') and hasattr(self.set, '__call__')
assert hasattr(self, 'get') and hasattr(self.get, '__call__')
assert hasattr(self, 'multiple')
if len(args) > 0 or len(kwargs) > 0:
return self.set(*args, **kwargs)
else:
r = self.get(*args, **kwargs)
if self.multiple:
return set(r)
else:
return next(iter(r), None)
def _property_to_string(self):
try:
s = str(self.linkName) + "=`" + \
";".join(str(s) for s in self.defined_values) + "'"
except AttributeError:
s = str(self.linkName) + '(no defined_values)'
return s