Browse Source

Merge pull request #111 from scossu/rel_uris

Relative URIs
Stefano Cossu 4 năm trước cách đây
mục cha
commit
d072557c57

+ 15 - 2
lakesuperior/api/resource.py

@@ -11,11 +11,13 @@ from rdflib import Literal
 from rdflib.namespace import XSD
 
 from lakesuperior.config_parser import config
+from lakesuperior.dictionaries.namespaces import ns_collection as nsc
 from lakesuperior.exceptions import (
         InvalidResourceError, ResourceNotExistsError, TombstoneError)
 from lakesuperior import env, thread_env
 from lakesuperior.globals import RES_DELETED, RES_UPDATED
 from lakesuperior.model.ldp.ldp_factory import LDP_NR_TYPE, LdpFactory
+from lakesuperior.util.toolbox import rel_uri_to_urn
 
 
 logger = logging.getLogger(__name__)
@@ -230,7 +232,8 @@ def create_or_replace(uid, **kwargs):
     :rtype: tuple(str, lakesuperior.model.ldp.ldpr.Ldpr)
     :return: A tuple of:
         1. Event type (str): whether the resource was created or updated.
-        2. Resource (lakesuperior.model.ldp.ldpr.Ldpr): The new or updated resource.
+        2. Resource (lakesuperior.model.ldp.ldpr.Ldpr): The new or updated
+            resource.
     """
     rsrc = LdpFactory.from_provided(uid, **kwargs)
     return rsrc.create_or_replace(), rsrc
@@ -244,7 +247,7 @@ def update(uid, update_str, is_metadata=False, handling='strict'):
     :param string uid: Resource UID.
     :param string update_str: SPARQL-Update statements.
     :param bool is_metadata: Whether the resource metadata are being updated.
-    :param str handling: How to handle servre-managed triples. ``strict``
+    :param str handling: How to handle server-managed triples. ``strict``
         (the default) rejects the update with an exception if server-managed
         triples are being changed. ``lenient`` modifies the update graph so
         offending triples are removed and the update can be applied.
@@ -277,6 +280,16 @@ def update_delta(uid, remove_trp, add_trp):
         add, as 3-tuples of RDFLib terms.
     """
     rsrc = LdpFactory.from_stored(uid)
+
+    # FIXME Wrong place to put this, should be at the LDP level.
+    remove_trp = {
+        (rel_uri_to_urn(s, uid), p, rel_uri_to_urn(o, uid))
+        for s, p, o in remove_trp
+    }
+    add_trp = {
+        (rel_uri_to_urn(s, uid), p, rel_uri_to_urn(o, uid))
+        for s, p, o in add_trp
+    }
     remove_trp = rsrc.check_mgd_terms(remove_trp)
     add_trp = rsrc.check_mgd_terms(add_trp)
 

+ 9 - 3
lakesuperior/model/ldp/ldp_factory.py

@@ -16,6 +16,7 @@ from lakesuperior.exceptions import (
         IncompatibleLdpTypeError, InvalidResourceError, ResourceExistsError,
         ResourceNotExistsError, TombstoneError)
 from lakesuperior.model.rdf.graph import Graph, from_rdf
+from lakesuperior.util.toolbox import rel_uri_to_urn
 
 
 LDP_NR_TYPE = nsc['ldp'].NonRDFSource
@@ -100,11 +101,16 @@ class LdpFactory:
         uri = nsc['fcres'][uid]
         if rdf_data:
             provided_imr = from_rdf(
-                uri=uri, data=rdf_data, format=rdf_fmt,
-                publicID=nsc['fcres'][uid]
+                uri=uri, data=rdf_data,
+                format=rdf_fmt, publicID=nsc['fcres'][uid]
             )
         elif graph:
-            provided_imr = Graph(uri=uri, data={*graph})
+            provided_imr = Graph(
+                uri=uri, data={
+                    (rel_uri_to_urn(s, uid), p, rel_uri_to_urn(o, uid))
+                    for s, p, o in graph
+                }
+            )
         else:
             provided_imr = Graph(uri=uri)
 

+ 3 - 4
lakesuperior/model/ldp/ldpr.py

@@ -29,7 +29,8 @@ from lakesuperior.exceptions import (
     ServerManagedTermError, TombstoneError)
 from lakesuperior.model.rdf.graph import Graph
 from lakesuperior.store.ldp_rs.rsrc_centric_layout import VERS_CONT_LABEL
