[PATCH 4 of 6 saem_ref] [rdf] Export prov-o information for authority record and concept schemes

Sylvain Thenault sylvain.thenault at logilab.fr
Thu Mar 16 10:46:33 CET 2017


# HG changeset patch
# User Sylvain Thénault <sylvain.thenault at logilab.fr>
# Date 1489576059 -3600
#      Wed Mar 15 12:07:39 2017 +0100
# Node ID 06537e435c3d0c4281597b0311430ed17b5aadf0
# Parent  1f51cb6a736651b25e91183158cf903aceee8d04
[rdf] Export prov-o information for authority record and concept schemes

Start by updating existing but no more used _prov_mapping function. Rename
authority record test to show its scope has been extended.

Related to extranet #12175187

diff --git a/cubicweb_saem_ref/entities/rdf.py b/cubicweb_saem_ref/entities/rdf.py
--- a/cubicweb_saem_ref/entities/rdf.py
+++ b/cubicweb_saem_ref/entities/rdf.py
@@ -20,24 +20,23 @@ from cubicweb.predicates import is_insta
 from cubes.skos import rdfio, entities as skos
 
 from .. import permanent_url
 
 
-def _register_agent_prov_mapping(reg):  # XXX move to the prov cube
-    """Register RDF mapping for PROV-O entity types related to Agent.
+def _prov_mapping(reg, etype):
+    """Register RDF mapping for PROV-O entity types related to some entity type (prov:Agent).
     """
     reg.register_prefix('prov', 'http://www.w3.org/ns/prov#')
-    # reg.register_etype_equivalence('Agent', 'prov:Agent')
     reg.register_etype_equivalence('Activity', 'prov:Activity')
     reg.register_attribute_equivalence('Activity', 'type', 'prov:type')
     reg.register_attribute_equivalence('Activity', 'description', 'prov:label')
     reg.register_attribute_equivalence('Activity', 'start', 'prov:startedAtTime')
     reg.register_attribute_equivalence('Activity', 'end', 'prov:endedAtTime')
-    reg.register_relation_equivalence('Activity', 'associated_with', 'Agent',
+    reg.register_relation_equivalence('Activity', 'associated_with', 'CWUser',
                                       'prov:wasAssociatedWith')
-    reg.register_relation_equivalence('Activity', 'generated', 'Agent', 'prov:generated')
-    reg.register_relation_equivalence('Activity', 'used', 'Agent', 'prov:used')
+    reg.register_relation_equivalence('Activity', 'generated', etype, 'prov:generated')
+    reg.register_relation_equivalence('Activity', 'used', etype, 'prov:used')
 
 
 def _base_rdf_mapping(reg, etype, kind):
     reg.register_prefix('foaf', 'http://xmlns.com/foaf/0.1/')
     reg.register_prefix('org', 'http://www.w3.org/ns/org#')
@@ -55,10 +54,11 @@ def _complete_agent_rdf_mapping(reg, ety
     reg.register_attribute_equivalence(etype, 'modification_date', 'dct:modified')
 
 
 def _complete_authority_record_rdf_mapping(reg):
     _complete_agent_rdf_mapping(reg, 'AuthorityRecord')
+    _prov_mapping(reg, 'AuthorityRecord')
     reg.register_attribute_equivalence('AuthorityRecord', lambda x: x.dc_title(), 'foaf:name')
     reg.register_attribute_equivalence('AuthorityRecord', 'start_date', 'schema_org:startDate')
     reg.register_attribute_equivalence('AuthorityRecord', 'end_date', 'schema_org:endDate')
     reg.register_etype_equivalence('PostalAddress', 'vcard:Location')
     reg.register_attribute_equivalence('PostalAddress', 'street', 'vcard:street-address')
@@ -160,10 +160,14 @@ class AuthorityRecordRDFPrimaryAdapter(s
                 'authority': 'org:OrganizationUnit',
                 'family': 'foaf:Group'}.get(self.entity.kind, 'foaf:Person')
         _base_rdf_mapping(reg, 'AuthorityRecord', kind)
         _complete_authority_record_rdf_mapping(reg)
 
+    def accept(self, entity):
+        """Return True if the entity should be recursivly added to the graph."""
+        return entity.cw_etype == 'Activity'
+
     def fill(self, graph):
         super(AuthorityRecordRDFPrimaryAdapter, self).fill(graph)
         reg = self.registry
         generator = rdfio.RDFGraphGenerator(graph)
         # Export addresses
@@ -257,5 +261,40 @@ def _iter_on_relations(entity, relation_
     """
     for rtype_name, reverse_rtype_name in relation_descriptions:
         for relation in getattr(entity, rtype_name):
             for related_entity in getattr(relation, reverse_rtype_name):
                 yield rtype_name, relation, related_entity
