瀏覽代碼

Move modules around.

* Rename lakesuperior.ldp to lakesuperior.model
* Rename lakesuperior.store_strategies to lakesuperior.store_layouts
Stefano Cossu 7 年之前
父節點
當前提交
9279520ff8

+ 2 - 2
etc.skeleton/application.yml

@@ -22,9 +22,9 @@ store:
     # The semantic store used for persisting LDP-RS (RDF Source) resources.
     # The semantic store used for persisting LDP-RS (RDF Source) resources.
     # MUST support SPARQL 1.1 query and update.
     # MUST support SPARQL 1.1 query and update.
     ldp_rs:
     ldp_rs:
-        # Store strategy. This corresponds to a sub-class of the
+        # Store layout. This corresponds to a sub-class of the
         # `lakesuperior.connectors.graph_store_connector.BaseGraphStoreConnector`.
         # `lakesuperior.connectors.graph_store_connector.BaseGraphStoreConnector`.
-        strategy: simple_strategy
+        layout: simple_layout
         webroot: http://localhost:9999/namespace/fcrepo/
         webroot: http://localhost:9999/namespace/fcrepo/
         query_ep: sparql
         query_ep: sparql
         update_ep: sparql
         update_ep: sparql

+ 1 - 1
lakesuperior/endpoints/ldp.py

@@ -1,6 +1,6 @@
 from flask import Blueprint, request
 from flask import Blueprint, request
 
 
-from lakesuperior.ldp.ldpr import Ldpr, Ldpc, LdpNr, \
+from lakesuperior.model.ldpr import Ldpr, Ldpc, LdpNr, \
         InvalidResourceError, ResourceNotExistsError
         InvalidResourceError, ResourceNotExistsError
 
 
 
 

+ 0 - 0
lakesuperior/ldp/fcrepo/README.md → lakesuperior/model/fcrepo/README.md


+ 18 - 0
lakesuperior/model/generic_resource.py

@@ -0,0 +1,18 @@
+from rdflib import Resource
+
+class GenericResource(Resource):
+    '''
+    Generic RDF resource that extends from rdflib.Resource.
+
+    This should also serve as the base class for LDP resource classes. Some
+    convenience methods missing in that class can also be added here.
+    '''
+
+    def extract(self, p=None, o=None):
+        '''
+        Extract an in-memory copy of the resource containing either a
+        sub-graph, defined with the `p` and `o` parameters, or the whole
+        resource.
+        '''
+        # @TODO
+        pass

+ 0 - 0
lakesuperior/ldp/lakesuperior/README.md → lakesuperior/model/lakesuperior/README.md


+ 13 - 13
lakesuperior/ldp/ldpr.py → lakesuperior/model/ldpr.py

@@ -122,7 +122,7 @@ class Ldpr(metaclass=ABCMeta):
     URIs are converted from URNs and passed by these methods to the methods
     URIs are converted from URNs and passed by these methods to the methods
     handling HTTP negotiation.
     handling HTTP negotiation.
 
 
-    The data passed to the store strategy for processing should be in a graph.
+    The data passed to the store layout for processing should be in a graph.
     All conversion from request payload strings is done here.
     All conversion from request payload strings is done here.
     '''
     '''
 
 
@@ -132,7 +132,7 @@ class Ldpr(metaclass=ABCMeta):
 
 
     _logger = logging.getLogger(__module__)
     _logger = logging.getLogger(__module__)
 
 
-    store_strategy = config['application']['store']['ldp_rs']['strategy']
+    store_layout = config['application']['store']['ldp_rs']['layout']
 
 
     ## MAGIC METHODS ##
     ## MAGIC METHODS ##
 
 
@@ -141,22 +141,22 @@ class Ldpr(metaclass=ABCMeta):
         persisted to storage.
         persisted to storage.
 
 
         Persistence is done in this class. None of the operations in the store
         Persistence is done in this class. None of the operations in the store