-from lakesuperior.util.toolbox import replace_term_domain
+from lakesuperior.util.toolbox import (
+        rel_uri_to_urn_string, replace_term_domain)
 
 DEF_MBR_REL_URI = nsc['ldp'].member
 DEF_INS_CNT_REL_URI = nsc['ldp'].memberSubject
@@ -674,9 +675,7 @@ class Ldpr(metaclass=ABCMeta):
         logger.debug('Provided SPARQL query: {}'.format(qry_str))
         # Workaround for RDFLib bug. See
         # https://github.com/RDFLib/rdflib/issues/824
-        qry_str = (
-                re.sub('<#([^>]+)>', '<{}#\\1>'.format(self.uri), qry_str)
-                .replace('<>', '<{}>'.format(self.uri)))
+        qry_str = rel_uri_to_urn_string(qry_str, self.uid)
         pre_gr = self.imr.as_rdflib()
         post_gr = rdflib.Graph(identifier=self.uri)
         post_gr |= pre_gr

+ 33 - 0
lakesuperior/util/toolbox.py

@@ -121,6 +121,39 @@ def split_uuid(uuid):
 
     return path
 
+def rel_uri_to_urn(uri, uid):
+    """
+    Convert a URIRef with a relative location (e.g. ``<>``) to an URN.
+
+    :param URIRef uri: The URI to convert.
+
+    :param str uid: Resource UID that the URI should be relative to.
+
+    :return: Converted URN if the input is relative, otherwise the unchanged
+        URI.
+    :rtype: URIRef
+    """
+    # FIXME This only accounts for empty URIs, not all relative URIs.
+    return nsc['fcres'][uid] if str(uri) == '' else uri
+    #return URIRef(
+    #        re.sub('<#([^>]+)>', f'<{base_uri}#\\1>', str(uri))
+    #        .replace('<>', f'<{base_uri}>'))
+
+
+def rel_uri_to_urn_string(string, uid):
+    """
+    Convert relative URIs in a SPARQL or RDF string to internal URNs.
+
+    :param str string: Input string.
+    :param str uid Resource UID to build the base URN from.
+
+    :rtype: str
+    :return: Modified string.
+    """
+    urn = str(nsc['fcres'][uid])
+    return (
+        re.sub('<#([^>]+)>', f'<{urn}#\\1>', string).replace('<>', f'<{urn}>')
+    )
 
 
 class RequestUtils:

+ 150 - 0
tests/2_api/test_2_0_resource_api.py

@@ -532,6 +532,156 @@ class TestResourceCRUD:
 
 
 