+
+
+_ACTIVITY_ATTRIBUTES = 'cwuri description type start end'.split()
+
+
+class ConceptSchemeRDFPrimaryAdapter(skos.ConceptSchemeRDFPrimaryAdapter):
+
+    # prefetch 'ark' attribute of concept for RDF export
+    skos.ConceptSchemeRDFPrimaryAdapter.concept_attributes.append('ark')
+
+    def register_rdf_mapping(self, reg):
+        super(ConceptSchemeRDFPrimaryAdapter, self).register_rdf_mapping(reg)
+        _prov_mapping(reg, 'ConceptScheme')
+
+    def accept(self, entity):
+        """Return True if the entity should be recursivly added to the graph."""
+        return entity.cw_etype == 'Activity'
+
+
+class ConceptRDFPrimaryAdapter(skos.ConceptRDFPrimaryAdapter):
+
+    def register_rdf_mapping(self, reg):
+        super(ConceptRDFPrimaryAdapter, self).register_rdf_mapping(reg)
+        _prov_mapping(reg, 'Concept')
+
+    def accept(self, entity):
+        """Return True if the entity should be recursivly added to the graph."""
+        return entity.cw_etype == 'Activity'
+
+
+def registration_callback(vreg):
+    vreg.register_all(globals().values(), __name__, [ConceptSchemeRDFPrimaryAdapter,
+                                                     ConceptRDFPrimaryAdapter])
+    vreg.register_and_replace(ConceptSchemeRDFPrimaryAdapter, skos.ConceptSchemeRDFPrimaryAdapter)
+    vreg.register_and_replace(ConceptRDFPrimaryAdapter, skos.ConceptRDFPrimaryAdapter)
diff --git a/cubicweb_saem_ref/entities/skos.py b/cubicweb_saem_ref/entities/skos.py
--- a/cubicweb_saem_ref/entities/skos.py
+++ b/cubicweb_saem_ref/entities/skos.py
@@ -78,14 +78,10 @@ class ConceptScheme(skos.ConceptScheme):
 
 class Concept(skos.Concept):
     fetch_attrs, cw_fetch_order = fetch_config(('cwuri', 'ark', 'definition', 'example'))
 
 
-# prefetch 'ark' attribute of concept for RDF export
-skos.ConceptSchemeRDFPrimaryAdapter.concept_attributes.append('ark')
-
-
 class ConceptSchemeOAIPMHRecordAdapter(oai.OAIPMHActiveRecordAdapter):
     """OAI-PMH adapter for ConceptScheme entity type."""
     __select__ = oai.OAIPMHActiveRecordAdapter.__select__ & is_instance('ConceptScheme')
     metadata_formats = {'rdf': (oai.RDF_METADATAFORMAT, 'list.rdf')}
 