-        strategy should commit an open transaction. Methods are wrapped in a
+        layout should commit an open transaction. Methods are wrapped in a
         transaction by using the `@transactional` decorator.
         transaction by using the `@transactional` decorator.
 
 
         @param uuid (string) UUID of the resource.
         @param uuid (string) UUID of the resource.
         '''
         '''
         self.uuid = uuid
         self.uuid = uuid
 
 
-        # Dynamically load the store strategy indicated in the configuration.
+        # Dynamically load the store layout indicated in the configuration.
         store_mod = import_module(
         store_mod = import_module(
-                'lakesuperior.store_strategies.rdf.{}'.format(
-                        self.store_strategy))
+                'lakesuperior.store_layouts.rdf.{}'.format(
+                        self.store_layout))
         self._rdf_store_cls = getattr(store_mod, Translator.camelcase(
         self._rdf_store_cls = getattr(store_mod, Translator.camelcase(
-                self.store_strategy))
+                self.store_layout))
         self.gs = self._rdf_store_cls(self.urn)
         self.gs = self._rdf_store_cls(self.urn)
 
 
-        # Same thing coud be done for the filesystem store strategy, but we
+        # Same thing coud be done for the filesystem store layout, but we
         # will keep it simple for now.
         # will keep it simple for now.
         self.fs = FilesystemConnector()
         self.fs = FilesystemConnector()
 
 
@@ -188,7 +188,7 @@ class Ldpr(metaclass=ABCMeta):
         '''
         '''
         The RDFLib resource representing this LDPR. This is a copy of the
         The RDFLib resource representing this LDPR. This is a copy of the
         stored data if present, and what gets passed to most methods of the
         stored data if present, and what gets passed to most methods of the
-        store strategy methods.
+        store layout methods.
 
 
         @return rdflib.resource.Resource
         @return rdflib.resource.Resource
         '''
         '''
@@ -302,14 +302,14 @@ class Ldpr(metaclass=ABCMeta):
     @classmethod
     @classmethod
     def load_gs_static(cls, uuid=None):
     def load_gs_static(cls, uuid=None):
         '''
         '''
-        Dynamically load the store strategy indicated in the configuration.
+        Dynamically load the store layout indicated in the configuration.
         This essentially replicates the init() code in a static context.
         This essentially replicates the init() code in a static context.
         '''
         '''
         store_mod = import_module(
         store_mod = import_module(
-                'lakesuperior.store_strategies.rdf.{}'.format(
-                        cls.store_strategy))
+                'lakesuperior.store_layouts.rdf.{}'.format(
+                        cls.store_layout))
         rdf_store_cls = getattr(store_mod, Translator.camelcase(
         rdf_store_cls = getattr(store_mod, Translator.camelcase(
-                cls.store_strategy))
+                cls.store_layout))
         return rdf_store_cls(uuid)
         return rdf_store_cls(uuid)
 
 
 
 

+ 12 - 12
lakesuperior/store_strategies/rdf/base_rdf_strategy.py → lakesuperior/store_layouts/rdf/base_rdf_layout.py

@@ -27,26 +27,26 @@ def needs_rsrc(fn):
 
 
 
 
 
 
-class BaseRdfStrategy(metaclass=ABCMeta):
+class BaseRdfLayout(metaclass=ABCMeta):
     '''
     '''
-    This class exposes an interface to build graph store strategies.
+    This class exposes an interface to build graph store layouts.
 
 
-    Some store strategies are provided. New ones aimed at specific uses
+    Some store layouts are provided. New ones aimed at specific uses
     and optimizations of the repository may be developed by extending this
     and optimizations of the repository may be developed by extending this
     class and implementing all its abstract methods.
     class and implementing all its abstract methods.
 
 
-    A strategy is implemented via application configuration. However, once
-    contents are ingested in a repository, changing a strategy will most likely
+    A layout is implemented via application configuration. However, once
+    contents are ingested in a repository, changing a layout will most likely
     require a migration.
     require a migration.
 
 