+@pytest.mark.usefixtures('db')
+class TestRelativeUris:
+    '''
+    Test inserting and updating resources with relative URIs.
+    '''
+    def test_create_self_uri_rdf(self):
+        """
+        Create a resource with empty string ("self") URIs in the RDF body.
+        """
+        uid = '/reluri01'
+        uri = nsc['fcres'][uid]
+        data = '''
+        <> a <urn:type:A> .
+        <http://ex.org/external> <urn:pred:x> <> .
+        '''
+        rsrc_api.create_or_replace(uid, rdf_data=data, rdf_fmt='ttl')
+        rsrc = rsrc_api.get(uid)
+
+        with env.app_globals.rdf_store.txn_ctx():
+            assert rsrc.imr[uri: nsc['rdf']['type']: URIRef('urn:type:A')]
+            assert rsrc.imr[
+                URIRef('http://ex.org/external'): URIRef('urn:pred:x'): uri]
+
+
+    def test_create_self_uri_graph(self):
+        """
+        Create a resource with empty string ("self") URIs in a RDFlib graph.
+        """
+        uid = '/reluri02'
+        uri = nsc['fcres'][uid]
+        gr = Graph()
+        with env.app_globals.rdf_store.txn_ctx():
+            gr.add({
+                (URIRef(''), nsc['rdf']['type'], URIRef('urn:type:A')),
+                (
+                    URIRef('http://ex.org/external'),
+                    URIRef('urn:pred:x'), URIRef('')
+                ),
+            })
+        rsrc_api.create_or_replace(uid, graph=gr)
+        rsrc = rsrc_api.get(uid)
+
+        with env.app_globals.rdf_store.txn_ctx():
+            assert rsrc.imr[uri: nsc['rdf']['type']: URIRef('urn:type:A')]
+            assert rsrc.imr[
+                URIRef('http://ex.org/external'): URIRef('urn:pred:x'): uri]
+
+
+    def test_create_hash_uri_rdf(self):
+        """
+        Create a resource with empty string ("self") URIs in the RDF body.
+        """
+        uid = '/reluri03'
+        uri = nsc['fcres'][uid]
+        data = '''
+        <#hash1> a <urn:type:A> .
+        <http://ex.org/external> <urn:pred:x> <#hash2> .
+        '''
+        rsrc_api.create_or_replace(uid, rdf_data=data, rdf_fmt='ttl')
+        rsrc = rsrc_api.get(uid)
+
+        with env.app_globals.rdf_store.txn_ctx():
+            assert rsrc.imr[
+                URIRef(str(uri) + '#hash1'): nsc['rdf'].type:
+                URIRef('urn:type:A')]
+            assert rsrc.imr[
+                URIRef('http://ex.org/external'): URIRef('urn:pred:x'):
+                URIRef(str(uri) + '#hash2')]
+
+
+    @pytest.mark.skip
+    def test_create_hash_uri_graph(self):
+        """
+        Create a resource with empty string ("self") URIs in a RDFlib graph.
+        """
+        uid = '/reluri04'
+        uri = nsc['fcres'][uid]
+        gr = Graph()
+        with env.app_globals.rdf_store.txn_ctx():
+            gr.add({
+                (URIRef('#hash1'), nsc['rdf']['type'], URIRef('urn:type:A')),
+                (
+                    URIRef('http://ex.org/external'),
+                    URIRef('urn:pred:x'), URIRef('#hash2')
+                )
+            })
+        rsrc_api.create_or_replace(uid, graph=gr)
+        rsrc = rsrc_api.get(uid)
+
+        with env.app_globals.rdf_store.txn_ctx():
+            assert rsrc.imr[
+                URIRef(str(uri) + '#hash1'): nsc['rdf']['type']:
+                URIRef('urn:type:A')]
+            assert rsrc.imr[
+                URIRef('http://ex.org/external'): URIRef('urn:pred:x'):
+                URIRef(str(uri) + '#hash2')]
+
+
+    @pytest.mark.skip(reason='RDFlib bug.')
+    def test_create_child_uri_rdf(self):
+        """
+        Create a resource with empty string ("self") URIs in the RDF body.
+        """
+        uid = '/reluri05'
+        uri = nsc['fcres'][uid]
+        data = '''
+        <child1> a <urn:type:A> .
+        <http://ex.org/external> <urn:pred:x> <child2> .
+        '''
+        rsrc_api.create_or_replace(uid, rdf_data=data, rdf_fmt='ttl')
+        rsrc = rsrc_api.get(uid)
+
+        with env.app_globals.rdf_store.txn_ctx():
+            assert rsrc.imr[
+                URIRef(str(uri) + '/child1'): nsc['rdf'].type:
+                URIRef('urn:type:A')]
+            assert rsrc.imr[
+                URIRef('http://ex.org/external'): URIRef('urn:pred:x'):
+                URIRef(str(uri) + '/child2')]
+
+
+    @pytest.mark.skip(reason='RDFlib bug.')
+    def test_create_child_uri_graph(self):
+        """
+        Create a resource with empty string ("self") URIs in the RDF body.
+        """
+        uid = '/reluri06'
+        uri = nsc['fcres'][uid]
+        gr = Graph()
+        with env.app_globals.rdf_store.txn_ctx():
+            gr.add({
+                (URIRef('child1'), nsc['rdf']['type'], URIRef('urn:type:A')),
+                (
+                    URIRef('http://ex.org/external'),
+                    URIRef('urn:pred:x'), URIRef('child22')
+                )
+            })
+        rsrc_api.create_or_replace(uid, graph=gr)
+        rsrc = rsrc_api.get(uid)
+
+        with env.app_globals.rdf_store.txn_ctx():
+            assert rsrc.imr[
+                URIRef(str(uri) + '/child1'): nsc['rdf'].type:
+                URIRef('urn:type:A')]
+            assert rsrc.imr[
+                URIRef('http://ex.org/external'): URIRef('urn:pred:x'):
+                URIRef(str(uri) + '/child2')]
+
+
+
 @pytest.mark.usefixtures('db')
 class TestAdvancedDelete:
     '''