Browse Source

Refactor triplestore connector to accommodate setup & teardown of test
fixtures.

Stefano Cossu 6 years ago
parent
commit
f6d5233884

+ 28 - 2
conftest.py

@@ -1,15 +1,41 @@
 import sys
 sys.path.append('.')
+
 import uuid
 
 import pytest
 
 from lakesuperior.app import create_app
 from lakesuperior.config_parser import config
+from lakesuperior.store_layouts.rdf.graph_store_connector import \
+        GraphStoreConnector
 
 
-@pytest.fixture
+@pytest.fixture(scope='module')
 def app():
     app = create_app(config['test'], config['logging'])
 
-    return app
+    yield app
+
+
+@pytest.fixture(scope='module')
+def db(app):
+    '''
+    Set up and tear down test triplestore.
+    '''
+    dbconf = app.config['store']['ldp_rs']
+    db = GraphStoreConnector(
+            query_ep=dbconf['webroot'] + dbconf['query_ep'],
+            update_ep=dbconf['webroot'] + dbconf['update_ep'])
+
+    db.ds.default_context.parse(source='data/bootstrap/simple_layout.nq',
+            format='nquads')
+    db.store.commit()
+
+    yield db
+
+    print('Tearing down fixure graph store.')
+    for g in db.ds.graphs():
+        db.ds.remove_graph(g)
+    db.store.commit()
+

+ 9 - 0
data/bootstrap/simple_layout.nq

@@ -0,0 +1,9 @@
+# This needs to be in N-Quads format because of
+# https://github.com/RDFLib/rdflib/issues/436
+
+<urn:fcsystem:root> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/ns/ldp#RDFSource> .
+<urn:fcsystem:root> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/ns/ldp#Container> .
+<urn:fcsystem:root> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/ns/ldp#BasicContainer> .
+<urn:fcsystem:root> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://fedora.info/definitions/v4/repository#RepositoryRoot> .
+<urn:fcsystem:root> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://fedora.info/definitions/v4/repository#Resource> .
+<urn:fcsystem:root> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://fedora.info/definitions/v4/repository#Container> .

+ 8 - 6
data/bootstrap/simple_layout.trig

@@ -1,9 +1,11 @@
 # Bootstrap data for simple RDF store layout.
 
-PREFIX ldp: <http://www.w3.org/ns/ldp#>
-PREFIX fcrepo: <http://fedora.info/definitions/v4/repository#>
+@prefix ldp: <http://www.w3.org/ns/ldp#> .
+@prefix fcrepo: <http://fedora.info/definitions/v4/repository#> .
 
-<urn:fcsystem:root>
-  a ldp:RDFSource , ldp:Container , ldp:BasicContainer ,
-    fcrepo:RepositoryRoot , fcrepo:Resource , fcrepo:Container ;
-  .
+{
+  <urn:fcsystem:root>
+    a ldp:RDFSource , ldp:Container , ldp:BasicContainer ,
+      fcrepo:RepositoryRoot , fcrepo:Resource , fcrepo:Container ;
+    .
+}

+ 1 - 0
doc/notes/TODO

@@ -21,6 +21,7 @@
   - [D] Framework + config
   - [W] HTTP layer
   - [ ] Unit tests
+- [W] Bootstrap
 - [ ] Optimize queries
 - [ ] Messaging SPI
 - [ ] Hook up Hyrax

+ 22 - 0
lakesuperior/config_parser.py

@@ -1,4 +1,5 @@
 import os
+import sys
 
 import hiyapyco
 import yaml
@@ -25,5 +26,26 @@ for cname in configs:
         config[cname] = yaml.load(stream, yaml.SafeLoader)
 
 # Merge default and test configurations.
+error_msg = '''
+**************
+** WARNING! **
+**************
+
+Your test {} store endpoint is set to be the same as the production endpoint.
+This means that if you run a test suite, your live data may be wiped clean!
+
+Please review your configuration before starting.
+'''
+
 config['test'] = hiyapyco.load(CONFIG_DIR + '/application.yml',
         CONFIG_DIR + '/test.yml', method=hiyapyco.METHOD_MERGE)
+
+if config['application']['store']['ldp_rs']['webroot'] == \
+        config['test']['store']['ldp_rs']['webroot']:
+            raise RuntimeError(error_msg.format('RDF'))
+            sys.exit()
+
+if config['application']['store']['ldp_nr']['path'] == \
+        config['test']['store']['ldp_nr']['path']:
+            raise RuntimeError(error_msg.format('binary'))
+            sys.exit()

+ 0 - 1
lakesuperior/model/ldp_nr.py

@@ -3,7 +3,6 @@ from rdflib.namespace import RDF, XSD
 from rdflib.resource import Resource
 from rdflib.term import URIRef, Literal, Variable
 
-from lakesuperior.config_parser import config
 from lakesuperior.dictionaries.namespaces import ns_collection as nsc
 from lakesuperior.model.ldpr import Ldpr, transactional, must_exist
 from lakesuperior.model.ldp_rs import LdpRs

+ 2 - 2
lakesuperior/model/ldp_rs.py

@@ -2,7 +2,7 @@ from copy import deepcopy
 
 import arrow
 
-from flask import request
+from flask import current_app
 from rdflib import Graph
 from rdflib.resource import Resource
 from rdflib.namespace import RDF, XSD
@@ -236,7 +236,7 @@ class LdpRs(Ldpr):
         g = self.provided_imr.graph
 
         for o in g.objects():
-            if isinstance(o, URIRef) and str(o).startswith(request.host_url) \
+            if isinstance(o, URIRef) and str(o).startswith(Toolbox().base_url)\
                     and not self.rdfly.ask_rsrc_exists(o):
                 if config == 'strict':
                     raise RefIntViolationError(o)

