diff options
-rw-r--r-- | bsfs/__init__.py | 15 | ||||
-rw-r--r-- | bsfs/front/__init__.py | 20 | ||||
-rw-r--r-- | bsfs/front/bsfs.py | 29 | ||||
-rw-r--r-- | bsfs/front/builder.py | 75 | ||||
-rw-r--r-- | test/front/__init__.py | 0 | ||||
-rw-r--r-- | test/front/test_bsfs.py | 38 | ||||
-rw-r--r-- | test/front/test_builder.py | 64 |
7 files changed, 241 insertions, 0 deletions
diff --git a/bsfs/__init__.py b/bsfs/__init__.py index f5f5cbc..079ffaf 100644 --- a/bsfs/__init__.py +++ b/bsfs/__init__.py @@ -4,5 +4,20 @@ Part of the BlackStar filesystem (bsfs) module. A copy of the license is provided with the project. Author: Matthias Baumgartner, 2022 """ +# imports +import collections +import typing + +# bsfs imports +from .front import Open + +# constants +T_VERSION_INFO = collections.namedtuple('T_VERSION_INFO', ('major', 'minor', 'micro')) # pylint: disable=invalid-name +version_info = T_VERSION_INFO(0, 0, 1) + +# exports +__all__: typing.Sequence[str] = ( + 'Open', + ) ## EOF ## diff --git a/bsfs/front/__init__.py b/bsfs/front/__init__.py new file mode 100644 index 0000000..92886ab --- /dev/null +++ b/bsfs/front/__init__.py @@ -0,0 +1,20 @@ +""" + +Part of the BlackStar filesystem (bsfs) module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import typing + +# inner-module imports +from .bsfs import Open +from .builder import build_graph + +# exports +__all__: typing.Sequence[str] = ( + 'Open', + 'build_graph', + ) + +## EOF ## diff --git a/bsfs/front/bsfs.py b/bsfs/front/bsfs.py new file mode 100644 index 0000000..968b3f5 --- /dev/null +++ b/bsfs/front/bsfs.py @@ -0,0 +1,29 @@ +""" + +Part of the BlackStar filesystem (bsfs) module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import typing + +# bsfs imports +from bsfs.graph import Graph + +# inner-module imports +from . import builder + +# exports +__all__: typing.Sequence[str] = ( + 'Open', + ) + + +## code ## + +# NOTE: Capitalized to mark entry point and to separate from builtin open. +def Open(cfg: typing.Any) -> Graph: # pylint: disable=invalid-name + """Open a BSFS storage and return a `bsfs.graph.Graph` instance.""" + return builder.build_graph(cfg) + +## EOF ## diff --git a/bsfs/front/builder.py b/bsfs/front/builder.py new file mode 100644 index 0000000..73f1703 --- /dev/null +++ b/bsfs/front/builder.py @@ -0,0 +1,75 @@ +""" + +Part of the BlackStar filesystem (bsfs) module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import typing + +# bsfs imports +from bsfs.graph import Graph +from bsfs.triple_store import TripleStoreBase, SparqlStore +from bsfs.utils import URI, errors + +# exports +__all__: typing.Sequence[str] = ( + 'build_graph', + ) + +# constants +_graph_classes = { + 'Graph': Graph, + } + +_backend_classes = { + 'SparqlStore': SparqlStore, + } + + +## code ## + +def build_backend(cfg: typing.Any) -> TripleStoreBase: + """Build and return a backend from user-provided config.""" + # essential checks + if not isinstance(cfg, dict): + raise TypeError(cfg) + if len(cfg) != 1: + raise errors.ConfigError(f'expected a single key that identifies the backend class, found {list(cfg)}') + # unpack from config + name = next(iter(cfg)) + args = cfg[name] + # check name + if name not in _backend_classes: + raise errors.ConfigError(f'{name} is not a valid triple store class name') + # build and return backend + cls = _backend_classes[name] + return cls.Open(**args) + + +def build_graph(cfg: typing.Any) -> Graph: + """Build and return a Graph from user-provided config.""" + # essential checks + if not isinstance(cfg, dict): + raise TypeError(cfg) + if len(cfg) != 1: + raise errors.ConfigError(f'expected a single key that identifies the graph class, found {list(cfg)}') + # unpack from config + name = next(iter(cfg)) + args = cfg[name] + # check name + if name not in _graph_classes: + raise errors.ConfigError(f'{name} is not a valid graph class name') + # check user argument + if 'user' not in args: + raise errors.ConfigError('required argument "user" is not provided') + user = URI(args['user']) + # check backend argument + if 'backend' not in args: + raise errors.ConfigError('required argument "backend" is not provided') + backend = build_backend(args['backend']) + # build and return graph + cls = _graph_classes[name] + return cls(backend, user) + +## EOF ## diff --git a/test/front/__init__.py b/test/front/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/front/__init__.py diff --git a/test/front/test_bsfs.py b/test/front/test_bsfs.py new file mode 100644 index 0000000..0d7f383 --- /dev/null +++ b/test/front/test_bsfs.py @@ -0,0 +1,38 @@ +""" + +Part of the bsfs test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import unittest + +# bsie imports +from bsfs.graph import Graph +from bsfs.triple_store import SparqlStore +from bsfs.utils import errors, URI + +# objects to test +from bsfs.front.bsfs import Open + + +## code ## + +class TestBSFS(unittest.TestCase): + def test_open(self): + # valid config produces a valid graph + config = {'Graph': {'backend': {'SparqlStore': {}}, 'user': 'http://example.com/me'}} + graph = Open(config) + self.assertIsInstance(graph, Graph) + self.assertIsInstance(graph._backend, SparqlStore) + self.assertEqual(graph._user, URI('http://example.com/me')) + # invalid config raises an error + self.assertRaises(errors.ConfigError, Open, {}) + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## diff --git a/test/front/test_builder.py b/test/front/test_builder.py new file mode 100644 index 0000000..08f2027 --- /dev/null +++ b/test/front/test_builder.py @@ -0,0 +1,64 @@ +""" + +Part of the bsfs test suite. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# imports +import unittest + +# bsie imports +from bsfs.graph import Graph +from bsfs.triple_store import SparqlStore +from bsfs.utils import errors, URI + +# objects to test +from bsfs.front.builder import build_backend, build_graph + + +## code ## + +class TestBuilder(unittest.TestCase): + def test_build_backend(self): + # valid config produces a valid store + store = build_backend({'SparqlStore': {}}) + self.assertIsInstance(store, SparqlStore) + self.assertIsNone(store.uri) + # cannot create an invalid store + self.assertRaises(errors.ConfigError, build_backend, {'MyStore': {}}) + # must pass a dict + self.assertRaises(TypeError, build_backend, 1234) + self.assertRaises(TypeError, build_backend, 'hello world') + self.assertRaises(TypeError, build_backend, [1,2,3]) + # cannot create a store from an invalid config + self.assertRaises(errors.ConfigError, build_backend, {}) + self.assertRaises(errors.ConfigError, build_backend, {'SparqlStore': {}, 'OtherStore': {}}) + self.assertRaises(TypeError, build_backend, {'SparqlStore': {'hello': 'world'}}) + + def test_build_graph(self): + # valid config produces a valid graph + graph = build_graph({'Graph': {'backend': {'SparqlStore': {}}, 'user': 'http://example.com/me'}}) + self.assertIsInstance(graph, Graph) + self.assertIsInstance(graph._backend, SparqlStore) + self.assertEqual(graph._user, URI('http://example.com/me')) + # cannot create an invalid graph + self.assertRaises(errors.ConfigError, build_graph, {'MyGraph': {}}) + # must pass a dict + self.assertRaises(TypeError, build_graph, 1234) + self.assertRaises(TypeError, build_graph, 'hello world') + self.assertRaises(TypeError, build_graph, [1,2,3]) + # cannot create a graph from an invalid config + self.assertRaises(errors.ConfigError, build_graph, {}) + self.assertRaises(errors.ConfigError, build_graph, {'Graph': {}, 'Graph2': {}}) + self.assertRaises(errors.ConfigError, build_graph, {'Graph': {}}) + self.assertRaises(errors.ConfigError, build_graph, {'Graph': {'user': 'http://example.com/me'}}) + self.assertRaises(errors.ConfigError, build_graph, {'Graph': {'backend': 'Hello world'}}) + self.assertRaises(TypeError, build_graph, {'Graph': {'user': 'http://example.com/me', 'backend': 'Hello world'}}) + + +## main ## + +if __name__ == '__main__': + unittest.main() + +## EOF ## |