diff --git a/test/test_entities_rdf.py b/test/test_entities_rdf.py
--- a/test/test_entities_rdf.py
+++ b/test/test_entities_rdf.py
@@ -38,10 +38,11 @@ class RDFExportTC(testutils.XmlTestMixin
         self.reg.register_prefix('foaf', 'http://xmlns.com/foaf/0.1/')
         self.reg.register_prefix('lglb', 'http://www.logilab.org/saem/0#')
         self.reg.register_prefix('org', 'http://www.w3.org/ns/org#')
         self.reg.register_prefix('schema', 'http://schema.org/')
         self.reg.register_prefix('vcard', 'http://www.w3.org/2006/vcard/ns#')
+        self.reg.register_prefix('prov', 'http://www.w3.org/ns/prov#')
 
     def _rdf_triples(self, entity, adapter_name='RDFPrimary'):
         graph = RDFLibRDFGraph()
         entity.cw_adapt_to(adapter_name).fill(graph)
         return list(graph.triples())
@@ -54,10 +55,25 @@ class RDFExportTC(testutils.XmlTestMixin
     def assertItemsNotIn(self, items, container):
         """Check that elements of `items` are not in `container`."""
         for item in items:
             self.assertNotIn(item, container)
 
+    def assertHasActivity(self, entity, activity, triples):
+        entity_url = permanent_url(entity)
+        activity_url = permanent_url(activity)
+        user_url = permanent_url(activity.associated_with[0])
+        self.assertItemsIn([
+            (activity_url, self.uri('rdf:type'), self.uri('prov:Activity')),
+            (activity_url, self.uri('prov:used'), entity_url),
+            (activity_url, self.uri('prov:generated'), entity_url),
+            (activity_url, self.uri('prov:wasAssociatedWith'), user_url),
+            (activity_url, self.uri('prov:label'), activity.description),
+            (activity_url, self.uri('prov:type'), activity.type),
+            (activity_url, self.uri('prov:startedAtTime'), activity.start),
+            (activity_url, self.uri('prov:endedAtTime'), activity.end),
+        ], triples)
+
     def uri(self, s):
         return unicode(self.reg.normalize_uri(s))
 
     def test_organization_unit(self):
         with self.admin_access.client_cnx() as cnx:
@@ -111,23 +127,26 @@ class RDFExportTC(testutils.XmlTestMixin
                 (permanent_url(agent), self.uri('rdf:type'), self.uri('foaf:Person')),
                 (permanent_url(agent), self.uri('foaf:name'), u'Roger Rabbit'),
                 (permanent_url(agent), self.uri('dc:identifier'), 'ark:/' + agent.ark),
             ], triples)
 
-    def test_authority_record_rdf_type(self):
+    def test_authority_record_rdf_base(self):
         with self.admin_access.client_cnx() as cnx:
             for kind, rdfkind in ((u'authority', 'org:OrganizationUnit'),
                                   (u'person', 'foaf:Person'),
                                   (u'family', 'foaf:Group')):
                 with self.subTest(kind=kind, rdfkind=rdfkind):
                     record = testutils.authority_record(cnx, u'Acme', kind=kind)
+                    cnx.commit()
+
                     triples = self._rdf_triples(record)
                     self.assertItemsIn([
                         (permanent_url(record), self.uri('rdf:type'), self.uri(rdfkind)),
                         (permanent_url(record), self.uri('foaf:name'), u'Acme'),
                         (permanent_url(record), self.uri('dc:identifier'), 'ark:/' + record.ark),
                     ], triples)
+                    self.assertHasActivity(record, record.reverse_generated[0], triples)
 
     def test_authority_record_places(self):
         with self.admin_access.client_cnx() as cnx:
             record = testutils.authority_record(cnx, u'Acme Inc. Authority')
             work_address = cnx.create_entity('PostalAddress', street=u"1 av. de l'europe",
@@ -202,9 +221,23 @@ class RDFExportTC(testutils.XmlTestMixin
                 (relation_url, self.uri('org:organization'), permanent_url(record2)),
                 (relation_url, self.uri('org:role'),
                  u'http://www.logilab.org/saem/association_role'),
             ], triples)
 
+    def test_conceptscheme_rdf_prov(self):
+        with self.admin_access.client_cnx() as cnx:
+            scheme = testutils.setup_scheme(cnx, u'some vocab')
+            cnx.commit()
+            concept = scheme.add_concept(u'some concept')
+            cnx.commit()
+            scheme.cw_clear_all_caches()
+
+            triples = self._rdf_triples(scheme)
+            # scheme has two activities: its creation and addition of a concept
+            self.assertHasActivity(scheme, scheme.reverse_generated[0], triples)
+            self.assertHasActivity(scheme, scheme.reverse_generated[1], triples)
+            self.assertHasActivity(concept, concept.reverse_generated[0], triples)
+
 
 if __name__ == '__main__':
     import unittest
     unittest.main()


More information about the saem-devel mailing list