Commit 38533a12 authored by Élodie Thiéblin's avatar Élodie Thiéblin
Browse files

[entities] simplify rdf generation and add a generic rdf adapter

Team: famarger, schabot, nchauvat, fferry, ethieblin
parent 0df0db725f07
......@@ -23,56 +23,117 @@ from cubicweb import _
from hashlib import sha1
from itertools import chain
from rdflib import URIRef, Literal
from rdflib import URIRef, Literal, namespace as rdflib_namespace
from logilab.mtconverter import TransformError
from logilab.common.decorators import cached, cachedproperty
from cubicweb.entity import EntityAdapter
from cubicweb import rdf
from cubicweb import (Unauthorized, ValidationError, ViolatedConstraint,
UniqueTogetherError)
from cubicweb.schema import constraint_name_for
from cubicweb.entity import EntityAdapter
from cubicweb.schema import constraint_name_for, VIRTUAL_RTYPES
from cubicweb.predicates import is_instance, relation_possible, match_exception
from cubicweb.rdf import NAMESPACES
class EntityRDFAdapter(EntityAdapter):
"""EntityRDFAdapter is to be specialized for each entity that wants to
be converted to RDF using the mechanism from cubicweb.rdf
"""
__abstract__ = True
__regid__ = "rdf"
SKIP_RTYPES = VIRTUAL_RTYPES | set(['cwuri', 'is', 'is_instance_of'])
def __init__(self, _cw, **kwargs):
super().__init__(_cw, **kwargs)
self.entity.complete()
self.used_namespaces = {}
def _use_namespace(self, prefix, base_url=None):
if prefix not in self.used_namespaces:
if base_url is not None:
if prefix in rdf.NAMESPACES:
raise KeyError('prefix redefinition not allowed: '
f'"{prefix}" already exists as "{rdf.NAMESPACES[prefix]}"')
ns = rdflib_namespace.Namespace(base_url)
else:
ns = rdf.NAMESPACES[prefix]
self.used_namespaces[prefix] = ns
elif base_url is not None:
if self.used_namespaces[prefix] != rdflib_namespace.Namespace(base_url):
raise ValueError('prefix redefinition not allowed: '
f'"{prefix}" already exists as "{self.used_namespaces[prefix]}"')
return self.used_namespaces[prefix]
@cachedproperty
def uri(self):
return self.entity.cwuri
return URIRef(self.entity.cwuri)
def triples(self):
"""return sequence of 3-tuple of rdflib identifiers"""
raise NotImplementedError()
yield from self.cw_triples()
yield from self.dc_triples()
def cw_triples(self):
RDF = self._use_namespace('rdf')
CW = self._use_namespace('cubicweb')
yield (self.uri, RDF.type, CW[self.entity.e_schema.type])
class CWUserFoafAdapter(EntityRDFAdapter):
__regid__ = "rdf.foaf"
for rschema, targettypes, role in self.entity.e_schema.relation_definitions('relation'):
rtype = rschema.type
if rtype in self.SKIP_RTYPES or rtype.endswith('_permission'):
continue
for targetype in targettypes:
# if rschema is an attribute
if targetype.final:
try:
value = self.entity.cw_attr_cache[rtype]
except KeyError:
continue
if value is not None:
yield (self.uri, CW[rtype], Literal(value))
# else if rschema is a relation
else:
for related in self.entity.related(rtype, role, entities=True, safe=True):
if role == 'subject':
yield (self.uri, CW[rtype], URIRef(related.cwuri))
else:
yield (URIRef(related.cwuri), CW[rtype], self.uri)
def dc_triples(self):
dc_entity = self.entity.cw_adapt_to('IDublinCore')
DC = self._use_namespace('dc')
yield (self.uri, DC.title, Literal(dc_entity.long_title())) # or title() ?
desc = dc_entity.description()
if desc:
yield (self.uri, DC.description, Literal(desc))
creator = dc_entity.creator() # use URI instead of Literal ?
if creator:
yield (self.uri, DC.creator, Literal(creator))
yield (self.uri, DC.date, Literal(dc_entity.date()))
yield (self.uri, DC.type, Literal(dc_entity.type()))
yield (self.uri, DC.language, Literal(dc_entity.language()))
class CWUserRDFAdapter(EntityRDFAdapter):
__select__ = is_instance("CWUser")
def triples(self):
RDF = NAMESPACES["rdf"]
FOAF = NAMESPACES["foaf"]
uri = URIRef(self.uri)
yield (uri, RDF.type, FOAF.Person)
yield from super().triples()
yield from self.foaf_triples()
def foaf_triples(self):
RDF = self._use_namespace('rdf')
FOAF = self._use_namespace('foaf')
yield (self.uri, RDF.type, FOAF.Person)
if self.entity.surname:
yield (uri, FOAF.familyName, Literal(self.entity.surname))
yield (self.uri, FOAF.familyName, Literal(self.entity.surname))
if self.entity.firstname:
yield (uri, FOAF.givenName, Literal(self.entity.firstname))
yield (self.uri, FOAF.givenName, Literal(self.entity.firstname))
emailaddr = self.entity.cw_adapt_to("IEmailable").get_email()
if emailaddr:
email_digest = sha1(emailaddr.encode("utf-8")).hexdigest()
yield (uri, FOAF.mbox_sha1sum, Literal(email_digest))
yield (self.uri, FOAF.mbox_sha1sum, Literal(email_digest))
class IDublinCoreAdapter(EntityAdapter):
......@@ -104,7 +165,7 @@ class IDublinCoreAdapter(EntityAdapter):
return self.entity.printable_value('description', format=format)
return u''
def authors(self):
def authors(self): # XXX is this part of DC ?
"""Return a suitable description for the author(s) of the entity"""
try:
return u', '.join(u.name() for u in self.entity.owned_by)
......@@ -132,7 +193,7 @@ class IDublinCoreAdapter(EntityAdapter):
# check if entities has internationalizable attributes
# XXX one is enough or check if all String attributes are internationalizable?
for rschema, attrschema in eschema.attribute_definitions():
if rschema.rdef(eschema, attrschema).internationalizable:
if getattr(rschema.rdef(eschema, attrschema), 'internationalizable', False):
return self._cw._(self._cw.user.property_value('ui.language'))
return self._cw._(self._cw.vreg.property_value('ui.language'))
......
......@@ -15,8 +15,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from rdflib import ConjunctiveGraph, plugin
from rdflib.namespace import Namespace, RDF, FOAF
from rdflib import plugin, namespace
import rdflib_jsonld # noqa
plugin.register("jsonld", plugin.Serializer, "rdflib_jsonld.serializer", "JsonLDSerializer")
......@@ -32,35 +31,25 @@ RDF_MIMETYPE_TO_FORMAT = {
}
NAMESPACES = {
"rdf": RDF,
"schema": Namespace("http://schema.org/"),
"foaf": FOAF,
"rdf": namespace.RDF,
"rdfs": namespace.RDFS,
"owl": namespace.OWL,
"xsd": namespace.XSD,
"skos": namespace.SKOS,
"void": namespace.VOID,
"dc": namespace.DC,
"dcterms": namespace.DCTERMS,
"foaf": namespace.FOAF,
"doap": namespace.DOAP,
"schema": namespace.Namespace("http://schema.org/"),
"cubicweb": namespace.Namespace("http://ns.cubicweb.org/cubicweb/0.0/")
}
# dict: name of CWEType -> list of regid of adapters derived from EntityRDFAdapter
ETYPES_ADAPTERS = {
"CWUser": ("rdf.foaf",),
}
def conjunctive_graph():
"""factory to build a ``ConjunctiveGraph`` and bind all namespaces
"""
graph = ConjunctiveGraph()
for prefix, rdfns in NAMESPACES.items():
graph.bind(prefix, rdfns)
return graph
def iter_rdf_adapters(entity):
for adapter_id in ETYPES_ADAPTERS.get(entity.__regid__, ()):
adapter = entity.cw_adapt_to(adapter_id)
if adapter:
yield adapter
def add_entity_to_graph(graph, entity):
for adapter in iter_rdf_adapters(entity):
adapter = entity.cw_adapt_to("rdf")
if adapter:
for triple in adapter.triples():
graph.add(triple)
for prefix, rdfns in adapter.used_namespaces.items():
graph.bind(prefix, rdfns)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment