Source code for owmeta_core.identifier_mixin
from __future__ import print_function
import hashlib
from rdflib.term import URIRef
from urllib.parse import quote
from six import string_types
from .graph_object import IdentifierMissingException
__all__ = ['IdMixin']
# This was pulled out of DataObject when it was used by multiple classes. It may prove
# useful for making custom GraphObjects, so it's retained here as a mixin
[docs]class IdMixin:
'''
Mixin that provides common identifier logic
Attributes
----------
hashfun : function
The function to use for encoding data provided to make_identifier.
Should return an object can ``.encode()`` to a :py:class:`bytes` (a.k.a.
:py:class:`str` in Python 2). Defaults to :py:func:`hashlib.sha224`
rdf_namespace : rdflib.namespace.Namespace
The namespace for identifiers created
direct_key : bool
Whether to make a key directly, just adding the string onto the namespace or
indirectly by hashing the key before joining with the namespace.
'''
hashfun = hashlib.sha224
direct_key = True
def __init__(self, ident=None, key=None, *args, direct_key=None, **kwargs):
super(IdMixin, self).__init__(*args, **kwargs)
if key is not None and ident is not None:
raise Exception("Only one of 'key' or 'ident' can be given to Context")
if ident is not None:
self._id = URIRef(ident)
else:
self._id = None
if direct_key is not None:
self.direct_key = bool(direct_key)
self.key = key
self._id_key = None
self._generated_id = None
[docs] @classmethod
def make_identifier(cls, data):
'''
Makes an identifier based on this class' `rdf_namespace` by calling
`__str__` on the data and passing to the class' `hashfun`.
If the `__str__` for data's type doesn't function as an identifier, you
should use either :meth:`make_identifier_direct` or override
:meth:`identifier_augment` and :meth:`defined_augment`
'''
strdata = str(data)
if strdata:
hsh = "a" + cls.hashfun(strdata.encode()).hexdigest()
return URIRef(cls.rdf_namespace[hsh])
else:
raise ValueError('Cannot use falsy value'
' {} to make an identifier'.format(strdata))
[docs] @classmethod
def make_identifier_direct(cls, string):
'''
Make identifier by using the `~urllib.parse.quote`'d value of `key` appended to
the `rdf_namespace` value
'''
if not isinstance(string, string_types):
raise ValueError('make_identifier_direct only accepts strings')
return URIRef(cls.rdf_namespace[quote(string)])
def _gen_identifier(self):
key = self.key
if key is None:
return None
if key == self._id_key:
return self._generated_id
if self.direct_key:
self._generated_id = self.make_identifier_direct(key)
else:
self._generated_id = self.make_identifier(key)
self._id_key = key
return self._generated_id
@property
def identifier(self):
'''
The identifier
'''
if self._id is not None:
return self._id
else:
ident = self._gen_identifier()
if ident is not None:
return ident
elif self.defined_augment():
return self.identifier_augment()
else:
raise IdentifierMissingException(self)
@identifier.setter
def identifier(self, value):
self._id = value
[docs] def identifier_augment(self):
"""
Override this method to define an identifier in lieu of one explicity set.
One must also override :meth:`defined_augment` to return True whenever
this method could return a valid identifier.
:exc:`~.graph_object.IdentifierMissingException` should be
raised if an identifier cannot be generated by this method.
Raises
------
`~.graph_object.IdentifierMissingException`
"""
raise IdentifierMissingException(self)
@property
def defined(self):
if self._id is not None or self.key is not None:
return True
else:
return self.defined_augment()
[docs] def defined_augment(self):
""" This fuction must return False if :meth:`identifier_augment` would
raise an :exc:`~.graph_object.IdentifierMissingException`. Override
it when defining a non-standard identifier for subclasses of DataObjects.
"""
return False