cubicweb/entities/adapters.py
changeset 12910 c87c3943d6ab
parent 12892 0df0db725f07
--- 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'))