-    The custom strategy must be in the lakesuperior.store_strategies.rdf
-    package and the class implementing the strategy must be called
-    `StoreStrategy`. The module name is the one defined in the app
+    The custom layout must be in the lakesuperior.store_layouts.rdf
+    package and the class implementing the layout must be called
+    `StoreLayout`. The module name is the one defined in the app
     configuration.
     configuration.
 
 
-    E.g. if the configuration indicates `simple_strategy` the application will
+    E.g. if the configuration indicates `simple_layout` the application will
     look for
     look for
-    `lakesuperior.store_strategies.rdf.simple_strategy.SimpleStrategy`.
+    `lakesuperior.store_layouts.rdf.simple_layout.SimpleLayout`.
 
 
     Some method naming conventions:
     Some method naming conventions:
 
 
@@ -67,7 +67,7 @@ class BaseRdfStrategy(metaclass=ABCMeta):
 
 
     def __init__(self, urn=None):
     def __init__(self, urn=None):
         '''
         '''
-        The strategy can be initialized with a URN to make resource-centric
+        The layout can be initialized with a URN to make resource-centric
         operations simpler. However, for generic queries, urn can be None and
         operations simpler. However, for generic queries, urn can be None and
         no `self.rsrc` is assigned. In this case, some methods will not be
         no `self.rsrc` is assigned. In this case, some methods will not be
         available.
         available.
@@ -121,7 +121,7 @@ class BaseRdfStrategy(metaclass=ABCMeta):
         '''
         '''
         Graph obtained by querying the triplestore and adding any abstraction
         Graph obtained by querying the triplestore and adding any abstraction
         and filtering to make up a graph that can be used for read-only,
         and filtering to make up a graph that can be used for read-only,
-        API-facing results. Different strategies can implement this in very
+        API-facing results. Different layouts can implement this in very
         different ways, so it is an abstract method.
         different ways, so it is an abstract method.
         '''
         '''
         pass
         pass

+ 170 - 0
lakesuperior/store_layouts/rdf/full_provenance_layout.py