+ 7 - 6
lakesuperior/model/ldpr.py

@@ -8,11 +8,11 @@ from uuid import uuid4
 
 import arrow
 
+from flask import current_app
 from rdflib import Graph
 from rdflib.resource import Resource
 from rdflib.namespace import RDF, XSD
 
-from lakesuperior.config_parser import config
 from lakesuperior.dictionaries.namespaces import ns_collection as nsc
 from lakesuperior.exceptions import InvalidResourceError, \
         ResourceNotExistsError, ServerManagedTermError
@@ -106,8 +106,6 @@ class Ldpr(metaclass=ABCMeta):
 
     _logger = logging.getLogger(__name__)
 
-    rdf_store_layout = config['application']['store']['ldp_rs']['layout']
-    non_rdf_store_layout = config['application']['store']['ldp_nr']['layout']
 
     ## MAGIC METHODS ##
 
@@ -122,6 +120,10 @@ class Ldpr(metaclass=ABCMeta):
         @param uuid (string) UUID of the resource. If None (must be explicitly
         set) it refers to the root node.
         '''
+        self.rdf_store_layout = current_app.config['store']['ldp_rs']['layout']
+        self.non_rdf_store_layout = \
+                current_app.config['store']['ldp_nr']['layout']
+
         self.uuid = uuid
 
         self._urn = nsc['fcres'][uuid] if self.uuid is not None \
@@ -305,15 +307,14 @@ class Ldpr(metaclass=ABCMeta):
     ## STATIC & CLASS METHODS ##
 
     @classmethod
-    def load_layout(cls, type, uuid=None):
+    def load_layout(cls, type):
         '''
         Dynamically load the store layout indicated in the configuration.
 
         @param type (string) One of `rdf` or `non_rdf`. Determines the type of
         layout to be loaded.
-        @param uuid (string) UUID of the base resource. For RDF layouts only.
         '''
-        layout_cls = getattr(cls, '{}_store_layout'.format(type))
+        layout_cls = getattr(cls(None), '{}_store_layout'.format(type))
         store_mod = import_module('lakesuperior.store_layouts.{0}.{1}'.format(
                 type, layout_cls))
         layout_cls = getattr(store_mod, Toolbox().camelcase(layout_cls))

+ 3 - 3
lakesuperior/store_layouts/non_rdf/base_non_rdf_layout.py

@@ -2,7 +2,7 @@ import logging
 
 from abc import ABCMeta, abstractmethod
 
-from lakesuperior.config_parser import config
+from flask import current_app
 
 
 class BaseNonRdfLayout(metaclass=ABCMeta):
@@ -14,7 +14,6 @@ class BaseNonRdfLayout(metaclass=ABCMeta):
     traditional filesystem—e.g. a layout persisting to HDFS can be written too.
     '''
 
-    _conf = config['application']['store']['ldp_nr']
     _logger = logging.getLogger(__name__)
 
 
@@ -22,7 +21,8 @@ class BaseNonRdfLayout(metaclass=ABCMeta):
         '''
         Initialize the base non-RDF store layout.
         '''
-        self.root = self._conf['path']
+        self.conf = current_app.config['store']['ldp_nr']
+        self.root = self.conf['path']
 
 
     ## INTERFACE METHODS ##

+ 2 - 2
lakesuperior/store_layouts/non_rdf/default_layout.py

@@ -79,8 +79,8 @@ class DefaultLayout(BaseNonRdfLayout):
         checksum.
         '''
         self._logger.debug('Generating path from uuid: {}'.format(uuid))
-        bl = self._conf['pairtree_branch_length']
-        bc = self._conf['pairtree_branches']
+        bl = self.conf['pairtree_branch_length']
+        bc = self.conf['pairtree_branches']
         term = len(uuid) if bc==0 else min(bc*bl, len(uuid))
 
         path = [ uuid[i:i+bl] for i in range(0, term, bl) ]

+ 1 - 1
lakesuperior/store_layouts/rdf/base_rdf_layout.py

@@ -69,7 +69,7 @@ class BaseRdfLayout(metaclass=ABCMeta):
         '''
         self.conf = current_app.config['store']['ldp_rs']
         self._conn = GraphStoreConnector(
-                self.conf['webroot'] + self.conf['query_ep'],
+                query_ep=self.conf['webroot'] + self.conf['query_ep'],
                 update_ep=self.conf['webroot'] + self.conf['update_ep'])
 
 

+ 8 - 3
lakesuperior/store_layouts/rdf/graph_store_connector.py

@@ -17,13 +17,18 @@ class GraphStoreConnector:
 
     _logger = logging.getLogger(__name__)
 
-    def __init__(self, query_ep, update_ep=None):
+    def __init__(self, query_ep, update_ep=None, autocommit=False):
+        '''
+        Initialize the connection to the SPARQL endpoint.
+
+        If `update_ep` is not specified, the store is initialized as read-only.
+        '''
         if update_ep:
             self.store = SPARQLUpdateStore(
                     queryEndpoint=query_ep,
                     update_endpoint=update_ep,
-                    autocommit=False,
-                    dirty_reads=True)
+                    autocommit=autocommit,
+                    dirty_reads=not autocommit)
 
             self.readonly = False
         else:

+ 1 - 1
server.py

@@ -4,7 +4,7 @@ from lakesuperior.app import create_app
 from lakesuperior.config_parser import config
 
 
-fcrepo = create_app(config['flask'], config['logging'])
+fcrepo = create_app(config['application'], config['logging'])
 
 
 ## GENERIC ROUTES ##