# HG changeset patch # User Elodie Thieblin # Date 1583880285 -3600 # Node ID c87c3943d6ab362b5a33d820fafe83f871807757 # Parent 0df0db725f07cea86caad147c16f91863429af93 [entities] simplify rdf generation and add a generic rdf adapter Team: famarger, schabot, nchauvat, fferry, ethieblin diff -r 0df0db725f07 -r c87c3943d6ab cubicweb/entities/adapters.py --- a/cubicweb/entities/adapters.py Fri Feb 28 17:11:01 2020 +0100 +++ b/cubicweb/entities/adapters.py Tue Mar 10 23:44:45 2020 +0100 @@ -23,56 +23,117 @@ 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]) + + 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 CWUserFoafAdapter(EntityRDFAdapter): - __regid__ = "rdf.foaf" +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 @@ 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 @@ # 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')) diff -r 0df0db725f07 -r c87c3943d6ab cubicweb/rdf.py --- a/cubicweb/rdf.py Fri Feb 28 17:11:01 2020 +0100 +++ b/cubicweb/rdf.py Tue Mar 10 23:44:45 2020 +0100 @@ -15,8 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . -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 @@ } NAMESPACES = { - "rdf": RDF, - "schema": Namespace("http://schema.org/"), - "foaf": FOAF, -} - - -# dict: name of CWEType -> list of regid of adapters derived from EntityRDFAdapter -ETYPES_ADAPTERS = { - "CWUser": ("rdf.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/") } -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)