@@ -0,0 +1,170 @@
+import arrow
+
+from uuid import uuid4
+
+from rdflib import Dataset, Graph
+from rdflib.namespace import FOAF, RDF, XSD
+from rdflib.plugins.sparql import prepareQuery
+from rdflib.plugins.stores.sparqlstore import SPARQLUpdateStore
+from rdflib.term import URIRef, Literal
+
+from lakesuperior.core.namespaces import ns_collection as nsc
+from lakesuperior.core.namespaces import ns_mgr as nsm
+from lakesuperior.store_layouts.rdf.base_rdf_layout import BaseRdfLayout
+
+
+class FullProvenanceLayout(BaseRdfLayout):
+    '''This is an implementation of the
+    [graph-per-resource pattern](http://patterns.dataincubator.org/book/graph-per-resource.html)
+    which stores each LDP resource in a separate graph, with a "main" graph
+    to keep track of resource metadata.
+    '''
+
+    DEFAULT_AGENT_URI = nsc['lake'].defaultAgent
+    MAIN_GRAPH_URI = nsc['fcg'].meta
+
+
+    ## MAGIC METHODS ##
+
+    def __init__(self):
+        self.main_graph = self.ds.graph(self.MAIN_GRAPH_URI)
+
+
+    ## PUBLIC METHODS ##
+
+    def ask_rsrc_exists(self, uuid):
+        '''Return whether the resource exists.
+
+        @param uuid Resource UUID.
+
+        @retrn boolean
+        '''
+        res = self.ds.graph(self.UNION_GRAPH_URI).resource(nsc['fcres'][uuid])
+
+        return len(res) > 0
+
+
+    def get_rsrc(self, uuid):
+        '''Get a resource graph.
+        '''
+        res = self.ds.graph(self.UNION_GRAPH_URI).query(
+            'CONSTRUCT WHERE { ?s ?p ?o }',
+            initBindings={'s' : nsc['fcres'][uuid]}
+        )
+
+        return self.globalize_graph(res.graph)
+
+
+    def put_rsrc(self, uuid, data, format='text/turtle', base_types=None,
+            agent=None):
+        '''Create a resource graph.
+
+        If the resource UUID exists already, it is either overwritten or a
+        version snapshot is created, depending on the parameters.
+        '''
+        if agent is None:
+            agent = self.DEFAULT_AGENT_URI
+
+        res_urn = nsc['fcres'][uuid]
+
+        # If there is a statement by this agent about this resource, replace
+        # its contents.
+        if self._get_res_stmt_by_agent(res_urn, agent):
+            pass # @TODO
+
+
+        # If the graph URI does not exist, create a new resource.
+        else:
+            # Create a new UUID for the statement set.
+            stmset_uri = nsc['stmset'][str(uuid4())]
+
+            # Create a temp graph to store the loaded data. For some reason,
+            # loading directly into the stored graph throws an assertion error.
+            tmp_g = Graph()
+            tmp_g.parse(data=data.decode('utf-8'), format=format,
+                    publicID=str(res_urn))
+
+            # Create the graph and add the data.
+            g = self.ds.graph(stmset_uri)
+            g += tmp_g
+
+            # Add metadata.
+            ts = arrow.utcnow()
+            main_graph = self.ds.graph(self.MAIN_GRAPH_URI)
+
+            main_graph.add((stmset_uri, FOAF.primaryTopic, res_urn))
+            main_graph.add((stmset_uri, RDF.type, nsc['prov'].Entity))
+            main_graph.add(
+                    (stmset_uri, nsc['prov'].generatedAtTime,
+                    Literal(ts, datatype=XSD.dateTime)))
+            main_graph.add(
+                    (stmset_uri, nsc['prov'].wasAttributedTo, agent))
+
+
+        #self.create_version(res_urn)
+
+        if base_types:
+            for type_uri in self.base_types:
+                main_graph.add((stmset_uri, RDF.type, type_uri))
+
+        # @TODO Create containment triples
+
+        self.conn.store.commit()
+
+
+
+    #def create_version(self, res_urn):
+    #    '''Swap out previous version if existing, and create new version
+    #    dependency.'''
+    #    main_graph = ds.graph(URIRef('urn:lake:' + self.MAIN_GRAPH_NAME))
+    #    prv_res_urn = self.select_current_graph_for_res(res_urn)
+
+    #    if prv_res_urn:
+    #        main_graph.remove((prv_res_urn, RDF.type, nsc['lake'].Resource))
+    #        main_graph.add((prv_res_urn, RDF.type, nsc['lake'].Snapshot))
+
+    #        main_graph.add((res_urn, RDF.type, nsc['lake'].Resource))
+    #        main_graph.add((res_urn, nsc['lake'].previousVersion, prv_res_urn))
+
+
+    #def select_current_graph_for_res(self, urn):
+    #    '''Select the current graph URI for a given resource.'''
+    #    qry = '''
+    #    SELECT ?g {
+    #      GRAPH ?mg { ?g a ?gt . }
+    #      GRAPH ?g { ?s ?p ?o . }
+    #    }
+    #    LIMIT 1
+    #    '''
+    #    rsp = self.ds.query(qry, initBindings={
+    #        'mg' : URIRef('urn:lake:' + self.MAIN_GRAPH_NAME),
+    #        'gt' : RESOURCE_TYPE_URI,
+    #        's' : urn
+    #    })
+
+    #    return list(rsp[0][0])
+
+
+    def _ask_res_stmt_by_agent_exists(self, res_urn, agent):
+        '''Ask if any statements have been made by a certain agent about a
+        certain resource.
+
+        @param rdflib.term.URIRef res_urn Resource URN.
+        @param rdflib.term.URIRef agent Agent URI.
+
+        @return boolean
+        '''
+        return self.query('''
+        ASK {
+          GRAPH ?mg {
+              ?g prov:wasAttributedTo ?a .
+          }
+          GRAPH ?g {
+              ?s ?p ?o .
+          }
+        }
+        ''', initBindings={
+            'a' : agent,
+            's' : res_urn,
+        })
+

+ 8 - 8
lakesuperior/store_strategies/rdf/simple_strategy.py → lakesuperior/store_layouts/rdf/simple_layout.py

@@ -9,13 +9,13 @@ from rdflib.term import Literal, URIRef, Variable
 
 
 from lakesuperior.core.namespaces import ns_collection as nsc
 from lakesuperior.core.namespaces import ns_collection as nsc
 from lakesuperior.core.namespaces import ns_mgr as nsm
 from lakesuperior.core.namespaces import ns_mgr as nsm
-from lakesuperior.store_strategies.rdf.base_rdf_strategy import BaseRdfStrategy
+from lakesuperior.store_layouts.rdf.base_rdf_layout import BaseRdfLayout
 from lakesuperior.util.digest import Digest
 from lakesuperior.util.digest import Digest
 
 
 
 
-class SimpleStrategy(BaseRdfStrategy):
+class SimpleLayout(BaseRdfLayout):
     '''
     '''
-    This is the simplest strategy.
+    This is the simplest layout.
 
 
     It uses a flat triple structure without named graphs aimed at performance.
     It uses a flat triple structure without named graphs aimed at performance.
 
 
@@ -28,7 +28,7 @@ class SimpleStrategy(BaseRdfStrategy):
     @property
     @property
     def headers(self):
     def headers(self):
         '''
         '''
-        See base_rdf_strategy.headers.
+        See base_rdf_layout.headers.
         '''
         '''
         headers = {
         headers = {
             'Link' : [],
             'Link' : [],
@@ -51,7 +51,7 @@ class SimpleStrategy(BaseRdfStrategy):
 
 
     def out_graph(self, srv_mgd=True, inbound=False, embed_children=False):
     def out_graph(self, srv_mgd=True, inbound=False, embed_children=False):
         '''
         '''
-        See base_rdf_strategy.out_graph.
+        See base_rdf_layout.out_graph.
         '''
         '''
         inbound_qry = '\n?s1 ?p1 {}'.format(self.base_urn.n3()) \
         inbound_qry = '\n?s1 ?p1 {}'.format(self.base_urn.n3()) \
                 if inbound else ''
                 if inbound else ''
@@ -71,7 +71,7 @@ class SimpleStrategy(BaseRdfStrategy):
 
 
     def ask_rsrc_exists(self, rsrc=None):
     def ask_rsrc_exists(self, rsrc=None):
         '''
         '''
-        See base_rdf_strategy.ask_rsrc_exists.
+        See base_rdf_layout.ask_rsrc_exists.
         '''
         '''
         if not rsrc:
         if not rsrc:
             if self.rsrc is not None:
             if self.rsrc is not None:
@@ -86,7 +86,7 @@ class SimpleStrategy(BaseRdfStrategy):
 
 
     def create_or_replace_rsrc(self, g):
     def create_or_replace_rsrc(self, g):
         '''
         '''
-        See base_rdf_strategy.create_or_replace_rsrc.
+        See base_rdf_layout.create_or_replace_rsrc.
         '''
         '''
         # @TODO Use gunicorn to get request timestamp.
         # @TODO Use gunicorn to get request timestamp.
         ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
         ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
@@ -117,7 +117,7 @@ class SimpleStrategy(BaseRdfStrategy):
 
 
     def create_rsrc(self, g):
     def create_rsrc(self, g):
         '''
         '''
-        See base_rdf_strategy.create_rsrc.
+        See base_rdf_layout.create_rsrc.
         '''
         '''
         # @TODO Use gunicorn to get request timestamp.
         # @TODO Use gunicorn to get request timestamp.
         ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)
         ts = Literal(arrow.utcnow(), datatype=XSD.dateTime)