aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.pylintrc13
-rw-r--r--CHANGELOG.md28
-rw-r--r--MANIFEST.in1
-rw-r--r--README57
-rw-r--r--README.md59
-rwxr-xr-xbsfs.app52
-rw-r--r--bsfs.toml11
-rw-r--r--bsfs/__init__.py5
-rw-r--r--bsfs/apps/__init__.py43
-rw-r--r--bsfs/apps/init.py11
-rw-r--r--bsfs/apps/migrate.py6
-rw-r--r--bsfs/front/__init__.py5
-rw-r--r--bsfs/front/bsfs.py5
-rw-r--r--bsfs/front/builder.py11
-rw-r--r--bsfs/graph/__init__.py5
-rw-r--r--bsfs/graph/ac/__init__.py5
-rw-r--r--bsfs/graph/ac/base.py27
-rw-r--r--bsfs/graph/ac/null.py13
-rw-r--r--bsfs/graph/graph.py100
-rw-r--r--bsfs/graph/nodes.py113
-rw-r--r--bsfs/graph/resolve.py27
-rw-r--r--bsfs/graph/result.py5
-rw-r--r--bsfs/graph/schema.nt13
-rw-r--r--bsfs/graph/walk.py5
-rw-r--r--bsfs/namespace/__init__.py8
-rw-r--r--bsfs/namespace/namespace.py102
-rw-r--r--bsfs/namespace/predefined.py32
-rw-r--r--bsfs/query/__init__.py5
-rw-r--r--bsfs/query/ast/__init__.py3
-rw-r--r--bsfs/query/ast/fetch.py5
-rw-r--r--bsfs/query/ast/filter_.py6
-rw-r--r--bsfs/query/matcher.py9
-rw-r--r--bsfs/query/validator.py21
-rw-r--r--bsfs/schema/__init__.py5
-rw-r--r--bsfs/schema/schema.py5
-rw-r--r--bsfs/schema/serialize.py20
-rw-r--r--bsfs/schema/types.py21
-rw-r--r--bsfs/triple_store/__init__.py5
-rw-r--r--bsfs/triple_store/base.py5
-rw-r--r--bsfs/triple_store/sparql/__init__.py5
-rw-r--r--bsfs/triple_store/sparql/distance.py11
-rw-r--r--bsfs/triple_store/sparql/parse_fetch.py5
-rw-r--r--bsfs/triple_store/sparql/parse_filter.py13
-rw-r--r--bsfs/triple_store/sparql/sparql.py15
-rw-r--r--bsfs/triple_store/sparql/utils.py8
-rw-r--r--bsfs/utils/__init__.py5
-rw-r--r--bsfs/utils/commons.py5
-rw-r--r--bsfs/utils/errors.py5
-rw-r--r--bsfs/utils/uri.py13
-rw-r--r--bsfs/utils/uuid.py5
-rw-r--r--doc/Makefile20
-rw-r--r--doc/make.bat35
-rw-r--r--doc/source/_static/arch_dark.pngbin0 -> 27346 bytes
-rw-r--r--doc/source/_static/arch_dark.svg500
-rw-r--r--doc/source/_static/arch_light.pngbin0 -> 17509 bytes
-rw-r--r--doc/source/_static/arch_light.svg499
-rw-r--r--doc/source/architecture.rst87
-rw-r--r--doc/source/concepts.rst98
-rw-r--r--doc/source/conf.py37
-rw-r--r--doc/source/index.rst75
-rw-r--r--doc/source/installation.rst43
-rw-r--r--setup.py44
-rw-r--r--test/apps/schema-1.nt4
-rw-r--r--test/apps/schema-2.nt4
-rw-r--r--test/apps/test_init.py5
-rw-r--r--test/apps/test_main.py37
-rw-r--r--test/apps/test_migrate.py5
-rw-r--r--test/front/test_bsfs.py8
-rw-r--r--test/front/test_builder.py8
-rw-r--r--test/graph/ac/test_base.py78
-rw-r--r--test/graph/ac/test_null.py65
-rw-r--r--test/graph/test_graph.py216
-rw-r--r--test/graph/test_nodes.py335
-rw-r--r--test/graph/test_resolve.py28
-rw-r--r--test/graph/test_result.py7
-rw-r--r--test/graph/test_walk.py57
-rw-r--r--test/namespace/test_namespace.py131
-rw-r--r--test/query/ast_test/test_fetch.py5
-rw-r--r--test/query/ast_test/test_filter_.py7
-rw-r--r--test/query/test_matcher.py5
-rw-r--r--test/query/test_validator.py34
-rw-r--r--test/schema/test_schema.py18
-rw-r--r--test/schema/test_serialize.py340
-rw-r--r--test/schema/test_types.py7
-rw-r--r--test/triple_store/sparql/test_distance.py5
-rw-r--r--test/triple_store/sparql/test_parse_fetch.py100
-rw-r--r--test/triple_store/sparql/test_parse_filter.py46
-rw-r--r--test/triple_store/sparql/test_sparql.py115
-rw-r--r--test/triple_store/sparql/test_utils.py29
-rw-r--r--test/triple_store/test_base.py5
-rw-r--r--test/utils/test_commons.py5
-rw-r--r--test/utils/test_uri.py24
-rw-r--r--test/utils/test_uuid.py5
94 files changed, 2795 insertions, 1344 deletions
diff --git a/.gitignore b/.gitignore
index ba88570..c32d36b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ build/
# doc builds
doc/build/
+doc/source/api
# doc extra files
diff --git a/.pylintrc b/.pylintrc
index 6b7f471..418a728 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -144,6 +144,19 @@ allow-wildcard-with-all=no
logging-format-style=old
+[MESSAGES CONTROL]
+
+# disable similarities check
+disable=raw-checker-failed,
+ bad-inline-option,
+ locally-disabled,
+ file-ignored,
+ suppressed-message,
+ useless-suppression,
+ deprecated-pragma,
+ use-symbolic-message-instead,
+ duplicate-code
+
[MISCELLANEOUS]
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..fb66c1e
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,28 @@
+
+# Changelog
+
+## 0.23.03 (Initial release)
+
+### Added
+
+- File graph protocol
+ - Graph access and navigation
+ - Syntactic sugar
+ - Fetch result shortcuts
+- Filter and Fetch Queries
+ - Syntax trees
+ - Validation
+ - Matching
+- Infrastructure to Open a storage
+- Storage schema
+- Backend
+ - Basic interface
+ - Sparql triple store: Manage triples via rdflib and sparql.
+- Access controls
+ - Basic interface
+ - NullAC: A dummy access control mechanism.
+- Essential utilities
+ - URI
+ - uuid
+ - namespaces
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..a06c41c
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+include bsfs/graph/schema.nt
diff --git a/README b/README
deleted file mode 100644
index da066f6..0000000
--- a/README
+++ /dev/null
@@ -1,57 +0,0 @@
-
-The Black Star File System
-==========================
-
-
-### Developer tools setup
-
-#### Test coverage (coverage)
-
-Resources:
-* https://coverage.readthedocs.io/en/6.5.0/index.html
-* https://nedbatchelder.com/blog/200710/flaws_in_coverage_measurement.html
-
-Commands:
-$ pip install coverage
-$ coverage run ; coverage html ; xdg-open .htmlcov/index.html
-
-
-
-#### Static code analysis (pylint)
-
-Resources:
-* https://github.com/PyCQA/pylint
-* https://pylint.org/
-* https://pylint.pycqa.org/en/latest/user_guide/messages/messages_overview.html#messages-overview
-
-Commands:
-$ pip install pylint
-$ pylint bsfs
-
-
-
-#### Type analysis (mypy)
-
-Resources:
-* https://github.com/python/mypy
-* https://mypy.readthedocs.io/en/stable/
-
-Commands:
-$ pip install mypy
-$ mypy
-
-
-
-#### Documentation (sphinx)
-
-Resources:
-*
-*
-
-Commands:
-$ pip install ...
-$
-
-
-
-
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..796c198
--- /dev/null
+++ b/README.md
@@ -0,0 +1,59 @@
+
+# The Black Star File System
+
+The Black Star File System (BSFS) is a semantic file system, meaning that it organizes files
+by association, and can record files, their metadata and content in a structured manner.
+
+
+## Installation
+
+You can install BSFS via pip:
+
+ $ pip install --extra-index-url https://pip.bsfs.io bsfs
+
+
+## Development
+
+Set up a virtual environment:
+
+ $ virtualenv env
+ $ source env/bin/activate
+
+Install bsfs as editable from the git repository:
+
+ $ git clone https://git.bsfs.io/bsfs.git
+ $ cd bsfs
+ $ pip install -e .
+
+If you want to develop (*dev*), run the tests (*test*), edit the
+documentation (*doc*), or build a distributable (*build*),
+install bsfs with the respective extras:
+
+ $ pip install -e .[dev,doc,build,test]
+
+Or, you can manually install the following packages besides BSFS:
+
+ $ pip install coverage mypy pylint
+ $ pip install sphinx sphinx-copybutton furo
+ $ pip install build
+
+To ensure code style discipline, run the following commands:
+
+ $ coverage run ; coverage html ; xdg-open .htmlcov/index.html
+ $ pylint bsfs
+ $ mypy
+
+To build the package, do:
+
+ $ python -m build
+
+To run only the tests (without coverage), run the following command from the **test folder**:
+
+ $ python -m unittest
+
+To build the documentation, run the following commands from the **doc folder**:
+
+ $ sphinx-apidoc -f -o source/api ../bsfs/ --module-first -d 1 --separate
+ $ make html
+ $ xdg-open build/html/index.html
+
diff --git a/bsfs.app b/bsfs.app
index babacbb..c837ca0 100755
--- a/bsfs.app
+++ b/bsfs.app
@@ -1,52 +1,6 @@
-"""BSFS tools.
-
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
-# imports
-import argparse
-import typing
-
-# module imports
-import bsfs
-import bsfs.apps
-
-# exports
-__all__: typing.Sequence[str] = (
- 'main',
- )
-
-# config
-apps = {
- 'init' : bsfs.apps.init,
- 'migrate' : bsfs.apps.migrate,
- }
-
-
-## code ##
-
-def main(argv):
- """Black Star File System maintenance tools."""
- parser = argparse.ArgumentParser(description=main.__doc__, prog='bsfs')
- # version
- parser.add_argument('--version', action='version',
- version='%(prog)s version {}.{}.{}'.format(*bsfs.version_info))
- # application selection
- parser.add_argument('app', choices=apps.keys(),
- help='Select the application to run.')
- # dangling args
- parser.add_argument('rest', nargs=argparse.REMAINDER)
- # parse
- args = parser.parse_args()
- # run application
- apps[args.app](args.rest)
-
-
-## main ##
-
+#!/usr/bin/env python3
if __name__ == '__main__':
+ import bsfs.apps
import sys
- main(sys.argv[1:])
+ bsfs.apps.main(sys.argv[1:])
-## EOF ##
diff --git a/bsfs.toml b/bsfs.toml
deleted file mode 100644
index 45bf1c9..0000000
--- a/bsfs.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[project]
-name = "bsfs"
-description = "A content aware graph file system."
-version = "0.0.1"
-license = {text = "BSD 3-Clause License"}
-authors = [{name='Matthias Baumgartner', email="dev@igsor.net"}]
-dependencies = [
- "rdflib",
-]
-requires-python = ">=3.7"
-
diff --git a/bsfs/__init__.py b/bsfs/__init__.py
index 079ffaf..cf08d64 100644
--- a/bsfs/__init__.py
+++ b/bsfs/__init__.py
@@ -1,9 +1,4 @@
-"""
-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
diff --git a/bsfs/apps/__init__.py b/bsfs/apps/__init__.py
index 7efaa87..62dc5b5 100644
--- a/bsfs/apps/__init__.py
+++ b/bsfs/apps/__init__.py
@@ -1,20 +1,53 @@
-"""
+#!/usr/bin/env python3
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
+import argparse
import typing
+# bsfs imports
+import bsfs
+
# inner-module imports
from .init import main as init
from .migrate import main as migrate
# exports
__all__: typing.Sequence[str] = (
+ 'main',
'init',
'migrate',
)
+# config
+apps = {
+ 'init' : init,
+ 'migrate' : migrate,
+ }
+
+
+## code ##
+
+def main(argv=None):
+ """Black Star File System maintenance tools."""
+ parser = argparse.ArgumentParser(description=main.__doc__, prog='bsfs')
+ # version
+ parser.add_argument('--version', action='version',
+ version='%(prog)s version {}.{}.{}'.format(*bsfs.version_info)) # pylint: disable=C0209
+ # application selection
+ parser.add_argument('app', choices=apps.keys(),
+ help='Select the application to run.')
+ # dangling args
+ parser.add_argument('rest', nargs=argparse.REMAINDER)
+ # parse
+ args = parser.parse_args(argv)
+ # run application
+ apps[args.app](args.rest)
+
+
+## main ##
+
+if __name__ == '__main__':
+ import sys
+ main(sys.argv[1:])
+
## EOF ##
diff --git a/bsfs/apps/init.py b/bsfs/apps/init.py
index 3e2ef37..9afbdd5 100644
--- a/bsfs/apps/init.py
+++ b/bsfs/apps/init.py
@@ -1,9 +1,5 @@
-"""
+#!/usr/bin/env python3
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import argparse
import json
@@ -60,9 +56,10 @@ def main(argv):
# print config
if args.output is not None:
with open(args.output, mode='wt', encoding='UTF-8') as ofile:
- json.dump(config, ofile)
+ json.dump(config, ofile, indent=4)
else:
- json.dump(config, sys.stdout)
+ json.dump(config, sys.stdout, indent=4)
+ print('')
## main ##
diff --git a/bsfs/apps/migrate.py b/bsfs/apps/migrate.py
index b9d019f..34ea2e7 100644
--- a/bsfs/apps/migrate.py
+++ b/bsfs/apps/migrate.py
@@ -1,9 +1,5 @@
-"""
+#!/usr/bin/env python3
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import argparse
import json
diff --git a/bsfs/front/__init__.py b/bsfs/front/__init__.py
index 92886ab..cedcd7f 100644
--- a/bsfs/front/__init__.py
+++ b/bsfs/front/__init__.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/front/bsfs.py b/bsfs/front/bsfs.py
index 968b3f5..f437212 100644
--- a/bsfs/front/bsfs.py
+++ b/bsfs/front/bsfs.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/front/builder.py b/bsfs/front/builder.py
index 73f1703..b1d488b 100644
--- a/bsfs/front/builder.py
+++ b/bsfs/front/builder.py
@@ -1,14 +1,9 @@
-"""
-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.graph import Graph, ac
from bsfs.triple_store import TripleStoreBase, SparqlStore
from bsfs.utils import URI, errors
@@ -68,8 +63,10 @@ def build_graph(cfg: typing.Any) -> Graph:
if 'backend' not in args:
raise errors.ConfigError('required argument "backend" is not provided')
backend = build_backend(args['backend'])
+ # build access controls
+ access_controls = ac.NullAC(backend, user)
# build and return graph
cls = _graph_classes[name]
- return cls(backend, user)
+ return cls(backend, access_controls)
## EOF ##
diff --git a/bsfs/graph/__init__.py b/bsfs/graph/__init__.py
index 82d2235..8d38d23 100644
--- a/bsfs/graph/__init__.py
+++ b/bsfs/graph/__init__.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/graph/ac/__init__.py b/bsfs/graph/ac/__init__.py
index 420de01..11b45df 100644
--- a/bsfs/graph/ac/__init__.py
+++ b/bsfs/graph/ac/__init__.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/graph/ac/base.py b/bsfs/graph/ac/base.py
index 79b09e5..e85c1dd 100644
--- a/bsfs/graph/ac/base.py
+++ b/bsfs/graph/ac/base.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import abc
import typing
@@ -12,7 +7,7 @@ import typing
from bsfs import schema
from bsfs.query import ast
from bsfs.triple_store import TripleStoreBase
-from bsfs.utils import URI
+from bsfs.utils import URI, typename
# exports
__all__: typing.Sequence[str] = (
@@ -44,6 +39,20 @@ class AccessControlBase(abc.ABC):
self._backend = backend
self._user = URI(user)
+ def __str__(self) -> str:
+ return f'{typename(self)}({self._user})'
+
+ def __repr__(self) -> str:
+ return f'{typename(self)}({self._user})'
+
+ def __eq__(self, other: typing.Any) -> bool:
+ return isinstance(other, type(self)) \
+ and self._backend == other._backend \
+ and self._user == other._user
+
+ def __hash__(self) -> int:
+ return hash((type(self), self._backend, self._user))
+
@abc.abstractmethod
def is_protected_predicate(self, pred: schema.Predicate) -> bool:
"""Return True if a predicate cannot be modified manually."""
@@ -69,7 +78,11 @@ class AccessControlBase(abc.ABC):
"""Return nodes that are allowed to be created."""
@abc.abstractmethod
- def filter_read(self, node_type: schema.Node, query: ast.filter.FilterExpression) -> ast.filter.FilterExpression:
+ def filter_read(
+ self,
+ node_type: schema.Node,
+ query: typing.Optional[ast.filter.FilterExpression],
+ ) -> typing.Optional[ast.filter.FilterExpression]:
"""Re-write a filter *query* to get (i.e., read) *node_type* nodes."""
@abc.abstractmethod
diff --git a/bsfs/graph/ac/null.py b/bsfs/graph/ac/null.py
index 6a923a5..c9ec7d0 100644
--- a/bsfs/graph/ac/null.py
+++ b/bsfs/graph/ac/null.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
@@ -29,7 +24,7 @@ class NullAC(base.AccessControlBase):
def is_protected_predicate(self, pred: schema.Predicate) -> bool:
"""Return True if a predicate cannot be modified manually."""
- return pred.uri == ns.bsm.t_created
+ return pred.uri == ns.bsn.t_created
def create(self, node_type: schema.Node, guids: typing.Iterable[URI]):
"""Perform post-creation operations on nodes, e.g. ownership information."""
@@ -50,7 +45,11 @@ class NullAC(base.AccessControlBase):
"""Return nodes that are allowed to be created."""
return guids
- def filter_read(self, node_type: schema.Node, query: ast.filter.FilterExpression) -> ast.filter.FilterExpression:
+ def filter_read(
+ self,
+ node_type: schema.Node,
+ query: typing.Optional[ast.filter.FilterExpression]
+ ) -> typing.Optional[ast.filter.FilterExpression]:
"""Re-write a filter *query* to get (i.e., read) *node_type* nodes."""
return query
diff --git a/bsfs/graph/graph.py b/bsfs/graph/graph.py
index df2e3a5..1b4c212 100644
--- a/bsfs/graph/graph.py
+++ b/bsfs/graph/graph.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import os
import typing
@@ -28,9 +23,7 @@ __all__: typing.Sequence[str] = (
## code ##
class Graph():
- """The Graph class is
-
- The Graph class provides a convenient interface to query and access a graph.
+ """The Graph class provides a convenient interface to query and access a graph.
Since it logically builds on the concept of graphs it is easier to
navigate than raw triple stores. Naturally, it uses a triple store
as *backend*. It also controls actions via access permissions to a *user*.
@@ -40,31 +33,33 @@ class Graph():
# link to the triple storage backend.
_backend: TripleStoreBase
- # user uri.
- _user: URI
+ # access controls.
+ _ac: ac.AccessControlBase
- def __init__(self, backend: TripleStoreBase, user: URI):
+ def __init__(
+ self,
+ backend: TripleStoreBase,
+ access_control: ac.AccessControlBase,
+ ):
+ # store members
self._backend = backend
- self._user = user
- self._resolver = resolve.Filter(self._backend.schema)
- self._validate = validate.Filter(self._backend.schema)
- self._ac = ac.NullAC(self._backend, self._user)
+ self._ac = access_control
# ensure Graph schema requirements
self.migrate(self._backend.schema)
def __hash__(self) -> int:
- return hash((type(self), self._backend, self._user))
+ return hash((type(self), self._backend, self._ac))
def __eq__(self, other) -> bool:
return isinstance(other, type(self)) \
and self._backend == other._backend \
- and self._user == other._user
+ and self._ac == other._ac
def __repr__(self) -> str:
- return f'{typename(self)}(backend={repr(self._backend)}, user={self._user})'
+ return f'{typename(self)}({repr(self._backend)}, {self._ac})'
def __str__(self) -> str:
- return f'{typename(self)}({str(self._backend)}, {self._user})'
+ return f'{typename(self)}({str(self._backend)})'
@property
def schema(self) -> bsc.Schema:
@@ -90,9 +85,6 @@ class Graph():
# migrate schema in backend
# FIXME: consult access controls!
self._backend.schema = schema
- # re-initialize members
- self._resolver.schema = self.schema
- self._validate.schema = self.schema
# return self
return self
@@ -104,41 +96,69 @@ class Graph():
*node_type*) once some data is assigned to them.
"""
+ # get node type
type_ = self.schema.node(node_type)
# NOTE: Nodes constructor materializes guids.
- return _nodes.Nodes(self._backend, self._user, type_, guids)
+ return _nodes.Nodes(self._backend, self._ac, type_, guids)
def node(self, node_type: URI, guid: URI) -> _nodes.Nodes:
"""Return node *guid* of type *node_type* as a `bsfs.graph.Nodes` instance.
- Note that the *guids* need not to exist (however, the *node_type* has
+ Note that the *guid* need not to exist (however, the *node_type* has
to be part of the schema). An inexistent guid will be created (using
*node_type*) once some data is assigned to them.
"""
return self.nodes(node_type, {guid})
- def get(self, node_type: URI, query: ast.filter.FilterExpression) -> _nodes.Nodes: # FIXME: How about empty query?
- """Return a `Nodes` instance over all nodes of type *node_type* that match the *subject* query."""
- # get node type
- type_ = self.schema.node(node_type)
- # resolve Nodes instances
- query = self._resolver(type_, query)
- # add access controls to query
- query = self._ac.filter_read(type_, query)
- # validate query
- self._validate(type_, query)
- # query the backend
- guids = self._backend.get(type_, query) # no need to materialize
+ def empty(self, node_type: URI) -> _nodes.Nodes:
+ """Return a `Nodes` instance with type *node_type* but no nodes."""
+ return self.nodes(node_type, set())
+
+ def get(
+ self,
+ node_type: URI,
+ query: typing.Optional[ast.filter.FilterExpression],
+ ) -> _nodes.Nodes:
+ """Return a `Nodes` instance over all nodes of type *node_type* that match the *query*."""
# return Nodes instance
- return _nodes.Nodes(self._backend, self._user, type_, guids)
+ type_ = self.schema.node(node_type)
+ return _nodes.Nodes(self._backend, self._ac, type_, self.__get(node_type, query))
+
+ def sorted(
+ self,
+ node_type: URI,
+ query: typing.Optional[ast.filter.FilterExpression],
+ # FIXME: sort ast
+ ) -> typing.Iterator[_nodes.Nodes]:
+ """Return a iterator over `Nodes` instances over all nodes of type *node_type* that match the *query*."""
+ # FIXME: Order should be a parameter
+ # return iterator over Nodes instances
+ type_ = self.schema.node(node_type)
+ for guid in self.__get(node_type, query):
+ yield _nodes.Nodes(self._backend, self._ac, type_, {guid})
def all(self, node_type: URI) -> _nodes.Nodes:
"""Return all instances of type *node_type*."""
+ type_ = self.schema.node(node_type)
+ return _nodes.Nodes(self._backend, self._ac, type_, self.__get(node_type, None))
+
+ def __get(
+ self,
+ node_type: URI,
+ query: typing.Optional[ast.filter.FilterExpression],
+ ) -> typing.Iterator[URI]:
+ """Build and execute a get query."""
# get node type
type_ = self.schema.node(node_type)
- guids = self._backend.get(type_, None) # no need to materialize
- return _nodes.Nodes(self._backend, self._user, type_, guids)
-
+ # resolve Nodes instances
+ query = resolve.Filter(self._backend.schema).resolve(type_, query)
+ # add access controls to query
+ query = self._ac.filter_read(type_, query)
+ # validate query
+ if query is not None:
+ validate.Filter(self._backend.schema).validate(type_, query)
+ # query the backend and return the (non-materialized) result
+ return self._backend.get(type_, query)
## EOF ##
diff --git a/bsfs/graph/nodes.py b/bsfs/graph/nodes.py
index bc71a32..47b0217 100644
--- a/bsfs/graph/nodes.py
+++ b/bsfs/graph/nodes.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
from collections import abc
import time
@@ -30,15 +25,17 @@ __all__: typing.Sequence[str] = (
## code ##
class Nodes():
- """
+ """Container for graph nodes, provides operations on nodes.
+
+ NOTE: Should not be created directly but only via `bsfs.graph.Graph`.
NOTE: guids may or may not exist. This is not verified as nodes are created on demand.
"""
# triple store backend.
_backend: TripleStoreBase
- # user uri.
- _user: URI
+ # access controls.
+ _ac: ac.AccessControlBase
# node type.
_node_type: bsc.Node
@@ -49,31 +46,29 @@ class Nodes():
def __init__(
self,
backend: TripleStoreBase,
- user: URI,
+ access_control: ac.AccessControlBase,
node_type: bsc.Node,
guids: typing.Iterable[URI],
):
# set main members
self._backend = backend
- self._user = user
+ self._ac = access_control
self._node_type = node_type
- self._guids = set(guids)
- # create helper instances
- # FIXME: Assumes that the schema does not change while the instance is in use!
- self._ac = ac.NullAC(self._backend, self._user)
+ # convert to URI since this is not guaranteed by Graph
+ self._guids = {URI(guid) for guid in guids}
def __eq__(self, other: typing.Any) -> bool:
return isinstance(other, Nodes) \
and self._backend == other._backend \
- and self._user == other._user \
+ and self._ac == other._ac \
and self._node_type == other._node_type \
and self._guids == other._guids
def __hash__(self) -> int:
- return hash((type(self), self._backend, self._user, self._node_type, tuple(sorted(self._guids))))
+ return hash((type(self), self._backend, self._ac, self._node_type, tuple(sorted(self._guids))))
def __repr__(self) -> str:
- return f'{typename(self)}({self._backend}, {self._user}, {self._node_type}, {self._guids})'
+ return f'{typename(self)}({self._backend}, {self._ac}, {self._node_type}, {self._guids})'
def __str__(self) -> str:
return f'{typename(self)}({self._node_type}, {self._guids})'
@@ -94,44 +89,44 @@ class Nodes():
return self._backend.schema
def __add__(self, other: typing.Any) -> 'Nodes':
- """Concatenate guids. Backend, user, and node type must match."""
+ """Concatenate guids. Backend, AC, and node type must match."""
if not isinstance(other, type(self)):
return NotImplemented
if self._backend != other._backend:
raise ValueError(other)
- if self._user != other._user:
+ if self._ac != other._ac:
raise ValueError(other)
if self.node_type != other.node_type:
raise ValueError(other)
- return Nodes(self._backend, self._user, self.node_type, self._guids | other._guids)
+ return Nodes(self._backend, self._ac, self.node_type, self._guids | other._guids)
def __or__(self, other: typing.Any) -> 'Nodes':
- """Concatenate guids. Backend, user, and node type must match."""
+ """Concatenate guids. Backend, AC, and node type must match."""
return self.__add__(other)
def __sub__(self, other: typing.Any) -> 'Nodes':
- """Subtract guids. Backend, user, and node type must match."""
+ """Subtract guids. Backend, AC, and node type must match."""
if not isinstance(other, type(self)):
return NotImplemented
if self._backend != other._backend:
raise ValueError(other)
- if self._user != other._user:
+ if self._ac != other._ac:
raise ValueError(other)
if self.node_type != other.node_type:
raise ValueError(other)
- return Nodes(self._backend, self._user, self.node_type, self._guids - other._guids)
+ return Nodes(self._backend, self._ac, self.node_type, self._guids - other._guids)
def __and__(self, other: typing.Any) -> 'Nodes':
- """Intersect guids. Backend, user, and node type must match."""
+ """Intersect guids. Backend, AC, and node type must match."""
if not isinstance(other, type(self)):
return NotImplemented
if self._backend != other._backend:
raise ValueError(other)
- if self._user != other._user:
+ if self._ac != other._ac:
raise ValueError(other)
if self.node_type != other.node_type:
raise ValueError(other)
- return Nodes(self._backend, self._user, self.node_type, self._guids & other._guids)
+ return Nodes(self._backend, self._ac, self.node_type, self._guids & other._guids)
def __len__(self) -> int:
"""Return the number of guids."""
@@ -140,7 +135,7 @@ class Nodes():
def __iter__(self) -> typing.Iterator['Nodes']:
"""Iterate over individual guids. Returns `Nodes` instances."""
return iter(
- Nodes(self._backend, self._user, self.node_type, {guid})
+ Nodes(self._backend, self._ac, self.node_type, {guid})
for guid in self._guids
)
@@ -175,7 +170,7 @@ class Nodes():
self._backend.commit()
except (
- errors.PermissionDeniedError, # tried to set a protected predicate (ns.bsm.t_created)
+ errors.PermissionDeniedError, # tried to set a protected predicate
errors.ConsistencyError, # node types are not in the schema or don't match the predicate
errors.InstanceError, # guids/values don't have the correct type
TypeError, # value is supposed to be a Nodes instance
@@ -250,32 +245,38 @@ class Nodes():
# add access controls to fetch
fetch = self._ac.fetch_read(self.node_type, fetch)
- # compose filter ast
- filter = ast.filter.IsIn(self.guids) # pylint: disable=redefined-builtin
- # add access controls to filter
- filter = self._ac.filter_read(self.node_type, filter)
-
- # validate queries
- validate.Filter(self._backend.schema)(self.node_type, filter)
- validate.Fetch(self._backend.schema)(self.node_type, fetch)
-
- # process results, convert if need be
- def triple_iter():
- # query the backend
- triples = self._backend.fetch(self.node_type, filter, fetch)
- # process triples
- for root, name, raw in triples:
- # get node
- node = Nodes(self._backend, self._user, self.node_type, {root})
- # get path
- path, tail = name2path[name]
- # covert raw to value
- if isinstance(tail.range, bsc.Node):
- value = Nodes(self._backend, self._user, tail.range, {raw})
- else:
- value = raw
- # emit triple
- yield node, path, value
+ if len(self._guids) == 0:
+ # shortcut: no need to query; no triples
+ # FIXME: if the Fetch query can given by the user, we might want to check its validity
+ def triple_iter():
+ return []
+ else:
+ # compose filter ast
+ filter = ast.filter.IsIn(self.guids) # pylint: disable=redefined-builtin
+ # add access controls to filter
+ filter = self._ac.filter_read(self.node_type, filter) # type: ignore [assignment]
+
+ # validate queries
+ validate.Filter(self._backend.schema).validate(self.node_type, filter)
+ validate.Fetch(self._backend.schema).validate(self.node_type, fetch)
+
+ # process results, convert if need be
+ def triple_iter():
+ # query the backend
+ triples = self._backend.fetch(self.node_type, filter, fetch)
+ # process triples
+ for root, name, raw in triples:
+ # get node
+ node = Nodes(self._backend, self._ac, self.node_type, {root})
+ # get path
+ path, tail = name2path[name]
+ # covert raw to value
+ if isinstance(tail.range, bsc.Node):
+ value = Nodes(self._backend, self._ac, tail.range, {raw})
+ else:
+ value = raw
+ # emit triple
+ yield node, path, value
# simplify by default
view_kwargs['node'] = view_kwargs.get('node', len(self._guids) != 1)
@@ -393,7 +394,7 @@ class Nodes():
self._backend.create(node_type, missing)
# add bookkeeping triples
self._backend.set(node_type, missing,
- self._backend.schema.predicate(ns.bsm.t_created), [time.time()])
+ self._backend.schema.predicate(ns.bsn.t_created), [time.time()])
# add permission triples
self._ac.create(node_type, missing)
# return available nodes
diff --git a/bsfs/graph/resolve.py b/bsfs/graph/resolve.py
index 4677401..a58eb67 100644
--- a/bsfs/graph/resolve.py
+++ b/bsfs/graph/resolve.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
@@ -32,16 +27,30 @@ class Filter():
input: Any(ns.bse.tag, Is(Nodes(...)))
output: Any(ns.bse.tag, Or(Is(...), Is(...), ...)))
- >>> tags = graph.node(ns.bsfs.Tag, 'http://example.com/me/tag#1234')
- >>> graph.get(ns.bsfs.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Is(tags)))
+ >>> tags = graph.node(ns.bsn.Tag, 'http://example.com/me/tag#1234')
+ >>> graph.get(ns.bsn.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Is(tags)))
"""
def __init__(self, schema):
self.schema = schema
- def __call__(self, root_type: bsc.Node, node: ast.filter.FilterExpression):
- # FIXME: node can be None!
+ def __call__(
+ self,
+ root_type: bsc.Node,
+ node: typing.Optional[ast.filter.FilterExpression],
+ ):
+ """Alias for `Resolve.resolve`."""
+ return self.resolve(root_type, node)
+
+ def resolve(
+ self,
+ root_type: bsc.Node,
+ node: typing.Optional[ast.filter.FilterExpression],
+ ):
+ """Resolve Nodes instances of a *node* query starting at *root_type*."""
+ if node is None:
+ return None
return self._parse_filter_expression(root_type, node)
def _parse_filter_expression(
diff --git a/bsfs/graph/result.py b/bsfs/graph/result.py
index 31822f1..0fcbb13 100644
--- a/bsfs/graph/result.py
+++ b/bsfs/graph/result.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
from collections import defaultdict
import typing
diff --git a/bsfs/graph/schema.nt b/bsfs/graph/schema.nt
index f619746..37bba5e 100644
--- a/bsfs/graph/schema.nt
+++ b/bsfs/graph/schema.nt
@@ -4,16 +4,17 @@ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
# bsfs prefixes
-prefix bsfs: <http://bsfs.ai/schema/>
-prefix bsm: <http://bsfs.ai/schema/Meta#>
+prefix bsfs: <https://schema.bsfs.io/core/>
+prefix bsl: <https://schema.bsfs.io/core/Literal/>
+prefix bsn: <https://schema.bsfs.io/core/Node#>
# literals
-bsfs:Number rdfs:subClassOf bsfs:Literal .
-xsd:integer rdfs:subClassOf bsfs:Number .
+bsl:Number rdfs:subClassOf bsfs:Literal .
+xsd:float rdfs:subClassOf bsl:Number .
# predicates
-bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+bsn:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
- rdfs:range xsd:integer ;
+ rdfs:range xsd:float ;
bsfs:unique "true"^^xsd:boolean .
diff --git a/bsfs/graph/walk.py b/bsfs/graph/walk.py
index 1b1cfa0..6415c9b 100644
--- a/bsfs/graph/walk.py
+++ b/bsfs/graph/walk.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
from collections import abc
import typing
diff --git a/bsfs/namespace/__init__.py b/bsfs/namespace/__init__.py
index 98d472f..76f39a2 100644
--- a/bsfs/namespace/__init__.py
+++ b/bsfs/namespace/__init__.py
@@ -1,19 +1,13 @@
-"""
-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 . import predefined as ns
-from .namespace import ClosedNamespace, Namespace
+from .namespace import Namespace
# exports
__all__: typing.Sequence[str] = (
- 'ClosedNamespace',
'Namespace',
'ns',
)
diff --git a/bsfs/namespace/namespace.py b/bsfs/namespace/namespace.py
index 1d443c1..b388f53 100644
--- a/bsfs/namespace/namespace.py
+++ b/bsfs/namespace/namespace.py
@@ -1,104 +1,54 @@
-"""
-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.utils import URI, typename
+from bsfs.utils import URI
# exports
__all__: typing.Sequence[str] = (
- 'ClosedNamespace',
'Namespace',
+ 'FinalNamespace',
)
## code ##
-class Namespace():
- """A namespace consists of a common prefix that is used in a set of URIs.
- Note that the prefix must include the separator between
- path and fragment (typically a '#' or a '/').
- """
-
- # namespace prefix.
- prefix: URI
-
- # fragment separator.
- fsep: str
-
- # path separator.
- psep: str
-
- def __init__(self, prefix: URI, fsep: str = '#', psep: str = '/'):
- # ensure prefix type
- prefix = URI(prefix)
- # truncate fragment separator
- while prefix.endswith(fsep):
- prefix = URI(prefix[:-1])
- # truncate path separator
- while prefix.endswith(psep):
- prefix = URI(prefix[:-1])
- # store members
- self.prefix = prefix
- self.fsep = fsep
- self.psep = psep
-
- def __eq__(self, other: typing.Any) -> bool:
- return isinstance(other, type(self)) \
- and self.prefix == other.prefix \
- and self.fsep == other.fsep \
- and self.psep == other.psep
+class Namespace(URI):
+ """The Namespace allows you to incrementally append path segments to an URI.
- def __hash__(self) -> int:
- return hash((type(self), self.prefix, self.fsep, self.psep))
+ Segments are separated by `Namespace.sep` ('/').
+ The `__call__` method signals that the URI is complete until the query part.
- def __str__(self) -> str:
- return str(self.prefix)
-
- def __repr__(self) -> str:
- return f'{typename(self)}({self.prefix}, {self.fsep}, {self.psep})'
-
- def __getattr__(self, fragment: str) -> URI:
- """Return prefix + fragment."""
- return URI(self.prefix + self.fsep + fragment)
-
- def __getitem__(self, fragment: str) -> URI:
- """Alias for getattr(self, fragment)."""
- return self.__getattr__(fragment)
+ """
- def __add__(self, value: typing.Any) -> 'Namespace':
- """Concatenate another namespace to this one."""
- if not isinstance(value, str):
- return NotImplemented
- return Namespace(self.prefix + self.psep + value, self.fsep, self.psep)
+ # path separator
+ sep: str = '/'
+ def __getattr__(self, query: str) -> 'Namespace':
+ """Append the *query* to the current value and return as Namespace."""
+ return Namespace(self + self.sep + query)
-class ClosedNamespace(Namespace):
- """Namespace that covers a restricted set of URIs."""
+ def __call__(self, sep: str = '#') -> 'FinalNamespace':
+ """Finalize the namespace."""
+ return FinalNamespace(self, sep)
- # set of permissible fragments.
- fragments: typing.Set[str]
- def __init__(self, prefix: URI, *args: str, fsep: str = '#', psep: str = '/'):
- super().__init__(prefix, fsep, psep)
- self.fragments = set(args)
+# FIXME: Integrate FinalNamespace into Namespace? Do we need to have both?
+class FinalNamespace(URI):
+ """The FinalNamespace allows you to append a fragment to an URI."""
- def __eq__(self, other: typing.Any) -> bool:
- return super().__eq__(other) and self.fragments == other.fragments
+ # fragment separator
+ sep: str
- def __hash__(self) -> int:
- return hash((type(self), self.prefix, tuple(sorted(self.fragments))))
+ def __new__(cls, value: str, sep: str = '#'):
+ inst = URI.__new__(cls, value)
+ inst.sep = sep
+ return inst
def __getattr__(self, fragment: str) -> URI:
- """Return prefix + fragment or raise a KeyError if the fragment is not part of this namespace."""
- if fragment not in self.fragments:
- raise KeyError(f'{fragment} is not a valid fragment of namespace {self.prefix}')
- return super().__getattr__(fragment)
+ """Append the *fragment* to the current value and return as URI."""
+ return URI(self + self.sep + fragment)
## EOF ##
diff --git a/bsfs/namespace/predefined.py b/bsfs/namespace/predefined.py
index cd48a46..8b60d39 100644
--- a/bsfs/namespace/predefined.py
+++ b/bsfs/namespace/predefined.py
@@ -1,35 +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.utils import URI
-
# inner-module imports
-from . import namespace
+from .namespace import Namespace, FinalNamespace
# essential bsfs namespaces
-bsfs: namespace.Namespace = namespace.Namespace(URI('http://bsfs.ai/schema'), fsep='/')
-
+bsfs = Namespace('https://schema.bsfs.io/core')
# additional bsfs namespaces
-bse: namespace.Namespace = namespace.Namespace(URI('http://bsfs.ai/schema/Entity'))
-bsm: namespace.Namespace = namespace.Namespace(URI('http://bsfs.ai/schema/Meta'))
+bsd = bsfs.distance()
+bsl = bsfs.Literal
+bsn = bsfs.Node()
# generic namespaces
-rdf: namespace.Namespace = namespace.Namespace(URI('http://www.w3.org/1999/02/22-rdf-syntax-ns'))
-rdfs: namespace.Namespace = namespace.Namespace(URI('http://www.w3.org/2000/01/rdf-schema'))
-schema: namespace.Namespace = namespace.Namespace(URI('http://schema.org'), fsep='/')
-xsd: namespace.Namespace = namespace.Namespace(URI('http://www.w3.org/2001/XMLSchema'))
+rdf = FinalNamespace('http://www.w3.org/1999/02/22-rdf-syntax-ns')
+rdfs = FinalNamespace('http://www.w3.org/2000/01/rdf-schema')
+xsd = FinalNamespace('http://www.w3.org/2001/XMLSchema')
+schema = FinalNamespace('http://schema.org', sep='/')
+# exports
__all__: typing.Sequence[str] = (
- 'bse',
+ 'bsd',
'bsfs',
- 'bsm',
+ 'bsl',
+ 'bsn',
'rdf',
'rdfs',
'schema',
diff --git a/bsfs/query/__init__.py b/bsfs/query/__init__.py
index 21c7389..58ff03a 100644
--- a/bsfs/query/__init__.py
+++ b/bsfs/query/__init__.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/query/ast/__init__.py b/bsfs/query/ast/__init__.py
index 66b097d..bceaac0 100644
--- a/bsfs/query/ast/__init__.py
+++ b/bsfs/query/ast/__init__.py
@@ -6,9 +6,6 @@ Classes beginning with an underscore (_) represent internal type hierarchies
and should not be used for parsing. Note that the AST structures do not
(and cannot) check semantic validity or consistency with a given schema.
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
"""
# imports
import typing
diff --git a/bsfs/query/ast/fetch.py b/bsfs/query/ast/fetch.py
index d653a8a..66d94e1 100644
--- a/bsfs/query/ast/fetch.py
+++ b/bsfs/query/ast/fetch.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
from collections import abc
import typing
diff --git a/bsfs/query/ast/filter_.py b/bsfs/query/ast/filter_.py
index b29d89e..610fdb4 100644
--- a/bsfs/query/ast/filter_.py
+++ b/bsfs/query/ast/filter_.py
@@ -10,7 +10,8 @@ For example, consider the following AST:
>>> Any(ns.bse.collection,
... And(
... Equals('hello'),
-... Any(ns.bsm.guid, Any(ns.bsm.guid, Equals('hello'))),
+... Is('hello world'),
+... Any(ns.bse.tag, Equals('world')),
... Any(ns.bst.label, Equals('world')),
... All(ns.bst.label, Not(Equals('world'))),
... )
@@ -22,9 +23,6 @@ This AST has multiple issues that are not verified upon its creation:
* Conditions exclude each other
* The predicate along the branch have incompatible domains and ranges.
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
"""
# imports
from collections import abc
diff --git a/bsfs/query/matcher.py b/bsfs/query/matcher.py
index a910756..17c9c8e 100644
--- a/bsfs/query/matcher.py
+++ b/bsfs/query/matcher.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
from collections import defaultdict
from itertools import product
@@ -220,8 +215,8 @@ class Filter():
two following queries are semantically identical, but structurally different,
and would therefore not match:
- >>> ast.filter.OneOf(ast.filter.Predicate(ns.bse.filename))
- >>> ast.filter.Predicate(ns.bse.filename)
+ >>> ast.filter.OneOf(ast.filter.Predicate(ns.bse.name))
+ >>> ast.filter.Predicate(ns.bse.name)
"""
diff --git a/bsfs/query/validator.py b/bsfs/query/validator.py
index f0aa795..10ca492 100644
--- a/bsfs/query/validator.py
+++ b/bsfs/query/validator.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
@@ -41,7 +36,11 @@ class Filter():
def __init__(self, schema: bsc.Schema):
self.schema = schema
- def __call__(self, root_type: bsc.Node, query: ast.filter.FilterExpression):
+ def __call__(self, root_type: bsc.Node, query: ast.filter.FilterExpression) -> bool:
+ """Alias for `Filter.validate`."""
+ return self.validate(root_type, query)
+
+ def validate(self, root_type: bsc.Node, query: ast.filter.FilterExpression) -> bool:
"""Validate a filter *query*, assuming the subject having *root_type*.
Raises a `bsfs.utils.errors.ConsistencyError` if the query violates the schema.
@@ -178,7 +177,7 @@ class Filter():
if not type_ <= dom:
raise errors.ConsistencyError(f'expected type {dom}, found {type_}')
# node.count is a numerical expression
- self._parse_filter_expression(self.schema.literal(ns.bsfs.Number), node.count)
+ self._parse_filter_expression(self.schema.literal(ns.bsl.Number), node.count)
def _distance(self, type_: bsc.Vertex, node: ast.filter.Distance):
# type is a Literal
@@ -219,7 +218,7 @@ class Filter():
if type_ not in self.schema.literals():
raise errors.ConsistencyError(f'literal {type_} is not in the schema')
# type must be a numerical
- if not type_ <= self.schema.literal(ns.bsfs.Number):
+ if not type_ <= self.schema.literal(ns.bsl.Number):
raise errors.ConsistencyError(f'expected a number type, found {type_}')
# FIXME: Check if node.value corresponds to type_
@@ -242,7 +241,11 @@ class Fetch():
def __init__(self, schema: bsc.Schema):
self.schema = schema
- def __call__(self, root_type: bsc.Node, query: ast.fetch.FetchExpression):
+ def __call__(self, root_type: bsc.Node, query: ast.fetch.FetchExpression) -> bool:
+ """Alias for `Fetch.validate`."""
+ return self.validate(root_type, query)
+
+ def validate(self, root_type: bsc.Node, query: ast.fetch.FetchExpression) -> bool:
"""Validate a fetch *query*, assuming the subject having *root_type*.
Raises a `bsfs.utils.errors.ConsistencyError` if the query violates the schema.
diff --git a/bsfs/schema/__init__.py b/bsfs/schema/__init__.py
index f53512e..ca2e0cd 100644
--- a/bsfs/schema/__init__.py
+++ b/bsfs/schema/__init__.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/schema/schema.py b/bsfs/schema/schema.py
index 0de4203..c104436 100644
--- a/bsfs/schema/schema.py
+++ b/bsfs/schema/schema.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
from collections import abc, namedtuple
import typing
diff --git a/bsfs/schema/serialize.py b/bsfs/schema/serialize.py
index acc009a..ea8b2f4 100644
--- a/bsfs/schema/serialize.py
+++ b/bsfs/schema/serialize.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import itertools
import typing
@@ -246,13 +241,14 @@ def to_string(schema_inst: schema.Schema, fmt: str = 'turtle') -> str:
graph.add(triple)
# add known namespaces for readability
# FIXME: more generically?
- graph.bind('bse', rdflib.URIRef(ns.bse['']))
- graph.bind('bsfs', rdflib.URIRef(ns.bsfs['']))
- graph.bind('bsm', rdflib.URIRef(ns.bsm['']))
- graph.bind('rdf', rdflib.URIRef(ns.rdf['']))
- graph.bind('rdfs', rdflib.URIRef(ns.rdfs['']))
- graph.bind('schema', rdflib.URIRef(ns.schema['']))
- graph.bind('xsd', rdflib.URIRef(ns.xsd['']))
+ graph.bind('bsfs', rdflib.URIRef(ns.bsfs + '/'))
+ graph.bind('bsl', rdflib.URIRef(ns.bsl + '/'))
+ graph.bind('bsn', rdflib.URIRef(ns.bsn + '#'))
+ graph.bind('bse', rdflib.URIRef(ns.bsfs.Entity() + '#'))
+ graph.bind('rdf', rdflib.URIRef(ns.rdf))
+ graph.bind('rdfs', rdflib.URIRef(ns.rdfs))
+ graph.bind('schema', rdflib.URIRef(ns.schema))
+ graph.bind('xsd', rdflib.URIRef(ns.xsd))
# serialize to turtle
return graph.serialize(format=fmt)
diff --git a/bsfs/schema/types.py b/bsfs/schema/types.py
index 12e7e94..5834df8 100644
--- a/bsfs/schema/types.py
+++ b/bsfs/schema/types.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
@@ -103,7 +98,7 @@ class _Type():
parent: typing.Optional['_Type'] = None,
**annotations: typing.Any,
):
- self.uri = uri
+ self.uri = URI(uri)
self.parent = parent
self.annotations = annotations
@@ -381,31 +376,31 @@ ROOT_LITERAL = Literal(
)
ROOT_BLOB = Literal(
- uri=ns.bsfs.BinaryBlob,
+ uri=ns.bsl.BinaryBlob,
parent=ROOT_LITERAL,
)
ROOT_NUMBER = Literal(
- uri=ns.bsfs.Number,
+ uri=ns.bsl.Number,
parent=ROOT_LITERAL,
)
ROOT_TIME = Literal(
- uri=ns.bsfs.Time,
+ uri=ns.bsl.Time,
parent=ROOT_LITERAL,
)
ROOT_ARRAY = Literal(
- uri=ns.bsfs.Array,
+ uri=ns.bsl.Array,
parent=ROOT_LITERAL,
)
ROOT_FEATURE = Feature(
- uri=ns.bsfs.Feature,
+ uri=ns.bsl.Array.Feature,
parent=ROOT_ARRAY,
dimension=1,
- dtype=ns.bsfs.f16,
- distance=ns.bsfs.euclidean,
+ dtype=ns.bsfs.dtype().f16,
+ distance=ns.bsd.euclidean,
)
# essential predicates
diff --git a/bsfs/triple_store/__init__.py b/bsfs/triple_store/__init__.py
index fb5a8a9..79a2887 100644
--- a/bsfs/triple_store/__init__.py
+++ b/bsfs/triple_store/__init__.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/triple_store/base.py b/bsfs/triple_store/base.py
index 1baa63b..58b5670 100644
--- a/bsfs/triple_store/base.py
+++ b/bsfs/triple_store/base.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import abc
import typing
diff --git a/bsfs/triple_store/sparql/__init__.py b/bsfs/triple_store/sparql/__init__.py
index 285334a..cfa2732 100644
--- a/bsfs/triple_store/sparql/__init__.py
+++ b/bsfs/triple_store/sparql/__init__.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/triple_store/sparql/distance.py b/bsfs/triple_store/sparql/distance.py
index 2f5387a..2c2f355 100644
--- a/bsfs/triple_store/sparql/distance.py
+++ b/bsfs/triple_store/sparql/distance.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import typing
@@ -48,9 +43,9 @@ def manhatten(fst, snd) -> float:
# Known distance functions.
DISTANCE_FU = {
- ns.bsfs.euclidean: euclid,
- ns.bsfs.cosine: cosine,
- ns.bsfs.manhatten: manhatten,
+ ns.bsd.euclidean: euclid,
+ ns.bsd.cosine: cosine,
+ ns.bsd.manhatten: manhatten,
}
## EOF ##
diff --git a/bsfs/triple_store/sparql/parse_fetch.py b/bsfs/triple_store/sparql/parse_fetch.py
index 20d4e74..fab8173 100644
--- a/bsfs/triple_store/sparql/parse_fetch.py
+++ b/bsfs/triple_store/sparql/parse_fetch.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import typing
diff --git a/bsfs/triple_store/sparql/parse_filter.py b/bsfs/triple_store/sparql/parse_filter.py
index dca0aea..2f5a25b 100644
--- a/bsfs/triple_store/sparql/parse_filter.py
+++ b/bsfs/triple_store/sparql/parse_filter.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import operator
import typing
@@ -156,16 +151,16 @@ class Filter():
raise errors.BackendError(f'the range of predicate {pred} is undefined')
dom, rng = pred.domain, pred.range
# encapsulate predicate uri
- puri = f'<{puri}>' # type: ignore [assignment] # variable re-use confuses mypy
+ uri_str = f'<{puri}>'
# apply reverse flag
if node.reverse:
- puri = URI('^' + puri)
+ uri_str = '^' + uri_str
dom, rng = rng, dom # type: ignore [assignment] # variable re-use confuses mypy
# check path consistency
if not node_type <= dom:
raise errors.ConsistencyError(f'expected type {dom} or subtype thereof, found {node_type}')
# return predicate URI and next node type
- return puri, rng
+ return uri_str, rng
def _any(self, node_type: bsc.Vertex, node: ast.filter.Any, head: str) -> str:
"""
@@ -272,7 +267,7 @@ class Filter():
"""
if not isinstance(node_type, bsc.Node):
raise errors.BackendError(f'expected Node, found {node_type}')
- return f'VALUES {head} {{ <{node.value}> }}'
+ return f'VALUES {head} {{ <{URI(node.value)}> }}'
def _equals(self, node_type: bsc.Vertex, node: ast.filter.Equals, head: str) -> str:
"""
diff --git a/bsfs/triple_store/sparql/sparql.py b/bsfs/triple_store/sparql/sparql.py
index dbf9d45..99e67d6 100644
--- a/bsfs/triple_store/sparql/sparql.py
+++ b/bsfs/triple_store/sparql/sparql.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import base64
import itertools
@@ -33,7 +28,7 @@ __all__: typing.Sequence[str] = (
## code ##
-rdflib.term.bind(ns.bsfs.BinaryBlob, bytes, constructor=base64.b64decode)
+rdflib.term.bind(ns.bsl.BinaryBlob, bytes, constructor=base64.b64decode)
class _Transaction():
"""Lightweight rdflib transactions for in-memory databases."""
@@ -289,7 +284,7 @@ class SparqlStore(base.TripleStoreBase):
raise errors.ConsistencyError(f'{node_type} is not defined in the schema')
# check and create guids
for guid in guids:
- subject = rdflib.URIRef(guid)
+ subject = rdflib.URIRef(URI(guid))
# check node existence
if (subject, rdflib.RDF.type, None) in self._graph:
# FIXME: node exists and may have a different type! ignore? raise? report?
@@ -328,7 +323,7 @@ class SparqlStore(base.TripleStoreBase):
raise errors.InstanceError(inconsistent)
# check guids
# FIXME: Fail or skip inexistent nodes?
- guids = set(guids)
+ guids = {URI(guid) for guid in guids}
inconsistent = {guid for guid in guids if not self._has_type(guid, node_type)}
if len(inconsistent) > 0:
raise errors.InstanceError(inconsistent)
@@ -340,8 +335,8 @@ class SparqlStore(base.TripleStoreBase):
# convert value
if isinstance(predicate.range, bsc.Literal):
dtype = rdflib.URIRef(predicate.range.uri)
- if predicate.range <= self.schema.literal(ns.bsfs.BinaryBlob):
- dtype = rdflib.URIRef(ns.bsfs.BinaryBlob)
+ if predicate.range <= self.schema.literal(ns.bsl.BinaryBlob):
+ dtype = rdflib.URIRef(ns.bsl.BinaryBlob)
value = base64.b64encode(value)
value = rdflib.Literal(value, datatype=dtype)
elif isinstance(predicate.range, bsc.Node):
diff --git a/bsfs/triple_store/sparql/utils.py b/bsfs/triple_store/sparql/utils.py
index deca4d8..38062c2 100644
--- a/bsfs/triple_store/sparql/utils.py
+++ b/bsfs/triple_store/sparql/utils.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import typing
@@ -127,11 +122,12 @@ class Query():
"""Return an executable sparql query."""
select = ' '.join(f'({head} as ?{name})' for head, name in self.select)
return f'''
- SELECT {self.root_head} {select}
+ SELECT DISTINCT {self.root_head} {select}
WHERE {{
{self.root_head} <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <{self.root_type}> .
{self.where}
}}
+ ORDER BY str({self.root_head})
'''
def __call__(self, graph: rdflib.Graph) -> rdflib.query.Result:
diff --git a/bsfs/utils/__init__.py b/bsfs/utils/__init__.py
index 6737cef..d497645 100644
--- a/bsfs/utils/__init__.py
+++ b/bsfs/utils/__init__.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/utils/commons.py b/bsfs/utils/commons.py
index e9f0b7f..a7092ae 100644
--- a/bsfs/utils/commons.py
+++ b/bsfs/utils/commons.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
from collections import abc
import typing
diff --git a/bsfs/utils/errors.py b/bsfs/utils/errors.py
index 6ae6484..b82e6e2 100644
--- a/bsfs/utils/errors.py
+++ b/bsfs/utils/errors.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import typing
diff --git a/bsfs/utils/uri.py b/bsfs/utils/uri.py
index 84854a4..5755a6e 100644
--- a/bsfs/utils/uri.py
+++ b/bsfs/utils/uri.py
@@ -1,14 +1,11 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import re
import typing
# constants
+RX_CHARS = re.compile(r'[<>" {}|\\^]')
+
RX_URI = re.compile(r'''
^
(?:(?P<scheme>[^:/?#]+):)? # scheme, ://-delimited
@@ -82,6 +79,9 @@ class URI(str):
no claim about the validity of an URI!
"""
+ # check characters
+ if RX_CHARS.search(query) is not None:
+ return False
# check uri
parts = RX_URI.match(query)
if parts is not None:
@@ -232,9 +232,6 @@ class URI(str):
# overload formatting methods
- def format(self, *args, **kwargs) -> 'URI':
- return URI(super().format(*args, **kwargs))
-
def __mod__(self, *args) -> 'URI':
return URI(super().__mod__(*args))
diff --git a/bsfs/utils/uuid.py b/bsfs/utils/uuid.py
index 70e1656..ad7fc1c 100644
--- a/bsfs/utils/uuid.py
+++ b/bsfs/utils/uuid.py
@@ -1,9 +1,4 @@
-"""
-Part of the BlackStar filesystem (bsfs) module.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
from collections import abc
import hashlib
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..d0c3cbf
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/doc/make.bat b/doc/make.bat
new file mode 100644
index 0000000..747ffb7
--- /dev/null
+++ b/doc/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/doc/source/_static/arch_dark.png b/doc/source/_static/arch_dark.png
new file mode 100644
index 0000000..b5ea1b3
--- /dev/null
+++ b/doc/source/_static/arch_dark.png
Binary files differ
diff --git a/doc/source/_static/arch_dark.svg b/doc/source/_static/arch_dark.svg
new file mode 100644
index 0000000..22de237
--- /dev/null
+++ b/doc/source/_static/arch_dark.svg
@@ -0,0 +1,500 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="226.45284mm"
+ height="347.16772mm"
+ viewBox="0 0 226.45283 347.16772"
+ version="1.1"
+ id="svg8"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ sodipodi:docname="architecture.svg"
+ inkscape:export-filename="/home/matthias/projects/black_star/modules/bsfs/doc/source/_static/architecture.png"
+ inkscape:export-xdpi="36.581741"
+ inkscape:export-ydpi="36.581741"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs2">
+ <marker
+ style="overflow:visible"
+ id="marker5768"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow2Mstart"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.6) translate(0,0)"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ style="stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round"
+ id="path5499" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow2Mend"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow2Mend"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.6) rotate(180) translate(0,0)"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ style="stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round;"
+ id="path5502" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow1Send"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow1Send"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.2) rotate(180) translate(6,0)"
+ style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ id="path5490" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow1Mend"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mend"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.4) rotate(180) translate(10,0)"
+ style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ id="path5484" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="Arrow1Mstart"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mstart"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.4) translate(10,0)"
+ style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ id="path5481" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Mstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path1226"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker2003"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Mend">
+ <path
+ transform="scale(-0.6)"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+ id="path2001"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker1943"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mend">
+ <path
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path1941"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker1883"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Send">
+ <path
+ transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path1881"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <inkscape:path-effect
+ effect="bspline"
+ id="path-effect1392"
+ is_visible="true"
+ weight="33.333333"
+ steps="2"
+ helper_size="0"
+ apply_no_weight="true"
+ apply_with_weight="true"
+ only_selected="false" />
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath1104">
+ <rect
+ ry="1.9402644e-06"
+ y="299.74707"
+ x="38.425957"
+ height="39.287846"
+ width="72.863937"
+ id="rect1106"
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:0.99141636;stroke:#ab0000;stroke-width:1.50988853;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ </clipPath>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.48410751"
+ inkscape:cx="549.46473"
+ inkscape:cy="378.01521"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-text-baseline="true"
+ inkscape:snap-page="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-global="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1147"
+ inkscape:window-x="0"
+ inkscape:window-y="29"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(62.276606,-425.46216)">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="32.604725"
+ y="629.79327"
+ id="text858"><tspan
+ sodipodi:role="line"
+ id="tspan856"
+ x="32.604725"
+ y="629.79327"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">Graph</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="31.855417"
+ y="653.12"
+ id="text862"><tspan
+ sodipodi:role="line"
+ id="tspan860"
+ x="31.855417"
+ y="653.12"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">Nodes</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="41.7075"
+ y="675.25732"
+ id="text866"><tspan
+ sodipodi:role="line"
+ id="tspan864"
+ x="41.7075"
+ y="675.25732"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">AC</tspan></text>
+ <rect
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#c5c9c7;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect854-9-6"
+ width="58.738262"
+ height="66.901787"
+ x="19.325045"
+ y="615.82233" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="26.010805"
+ y="742.52582"
+ id="text870"><tspan
+ sodipodi:role="line"
+ id="tspan868"
+ x="26.010805"
+ y="742.52582"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">Backend</tspan></text>
+ <rect
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#c5c9c7;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect854-9-7"
+ width="58.738262"
+ height="66.901787"
+ x="19.325045"
+ y="705.22809" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="38.606937"
+ y="541.41333"
+ id="text874"><tspan
+ sodipodi:role="line"
+ id="tspan872"
+ x="38.606937"
+ y="541.41333"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">App</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="40.692081"
+ y="563.71997"
+ id="text878"><tspan
+ sodipodi:role="line"
+ id="tspan876"
+ x="40.692081"
+ y="563.71997"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">Lib</tspan></text>
+ <rect
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#c5c9c7;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect854-9-9"
+ width="58.738262"
+ height="66.901787"
+ x="19.325045"
+ y="526.41656" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="31.886417"
+ y="585.92139"
+ id="text882"><tspan
+ sodipodi:role="line"
+ id="tspan880"
+ x="31.886417"
+ y="585.92139"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">Config</tspan></text>
+ <rect
+ transform="rotate(-90)"
+ y="-34.720181"
+ x="-772.12939"
+ height="14.9375"
+ width="230.69272"
+ id="rect815"
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#c5c9c7;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <text
+ transform="rotate(-90)"
+ id="text819"
+ y="-23.857788"
+ x="-677.57135"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ xml:space="preserve"><tspan
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1"
+ y="-23.857788"
+ x="-677.57135"
+ id="tspan817"
+ sodipodi:role="line">Query AST</tspan></text>
+ <rect
+ transform="rotate(-90)"
+ y="-61.77615"
+ x="-772.12897"
+ height="15.004211"
+ width="230.69226"
+ id="rect815-3"
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#c5c9c7;stroke-width:1.00091;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <text
+ transform="rotate(-90)"
+ id="text819-6"
+ y="-50.361935"
+ x="-670.42908"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ xml:space="preserve"><tspan
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1"
+ y="-50.361935"
+ x="-670.42908"
+ id="tspan817-7"
+ sodipodi:role="line">Schema</tspan></text>
+ <rect
+ transform="rotate(-90)"
+ y="-7.7468448"
+ x="-772.1449"
+ height="15.036049"
+ width="230.70819"
+ id="rect815-3-7"
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#c5c9c7;stroke-width:0.969072;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <text
+ transform="matrix(0,-0.96614119,1.0350454,0,0,0)"
+ id="text819-6-5"
+ y="3.5665975"
+ x="-691.72668"
+ style="font-style:normal;font-weight:normal;font-size:10.2467px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.256167"
+ xml:space="preserve"><tspan
+ style="stroke-width:0.256167;fill:#c5c9c7;fill-opacity:1"
+ y="3.5665975"
+ x="-691.72668"
+ id="tspan817-7-9"
+ sodipodi:role="line">Utils</tspan></text>
+ <g
+ id="g1066-2"
+ transform="translate(79.507472,180.86615)" />
+ <path
+ style="fill:none;stroke:#c5c9c7;stroke-width:0.98072147;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 97.390174,771.63946 c 23.866256,0 0,-33.21047 23.866256,-33.21047 -23.866256,0 0,-33.2105 -23.866256,-33.2105"
+ id="path1406-3-2-6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="128.08542"
+ y="563.90021"
+ id="text1481"><tspan
+ sodipodi:role="line"
+ id="tspan1479"
+ x="128.08542"
+ y="563.90021"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">Front</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="128.52983"
+ y="653.37573"
+ id="text1481-1"><tspan
+ sodipodi:role="line"
+ id="tspan1479-5"
+ x="128.52983"
+ y="653.37573"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">Center</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="128.08542"
+ y="742.37445"
+ id="text1481-5"><tspan
+ sodipodi:role="line"
+ id="tspan1479-4"
+ x="128.08542"
+ y="742.37445"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">Back</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="-51.901089"
+ y="536.83838"
+ id="text1481-7"><tspan
+ sodipodi:role="line"
+ id="tspan1479-6"
+ x="-51.901089"
+ y="536.83838"
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1">Envelope</tspan></text>
+ <path
+ style="fill:none;stroke:#c5c9c7;stroke-width:0.98072147;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 97.390174,682.73371 c 23.866246,0 0,-33.21047 23.866246,-33.21047 -23.866246,0 0,-33.2105 -23.866246,-33.2105"
+ id="path1406-3-2-6-1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:none;stroke:#c5c9c7;stroke-width:0.98072147;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 97.390174,593.32795 c 23.866246,0 0,-33.21047 23.866246,-33.21047 -23.866246,0 0,-33.2105 -23.866246,-33.2105"
+ id="path1406-3-2-6-9"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <text
+ id="text1593"
+ y="491.35275"
+ x="-4.7864752"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#c5c9c7;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ xml:space="preserve"><tspan
+ style="stroke-width:0.264583;fill:#c5c9c7;fill-opacity:1"
+ y="491.35275"
+ x="-4.7864752"
+ id="tspan1591"
+ sodipodi:role="line">Client</tspan></text>
+ <g
+ transform="translate(-64.429786,152.46769)"
+ style="stroke:#c5c9c7;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="g1110">
+ <circle
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:0.991416;stroke:#c5c9c7;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="path1089"
+ cx="75.138489"
+ cy="287.25885"
+ r="13.76438" />
+ <ellipse
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:0.991416;stroke:#c5c9c7;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="path1091"
+ cx="75.138489"
+ cy="352.85028"
+ rx="30.794813"
+ ry="49.827057"
+ clip-path="url(#clipPath1104)" />
+ </g>
+ <path
+ style="mix-blend-mode:normal;fill:none;fill-opacity:0.101961;stroke:#c5c9c7;stroke-width:0.718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker5768);marker-end:url(#Arrow2Mend);paint-order:normal"
+ d="m -26.95943,522.5725 c 0,-0.24385 0.07083,-4.09079 0.07878,-4.32466 0.761115,-22.40679 37.602027,0.47268 37.602027,-22.88944 0,23.36145 36.838813,0.48396 37.601969,22.88752 0.008,0.23448 0.0397,4.10164 0.0397,4.34615"
+ id="path1406-3-2"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscsc" />
+ <rect
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#c5c9c7;stroke-width:0.96907479;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect815-3-7-7"
+ width="69.081276"
+ height="15.036072"
+ x="-61.792068"
+ y="526.40063" />
+ </g>
+</svg>
diff --git a/doc/source/_static/arch_light.png b/doc/source/_static/arch_light.png
new file mode 100644
index 0000000..c210ecf
--- /dev/null
+++ b/doc/source/_static/arch_light.png
Binary files differ
diff --git a/doc/source/_static/arch_light.svg b/doc/source/_static/arch_light.svg
new file mode 100644
index 0000000..e93694c
--- /dev/null
+++ b/doc/source/_static/arch_light.svg
@@ -0,0 +1,499 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="226.45284mm"
+ height="347.16772mm"
+ viewBox="0 0 226.45283 347.16772"
+ version="1.1"
+ id="svg8"
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
+ sodipodi:docname="arch_light.svg"
+ inkscape:export-filename="/home/matthias/projects/black_star/modules/bsfs/doc/source/_static/arch_light.png"
+ inkscape:export-xdpi="36.581741"
+ inkscape:export-ydpi="36.581741"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs
+ id="defs2">
+ <marker
+ style="overflow:visible"
+ id="marker5768"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow2Mstart"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.6) translate(0,0)"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ style="stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round"
+ id="path5499" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow2Mend"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow2Mend"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.6) rotate(180) translate(0,0)"
+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+ style="stroke:context-stroke;fill-rule:evenodd;fill:context-stroke;stroke-width:0.62500000;stroke-linejoin:round;"
+ id="path5502" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow1Send"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow1Send"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.2) rotate(180) translate(6,0)"
+ style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ id="path5490" />
+ </marker>
+ <marker
+ style="overflow:visible;"
+ id="Arrow1Mend"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mend"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.4) rotate(180) translate(10,0)"
+ style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt;"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ id="path5484" />
+ </marker>
+ <marker
+ style="overflow:visible"
+ id="Arrow1Mstart"
+ refX="0.0"
+ refY="0.0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mstart"
+ inkscape:isstock="true">
+ <path
+ transform="scale(0.4) translate(10,0)"
+ style="fill-rule:evenodd;fill:context-stroke;stroke:context-stroke;stroke-width:1.0pt"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ id="path5481" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Mstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Mstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path1226"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="scale(0.6)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker2003"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow2Mend">
+ <path
+ transform="scale(-0.6)"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
+ id="path2001"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker1943"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Mend">
+ <path
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path1941"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:isstock="true"
+ style="overflow:visible"
+ id="marker1883"
+ refX="0"
+ refY="0"
+ orient="auto"
+ inkscape:stockid="Arrow1Send">
+ <path
+ transform="matrix(-0.2,0,0,-0.2,-1.2,0)"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
+ d="M 0,0 5,-5 -12.5,0 5,5 Z"
+ id="path1881"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <inkscape:path-effect
+ effect="bspline"
+ id="path-effect1392"
+ is_visible="true"
+ weight="33.333333"
+ steps="2"
+ helper_size="0"
+ apply_no_weight="true"
+ apply_with_weight="true"
+ only_selected="false" />
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath1104">
+ <rect
+ ry="1.9402644e-06"
+ y="299.74707"
+ x="38.425957"
+ height="39.287846"
+ width="72.863937"
+ id="rect1106"
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:0.99141636;stroke:#ab0000;stroke-width:1.50988853;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ </clipPath>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.48410751"
+ inkscape:cx="532.93947"
+ inkscape:cy="378.01521"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-text-baseline="true"
+ inkscape:snap-page="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:snap-global="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1920"
+ inkscape:window-height="1147"
+ inkscape:window-x="0"
+ inkscape:window-y="29"
+ inkscape:window-maximized="1"
+ inkscape:pagecheckerboard="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(62.276606,-425.46216)">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="32.604725"
+ y="629.79327"
+ id="text858"><tspan
+ sodipodi:role="line"
+ id="tspan856"
+ x="32.604725"
+ y="629.79327"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">Graph</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="31.855417"
+ y="653.12"
+ id="text862"><tspan
+ sodipodi:role="line"
+ id="tspan860"
+ x="31.855417"
+ y="653.12"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">Nodes</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="41.7075"
+ y="675.25732"
+ id="text866"><tspan
+ sodipodi:role="line"
+ id="tspan864"
+ x="41.7075"
+ y="675.25732"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">AC</tspan></text>
+ <rect
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect854-9-6"
+ width="58.738262"
+ height="66.901787"
+ x="19.325045"
+ y="615.82233" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="26.010805"
+ y="742.52582"
+ id="text870"><tspan
+ sodipodi:role="line"
+ id="tspan868"
+ x="26.010805"
+ y="742.52582"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">Backend</tspan></text>
+ <rect
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect854-9-7"
+ width="58.738262"
+ height="66.901787"
+ x="19.325045"
+ y="705.22809" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="38.606937"
+ y="541.41333"
+ id="text874"><tspan
+ sodipodi:role="line"
+ id="tspan872"
+ x="38.606937"
+ y="541.41333"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">App</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="40.692081"
+ y="563.71997"
+ id="text878"><tspan
+ sodipodi:role="line"
+ id="tspan876"
+ x="40.692081"
+ y="563.71997"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">Lib</tspan></text>
+ <rect
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect854-9-9"
+ width="58.738262"
+ height="66.901787"
+ x="19.325045"
+ y="526.41656" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="31.886417"
+ y="585.92139"
+ id="text882"><tspan
+ sodipodi:role="line"
+ id="tspan880"
+ x="31.886417"
+ y="585.92139"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">Config</tspan></text>
+ <rect
+ transform="rotate(-90)"
+ y="-34.720181"
+ x="-772.12939"
+ height="14.9375"
+ width="230.69272"
+ id="rect815"
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <text
+ transform="rotate(-90)"
+ id="text819"
+ y="-23.857788"
+ x="-677.57135"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ xml:space="preserve"><tspan
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1"
+ y="-23.857788"
+ x="-677.57135"
+ id="tspan817"
+ sodipodi:role="line">Query AST</tspan></text>
+ <rect
+ transform="rotate(-90)"
+ y="-61.77615"
+ x="-772.12897"
+ height="15.004211"
+ width="230.69226"
+ id="rect815-3"
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.00091;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <text
+ transform="rotate(-90)"
+ id="text819-6"
+ y="-50.361935"
+ x="-670.42908"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ xml:space="preserve"><tspan
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1"
+ y="-50.361935"
+ x="-670.42908"
+ id="tspan817-7"
+ sodipodi:role="line">Schema</tspan></text>
+ <rect
+ transform="rotate(-90)"
+ y="-7.7468448"
+ x="-772.1449"
+ height="15.036049"
+ width="230.70819"
+ id="rect815-3-7"
+ style="vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.969072;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+ <text
+ transform="matrix(0,-0.96614119,1.0350454,0,0,0)"
+ id="text819-6-5"
+ y="3.5665975"
+ x="-691.72668"
+ style="font-style:normal;font-weight:normal;font-size:10.2467px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.256167"
+ xml:space="preserve"><tspan
+ style="stroke-width:0.256167;fill:#000000;fill-opacity:1"
+ y="3.5665975"
+ x="-691.72668"
+ id="tspan817-7-9"
+ sodipodi:role="line">Utils</tspan></text>
+ <g
+ id="g1066-2"
+ transform="translate(79.507472,180.86615)" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.98072147;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 97.390174,771.63946 c 23.866256,0 0,-33.21047 23.866256,-33.21047 -23.866256,0 0,-33.2105 -23.866256,-33.2105"
+ id="path1406-3-2-6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="128.08542"
+ y="563.90021"
+ id="text1481"><tspan
+ sodipodi:role="line"
+ id="tspan1479"
+ x="128.08542"
+ y="563.90021"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">Front</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="128.52983"
+ y="653.37573"
+ id="text1481-1"><tspan
+ sodipodi:role="line"
+ id="tspan1479-5"
+ x="128.52983"
+ y="653.37573"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">Center</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="128.08542"
+ y="742.37445"
+ id="text1481-5"><tspan
+ sodipodi:role="line"
+ id="tspan1479-4"
+ x="128.08542"
+ y="742.37445"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">Back</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ x="-51.901089"
+ y="536.83838"
+ id="text1481-7"><tspan
+ sodipodi:role="line"
+ id="tspan1479-6"
+ x="-51.901089"
+ y="536.83838"
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1">Envelope</tspan></text>
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.98072147;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 97.390174,682.73371 c 23.866246,0 0,-33.21047 23.866246,-33.21047 -23.866246,0 0,-33.2105 -23.866246,-33.2105"
+ id="path1406-3-2-6-1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:0.98072147;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 97.390174,593.32795 c 23.866246,0 0,-33.21047 23.866246,-33.21047 -23.866246,0 0,-33.2105 -23.866246,-33.2105"
+ id="path1406-3-2-6-9"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <text
+ id="text1593"
+ y="491.35275"
+ x="-4.7864752"
+ style="font-style:normal;font-weight:normal;font-size:10.5833px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583"
+ xml:space="preserve"><tspan
+ style="stroke-width:0.264583;fill:#000000;fill-opacity:1"
+ y="491.35275"
+ x="-4.7864752"
+ id="tspan1591"
+ sodipodi:role="line">Client</tspan></text>
+ <g
+ transform="translate(-64.429786,152.46769)"
+ style="stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="g1110">
+ <circle
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:0.991416;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="path1089"
+ cx="75.138489"
+ cy="287.25885"
+ r="13.76438" />
+ <ellipse
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:0.991416;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="path1091"
+ cx="75.138489"
+ cy="352.85028"
+ rx="30.794813"
+ ry="49.827057"
+ clip-path="url(#clipPath1104)" />
+ </g>
+ <path
+ style="mix-blend-mode:normal;fill:none;fill-opacity:0.101961;stroke:#000000;stroke-width:0.718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#marker5768);marker-end:url(#Arrow2Mend);paint-order:normal"
+ d="m -26.95943,522.5725 c 0,-0.24385 0.07083,-4.09079 0.07878,-4.32466 0.761115,-22.40679 37.602027,0.47268 37.602027,-22.88944 0,23.36145 36.838813,0.48396 37.601969,22.88752 0.008,0.23448 0.0397,4.10164 0.0397,4.34615"
+ id="path1406-3-2"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscsc" />
+ <rect
+ style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.96907479;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+ id="rect815-3-7-7"
+ width="69.081276"
+ height="15.036072"
+ x="-61.792068"
+ y="526.40063" />
+ </g>
+</svg>
diff --git a/doc/source/architecture.rst b/doc/source/architecture.rst
new file mode 100644
index 0000000..4cca49a
--- /dev/null
+++ b/doc/source/architecture.rst
@@ -0,0 +1,87 @@
+
+Architecture
+============
+
+The BSFS stack can be coarsely divided into four parts (see the image below).
+
+* Envelope: Essentials and utils used throughout the whole codebase.
+* Front: End-user applications and APIs.
+* Center: The core interfaces and functionality.
+* Back: The triple store backends.
+
+Details of these components are given in the sections below.
+
+
+.. image:: _static/arch_light.png
+ :class: only-light
+
+.. image:: _static/arch_dark.png
+ :class: only-dark
+
+
+Envelope
+--------
+
+Most notably, the envelope covers the :class:`Schema <bsfs.schema.schema.Schema>` and the :mod:`Query syntax trees (AST) <bsfs.query.ast>`.
+Both of them essential for all parts of the BSFS stack.
+For example, the schema is specified by the user via the :func:`Migrate <bsfs.apps.migrate.main>` command, checked and extended by the :class:`Graph <bsfs.graph.graph.Graph>`, and ultimately stored by a :class:`Triple Store backend <bsfs.triple_store.base.TripleStoreBase>`.
+Similarly, the Query AST may be provided by a caller and is translated to a database query by a backend.
+In addition, the envelope also contains some classes to handle URIs:
+:class:`URI <bsfs.utils.uri.URI>` defines the URI base class,
+:class:`Namespace <bsfs.namespace.Namespace>` provides shortcuts to generate URIs, and
+:mod:`UUID <bsfs.utils.uuid>` is used to generate unique URIs.
+
+
+Front
+-----
+
+The front consists of exposed interfaces such as end-user applications or APIs,
+and all utils needed to offer this functionality.
+See :mod:`bsfs.apps` and :mod:`bsfs.front`.
+
+
+Center
+------
+
+The heart of BSFS is grouped around the :mod:`bsfs.graph` module.
+These classes provide the interface to navigate and manipulate the file graph
+in a safe and programmer friendly manner.
+Some of them are indirectly exposed through the higher-level APIs.
+
+The two core design principles of BSFS are the focus on nodes and batch processing.
+They are realized in the the Graph and Nodes classes.
+The :class:`Graph class <bsfs.graph.graph.Graph>` manages the graph as a whole,
+and offers methods to get a specific set of Nodes.
+In turn, the :class:`Nodes class <bsfs.graph.nodes.Nodes>` represents such a set of nodes,
+and performs operations on the whole node set at once.
+Besides, the :mod:`bsfs.graph` module also comes with some syntactic sugar.
+
+Example::
+
+ # Open a file graph.
+ from bsfs import Open, ns
+ graph = Open(...)
+ # Get all nodes of type File.
+ nodes = graph.all(ns.bsfs.File)
+ # Set the author of all nodes at once.
+ nodes.set(ns.bse.author, 'Myself')
+ # Retrieve the author of all nodes at once.
+ set(nodes.get(ns.bse.author, node=False))
+ # Same as above, but shorter.
+ set(nodes.comment(node=False))
+
+
+Back
+----
+
+There are various graph databases (e.g., `RDFLib`_, `Blazegraph`_, `Titan`_, etc.)
+and it would be foolish to replicate the work that others have done.
+Instead, we use third-party stores that take care of how to store and manage the data.
+The :class:`Backend base class <bsfs.triple_store.base.TripleStoreBase>` defines the
+interface to integrate any such third-party store to BSFS.
+Besides storing the data, a triple store backend also need to track the current schema.
+
+
+.. _RDFLib: https://rdflib.readthedocs.io/en/stable/index.html
+.. _Blazegraph: https://blazegraph.com/
+.. _Titan: http://titan.thinkaurelius.com/
diff --git a/doc/source/concepts.rst b/doc/source/concepts.rst
new file mode 100644
index 0000000..9c2ed43
--- /dev/null
+++ b/doc/source/concepts.rst
@@ -0,0 +1,98 @@
+
+Core concepts
+=============
+
+In the following, we present a few core concepts that should help in understanding the BSFS operations and codebase.
+
+
+Graph storage
+-------------
+
+`RDF`_ describes a network or graph like the file graph as a set of
+*(subject, predicate, object)* triples.
+*Subject* is the identifier of the source node,
+*object* is the identifier of the target node (or a literal value),
+and *predicate* is the type of relation between the source node and the target.
+As suggested by `RDF`_, we use URIs to identify nodes and predicates.
+For example, a triple that assigns me as the author of a file could look like this::
+
+ <http://example.com/file#1234> <https://bsfs.io/schema/Entity#author> <http://example.com/me>
+
+Note that alternatively, the *object* could also be a literal value ("me")::
+
+ <http://example.com/file#1234> <https://bsfs.io/schema/Entity#author> "me"
+
+There are a number of graph databases that support this or an analoguous paradigm,
+such as `RDFLib`_, `Blazegraph`_, `TypeDB`_, `Titan`_,
+and `many more <https://en.wikipedia.org/wiki/Graph_database#List_of_graph_databases>`_.
+BSFS uses such a third-party graph database to store its file graph.
+
+As usual in database systems,
+we have to distinguish schema data (that coverns the structure of the storage)
+from instance data (the actual database content).
+Similar to relational database systems,
+both kinds of data can be represented as triples,
+and subsequently stored within the same graph storage
+(although one might need to separate them logically).
+In BSFS, we employ an explicit schema (see next section) that is managed alongside the data.
+
+
+
+Schema
+------
+
+BSFS ensures consistency across multiple distributed client applications
+by maintaining an explicit schema that governs node types and predicates.
+Furthermore, exposing the schema allows client to run a number of compatibility and validity checks
+locally, and a graph database may use the schema to optimize its storage or operations.
+
+In BSFS, the schema is initially provided by the system administrator
+(usually in the `Turtle`_ format)
+and subsequently stored by the backend.
+The default schema defines three root types
+(``bsfs:Node``, ``bsfs:Predicate``, and ``bsfs:Literal``),
+and BSFS expects any node, literal, or predicate to be derived from these roots.
+
+For example, a new predicate can be defined like so::
+
+ # define some abbreviations
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix bsfs: <http://schema.bsfs.io/>
+ prefix bse: <http://schema.bsfs.io/Entity#>
+
+ # define a node type
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+
+ # define a literal type
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ # define a predicate ("author of a node")
+ bse:author rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range xsd:string .
+
+BSFS checks all requests and rejects queries or operations that violate the schema.
+
+
+Querying
+--------
+
+BSFS at its core is not much more than a translator from a user query into a graph database query.
+It operates directly on three abstract syntax trees (AST),
+to run fetch, search, or sort, queries respectively.
+By not using an existing query language,
+we avoid an unnecessary and possibly expensive parsing step.
+Some routines create an AST internally (e.g., :func:`bsfs.graph.graph.Graph.all`),
+others accept an user-defined AST (e.g., :func:`bsfs.graph.graph.Graph.get`).
+One way or another, the AST is validated against the schema,
+and access control conditions are added.
+
+
+.. _RDF: https://www.w3.org/RDF/
+.. _RDFLib: https://rdflib.readthedocs.io/en/stable/index.html
+.. _Blazegraph: https://blazegraph.com/
+.. _Titan: http://titan.thinkaurelius.com/
+.. _TypeDB: https://vaticle.com/
+.. _Turtle: https://www.w3.org/TR/turtle/
+
+
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100644
index 0000000..6de4993
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,37 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = 'Black Star File System'
+copyright = '2023, Matthias Baumgartner'
+author = 'Matthias Baumgartner'
+release = '0.5'
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+ 'sphinx_copybutton',
+ 'sphinx.ext.autodoc',
+ ]
+
+templates_path = ['_templates']
+exclude_patterns = []
+
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = 'furo'
+html_static_path = ['_static']
+
+html_title = 'bsfs'
+html_theme_options = {
+ 'announcement': '<em>This project is under heavy development and subject to rapid changes. Use at your own discretion.</em>',
+ }
+
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 0000000..91d53f6
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,75 @@
+
+The Black Star File System
+==========================
+
+A file system has two roles: It has to specify how to write files to a medium, and it has to define how a user can access files.
+Most file systems focus on the first role and adopt the standard directory tree approach for the second role.
+It is of course necessary to solve the challenges of medium access, but we should not neglect the user's perspective.
+As a user, I mostly care about how how conveniently I can organize my data, and quickly I can access relevant information.
+The hierarchical approach is rather restrictive in this regard:
+You can only organize files in a directory tree [#f1]_, and search tasks often require third-party tools like `find`_ or `locate`_.
+
+Tagging file systems proposed an alternative file organization model.
+Instead of a placing files in directories, they assign one or more (user-defined) tags to each file.
+This increases the flexibility over a hierarchical data model,
+because you can group any combination of files, and each file can be a part of various groups.
+Semantic file systems push this idea one step further by trying to understand
+the data they're dealing with.
+For example, files can be grouped by their data type (documents), file format (odt),
+author (yourself), topic (information management), etc.
+The benefit for the user is that they can browse their files by association rather than by location --- similar to how we nagivate the Web.
+
+Clearly, the hierarchical approach is insufficient to organize this variety of information.
+Instead, we need a network of files,
+where they can be connected to each other, their properties, or to auxiliary nodes
+(such as tags, collections, etc.) under a given relationship.
+We call this the file graph.
+With the *Black Star File System (BSFS)*, you can store, manage, and query such a file graph.
+
+..
+ TODO: Clarify
+ * Different relationships
+ * Properties and auxiliary nodes
+
+ TODO: File graph image
+ TODO: SFS/TFS references
+
+ TODO: BSFS features
+ Within BSFS, you can store the file content, file metadata,
+ and content-derived information (e.g., features) alike.
+
+ Within the file graph, we link files directly,
+ through properties, or through intermediate nodes.
+
+The Black Star File System is designed with three query patterns in mind:
+navigation, search, and browsing.
+
+The **navigation** pattern describes the case when the user knows exactly what they want,
+and they already have an address or id of the target file.
+BSFS identifies each file with a unique URI,
+or you can quickly navigate to a file via its name or other file properties.
+
+A **search** occurs when the user lacks the specific address or identifier to a target file,
+but they have relatively clear and narrow search criteria.
+With BSFS, you can search by file properties (name, size), content (keywords, features),
+or associations to other files and auxiliary nodes (tags, collections).
+
+**Browsing** takes place when the user has only vague query criteria but wants to quickly scan and compare many files.
+In BSFS, you can browse along file associations and rank results by a variety of similarity metrics.
+
+.. toctree::
+ :maxdepth: 1
+
+ installation
+ concepts
+ architecture
+ api/modules
+
+
+.. [#f1] although links and similar techniques allow some deviation from this principle
+
+.. _find: https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-find.html#Invoking-find
+
+.. _locate: https://www.gnu.org/software/findutils/manual/html_node/find_html/Invoking-locate.html
+
+
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
new file mode 100644
index 0000000..4316136
--- /dev/null
+++ b/doc/source/installation.rst
@@ -0,0 +1,43 @@
+
+Installation
+============
+
+Install *BSFS* via pip::
+
+ pip install --extra-index-url https://pip.bsfs.io bsfs
+
+This installs the `bsfs` python package as well as the `bsfs.app` command.
+It is recommended to install *bsfs* in a virtual environment (via `virtualenv`).
+
+
+License
+-------
+
+This project is released under the terms of the 3-clause BSD License.
+By downloading or using the application you agree to the license's terms and conditions.
+
+.. literalinclude:: ../../LICENSE
+
+
+Source
+------
+
+Check out our git repository::
+
+ git clone https://git.bsfs.io/bsfs.git
+
+You can further install *bsfs* via the ususal `setuptools <https://setuptools.pypa.io/en/latest/index.html>`_ commands from your bsfs source directory::
+
+ python setup.py develop
+
+For development, you also need to install some additional dependencies::
+
+ # code style discipline
+ pip install mypy coverage pylint
+
+ # documentation
+ pip install sphinx sphinx-copybutton furo
+
+ # packaging
+ pip install build
+
diff --git a/setup.py b/setup.py
index 243c73f..f6bd3e8 100644
--- a/setup.py
+++ b/setup.py
@@ -1,26 +1,44 @@
-from setuptools import setup
+from setuptools import setup, find_packages
import os
setup(
+ # package metadata
name='bsfs',
- version='0.0.1',
+ version='0.23.03',
author='Matthias Baumgartner',
- author_email='dev@igsor.net',
- description='A content aware graph file system.',
- long_description=open(os.path.join(os.path.dirname(__file__), 'README')).read(),
+ author_email='dev@bsfs.io',
+ description='A content-aware graph file system.',
+ long_description=open(os.path.join(os.path.dirname(__file__), 'README.md')).read(),
license='BSD',
license_files=('LICENSE', ),
- url='https://www.igsor.net/projects/blackstar/bsfs/',
- download_url='https://pip.igsor.net',
- packages=('bsfs', ),
+ url='https://www.bsfs.io/bsfs/',
+ download_url='https://pip.bsfs.io',
+
+ # packages
+ packages=find_packages(include=['bsfs']),
+ package_dir={'bsfs': 'bsfs'},
+ # data files are included if mentioned in MANIFEST.in
+ include_package_data=True,
+
+ # entrypoints
+ entry_points={
+ 'console_scripts': [
+ 'bsfs = bsfs.apps:main',
+ ],
+ },
+
+ # dependencies
+ python_requires=">=3.7",
install_requires=(
'rdflib', # schema and sparql storage
'hopcroftkarp', # ast matching
+ 'numpy', # distance functions for sparql store
),
- python_requires=">=3.7",
+ extras_require={
+ 'dev': ['coverage', 'mypy', 'pylint'],
+ 'doc': ['sphinx', 'furo', 'sphinx-copybutton'],
+ 'test': [],
+ 'build': ['build'],
+ },
)
-
-# FIXME: bsfs/graph/schema.nt
-# FIXME: bsfs.app
-
diff --git a/test/apps/schema-1.nt b/test/apps/schema-1.nt
index e57146d..4daf0ad 100644
--- a/test/apps/schema-1.nt
+++ b/test/apps/schema-1.nt
@@ -3,8 +3,8 @@ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
# common bsfs prefixes
-prefix bsfs: <http://bsfs.ai/schema/>
-prefix bse: <http://bsfs.ai/schema/Entity#>
+prefix bsfs: <http://schema.bsfs.io/core/>
+prefix bse: <http://schema.bsfs.io/core/Node/Entity#>
# essential nodes
bsfs:Entity rdfs:subClassOf bsfs:Node .
diff --git a/test/apps/schema-2.nt b/test/apps/schema-2.nt
index 4c5468f..4eb2467 100644
--- a/test/apps/schema-2.nt
+++ b/test/apps/schema-2.nt
@@ -3,8 +3,8 @@ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
# common bsfs prefixes
-prefix bsfs: <http://bsfs.ai/schema/>
-prefix bse: <http://bsfs.ai/schema/Entity#>
+prefix bsfs: <http://schema.bsfs.io/core/>
+prefix bse: <http://schema.bsfs.io/core/Node/Entity#>
# essential nodes
bsfs:Entity rdfs:subClassOf bsfs:Node .
diff --git a/test/apps/test_init.py b/test/apps/test_init.py
index bae6a68..59e10eb 100644
--- a/test/apps/test_init.py
+++ b/test/apps/test_init.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import contextlib
import io
diff --git a/test/apps/test_main.py b/test/apps/test_main.py
new file mode 100644
index 0000000..d61372f
--- /dev/null
+++ b/test/apps/test_main.py
@@ -0,0 +1,37 @@
+
+# imports
+import contextlib
+import io
+import json
+import unittest
+
+# objects to test
+from bsfs.apps import main
+
+
+## code ##
+
+class TestMain(unittest.TestCase):
+ def test_main(self):
+ # must at least pass an app
+ with contextlib.redirect_stderr(io.StringIO()):
+ self.assertRaises(SystemExit, main, [])
+ # app takes over
+ with contextlib.redirect_stderr(io.StringIO()):
+ self.assertRaises(SystemExit, main, ['init'])
+ outbuf = io.StringIO()
+ with contextlib.redirect_stdout(outbuf):
+ main(['init', 'sparql'])
+ self.assertEqual(json.loads(outbuf.getvalue()), {
+ 'Graph': {
+ 'user': 'http://example.com/me',
+ 'backend': {
+ 'SparqlStore': {}}}})
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/apps/test_migrate.py b/test/apps/test_migrate.py
index 230c032..618cb37 100644
--- a/test/apps/test_migrate.py
+++ b/test/apps/test_migrate.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import contextlib
import io
diff --git a/test/front/test_bsfs.py b/test/front/test_bsfs.py
index 0d7f383..8905bf8 100644
--- a/test/front/test_bsfs.py
+++ b/test/front/test_bsfs.py
@@ -1,14 +1,10 @@
-"""
-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.graph.ac import NullAC
from bsfs.triple_store import SparqlStore
from bsfs.utils import errors, URI
@@ -25,7 +21,7 @@ class TestBSFS(unittest.TestCase):
graph = Open(config)
self.assertIsInstance(graph, Graph)
self.assertIsInstance(graph._backend, SparqlStore)
- self.assertEqual(graph._user, URI('http://example.com/me'))
+ self.assertEqual(graph._ac, NullAC(graph._backend, URI('http://example.com/me')))
# invalid config raises an error
self.assertRaises(errors.ConfigError, Open, {})
diff --git a/test/front/test_builder.py b/test/front/test_builder.py
index 08f2027..875fa8a 100644
--- a/test/front/test_builder.py
+++ b/test/front/test_builder.py
@@ -1,14 +1,10 @@
-"""
-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.graph.ac import NullAC
from bsfs.triple_store import SparqlStore
from bsfs.utils import errors, URI
@@ -40,7 +36,7 @@ class TestBuilder(unittest.TestCase):
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'))
+ self.assertEqual(graph._ac, NullAC(graph._backend, URI('http://example.com/me')))
# cannot create an invalid graph
self.assertRaises(errors.ConfigError, build_graph, {'MyGraph': {}})
# must pass a dict
diff --git a/test/graph/ac/test_base.py b/test/graph/ac/test_base.py
new file mode 100644
index 0000000..addecd4
--- /dev/null
+++ b/test/graph/ac/test_base.py
@@ -0,0 +1,78 @@
+
+# imports
+import unittest
+
+# bsie imports
+from bsfs import schema as bsc
+from bsfs.namespace import ns
+from bsfs.query import ast
+from bsfs.triple_store import SparqlStore
+from bsfs.utils import URI
+
+# objects to test
+from bsfs.graph.ac.base import AccessControlBase
+
+
+## code ##
+
+class StubAC(AccessControlBase):
+ def is_protected_predicate(self, pred):
+ pass
+ def create(self, node_type, guids):
+ pass
+ def link_from_node(self, node_type, guids):
+ pass
+ def link_to_node(self, node_type, guids):
+ pass
+ def write_literal(self, node_type, guids):
+ pass
+ def createable(self, node_type, guids):
+ pass
+ def filter_read(self, node_type, query):
+ pass
+ def fetch_read(self, node_type, query):
+ pass
+
+
+class TestAccessControlBase(unittest.TestCase):
+ def setUp(self):
+ self.backend = SparqlStore()
+ self.user = URI('http://www.example.com/me')
+
+ def test_essentials(self):
+ ac = StubAC(self.backend, self.user)
+ # equal construction means equal instance
+ self.assertEqual(StubAC(self.backend, self.user), StubAC(self.backend, self.user))
+ self.assertEqual(hash(StubAC(self.backend, self.user)), hash(StubAC(self.backend, self.user)))
+ self.assertEqual(ac, StubAC(self.backend, self.user))
+ self.assertEqual(hash(ac), hash(StubAC(self.backend, self.user)))
+ # equivalence respects type
+ class Foo(): pass
+ self.assertNotEqual(ac, 1234)
+ self.assertNotEqual(hash(ac), hash(1234))
+ self.assertNotEqual(ac, 'hello')
+ self.assertNotEqual(hash(ac), hash('hello'))
+ self.assertNotEqual(ac, Foo())
+ self.assertNotEqual(hash(ac), hash(Foo()))
+ # equivalence respects backend
+ self.assertNotEqual(ac, StubAC(SparqlStore(), self.user))
+ self.assertNotEqual(hash(ac), hash(StubAC(SparqlStore(), self.user)))
+ # equivalence respects user
+ self.assertNotEqual(ac, StubAC(self.backend, URI('http://www.example.com/you')))
+ self.assertNotEqual(hash(ac), hash(StubAC(self.backend, URI('http://www.example.com/you'))))
+ # string conversion
+ self.assertEqual(str(ac), f'StubAC({self.user})')
+ self.assertEqual(repr(ac), f'StubAC({self.user})')
+ # string conversion respects user
+ self.assertEqual(str(StubAC(self.backend, URI('http://www.example.com/you'))),
+ f'StubAC(http://www.example.com/you)')
+ self.assertEqual(repr(StubAC(self.backend, URI('http://www.example.com/you'))),
+ f'StubAC(http://www.example.com/you)')
+
+
+## main ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+## EOF ##
diff --git a/test/graph/ac/test_null.py b/test/graph/ac/test_null.py
index e35852d..142bc23 100644
--- a/test/graph/ac/test_null.py
+++ b/test/graph/ac/test_null.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
@@ -20,6 +15,8 @@ from bsfs.graph.ac.null import NullAC
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestNullAC(unittest.TestCase):
def setUp(self):
self.backend = SparqlStore()
@@ -27,18 +24,19 @@ class TestNullAC(unittest.TestCase):
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bsm: <http://bsfs.ai/schema/Meta#>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsn: <https://schema.bsfs.io/core/Node#>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
# predicates mandated by Nodes
- bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ bsn:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
rdfs:range xsd:integer ;
bsfs:unique "true"^^xsd:boolean .
@@ -64,10 +62,40 @@ class TestNullAC(unittest.TestCase):
self.p_author = self.backend.schema.predicate(ns.bse.author)
self.p_filesize = self.backend.schema.predicate(ns.bse.filesize)
self.p_tag = self.backend.schema.predicate(ns.bse.tag)
- self.p_created = self.backend.schema.predicate(ns.bsm.t_created)
+ self.p_created = self.backend.schema.predicate(ns.bsn.t_created)
self.ent_type = self.backend.schema.node(ns.bsfs.Entity)
self.ent_ids = {URI('http://www.example.com/me/entity#1234'), URI('http://www.example.com/me/entity#4321')}
+ def test_essentials(self):
+ ac = NullAC(self.backend, self.user)
+ # equal construction means equal instance
+ self.assertEqual(NullAC(self.backend, self.user), NullAC(self.backend, self.user))
+ self.assertEqual(hash(NullAC(self.backend, self.user)), hash(NullAC(self.backend, self.user)))
+ self.assertEqual(ac, NullAC(self.backend, self.user))
+ self.assertEqual(hash(ac), hash(NullAC(self.backend, self.user)))
+ # equivalence respects type
+ class Foo(): pass
+ self.assertNotEqual(ac, 1234)
+ self.assertNotEqual(hash(ac), hash(1234))
+ self.assertNotEqual(ac, 'hello')
+ self.assertNotEqual(hash(ac), hash('hello'))
+ self.assertNotEqual(ac, Foo())
+ self.assertNotEqual(hash(ac), hash(Foo()))
+ # equivalence respects backend
+ self.assertNotEqual(ac, NullAC(SparqlStore(), self.user))
+ self.assertNotEqual(hash(ac), hash(NullAC(SparqlStore(), self.user)))
+ # equivalence respects user
+ self.assertNotEqual(ac, NullAC(self.backend, URI('http://www.example.com/you')))
+ self.assertNotEqual(hash(ac), hash(NullAC(self.backend, URI('http://www.example.com/you'))))
+ # string conversion
+ self.assertEqual(str(ac), f'NullAC({self.user})')
+ self.assertEqual(repr(ac), f'NullAC({self.user})')
+ # string conversion respects user
+ self.assertEqual(str(NullAC(self.backend, URI('http://www.example.com/you'))),
+ f'NullAC(http://www.example.com/you)')
+ self.assertEqual(repr(NullAC(self.backend, URI('http://www.example.com/you'))),
+ f'NullAC(http://www.example.com/you)')
+
def test_is_protected_predicate(self):
ac = NullAC(self.backend, self.user)
self.assertTrue(ac.is_protected_predicate(self.p_created))
@@ -101,8 +129,19 @@ class TestNullAC(unittest.TestCase):
ast.filter.Any(ns.bse.tag, ast.filter.Is('http://example.com/tag#4321')),
ast.filter.Any(ns.bse.author, ast.filter.Equals('Me, Myself, and I')))
ac = NullAC(self.backend, self.user)
+ # NullAC returns query
self.assertEqual(query, ac.filter_read(self.ent_type, query))
- return query
+ # query can be none
+ self.assertIsNone(ac.filter_read(self.ent_type, None))
+
+ def test_fetch_read(self):
+ query = ast.fetch.All(
+ ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bse.label, 'tag_label')),
+ ast.fetch.Node(ns.bse.tag, 'tag_node'),
+ ast.fetch.Value(ns.bse.iso, 'iso'))
+ ac = NullAC(self.backend, self.user)
+ # NullAC returns query
+ self.assertEqual(query, ac.fetch_read(self.ent_type, query))
## main ##
diff --git a/test/graph/test_graph.py b/test/graph/test_graph.py
index 5db1fd2..167168d 100644
--- a/test/graph/test_graph.py
+++ b/test/graph/test_graph.py
@@ -1,14 +1,12 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
+from functools import reduce
+import operator
import unittest
# bsie imports
from bsfs import schema
+from bsfs.graph.ac import NullAC
from bsfs.graph.nodes import Nodes
from bsfs.namespace import ns
from bsfs.query import ast
@@ -21,96 +19,95 @@ from bsfs.graph.graph import Graph
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestGraph(unittest.TestCase):
def setUp(self):
- self.user = URI('http://example.com/me')
self.backend = SparqlStore.Open()
self.backend.schema = schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
''')
+ self.user = URI('http://example.com/me')
+ self.ac = NullAC(self.backend, self.user)
def test_str(self):
- self.assertEqual(str(Graph(self.backend, self.user)),
- 'Graph(SparqlStore(uri=None), http://example.com/me)')
- self.assertEqual(repr(Graph(self.backend, self.user)),
- 'Graph(backend=SparqlStore(uri=None), user=http://example.com/me)')
+ self.assertEqual(str(Graph(self.backend, self.ac)),
+ 'Graph(SparqlStore(uri=None))')
+ self.assertEqual(repr(Graph(self.backend, self.ac)),
+ 'Graph(SparqlStore(uri=None), NullAC(http://example.com/me))')
# str respects backend
class Foo(SparqlStore): pass
- self.assertEqual(str(Graph(Foo.Open(), self.user)),
- 'Graph(Foo(uri=None), http://example.com/me)')
- self.assertEqual(repr(Graph(Foo.Open(), self.user)),
- 'Graph(backend=Foo(uri=None), user=http://example.com/me)')
+ self.assertEqual(str(Graph(Foo.Open(), self.ac)),
+ 'Graph(Foo(uri=None))')
+ self.assertEqual(repr(Graph(Foo.Open(), self.ac)),
+ 'Graph(Foo(uri=None), NullAC(http://example.com/me))')
# str respect user
- self.assertEqual(str(Graph(self.backend, URI('http://example.com/you'))),
- 'Graph(SparqlStore(uri=None), http://example.com/you)')
- self.assertEqual(repr(Graph(self.backend, URI('http://example.com/you'))),
- 'Graph(backend=SparqlStore(uri=None), user=http://example.com/you)')
+ self.assertEqual(str(Graph(self.backend, NullAC(self.backend, URI('http://example.com/you')))),
+ 'Graph(SparqlStore(uri=None))')
+ self.assertEqual(repr(Graph(self.backend, NullAC(self.backend, URI('http://example.com/you')))),
+ 'Graph(SparqlStore(uri=None), NullAC(http://example.com/you))')
# str respects type
class Bar(Graph): pass
- self.assertEqual(str(Bar(self.backend, self.user)),
- 'Bar(SparqlStore(uri=None), http://example.com/me)')
- self.assertEqual(repr(Bar(self.backend, self.user)),
- 'Bar(backend=SparqlStore(uri=None), user=http://example.com/me)')
+ self.assertEqual(str(Bar(self.backend, self.ac)),
+ 'Bar(SparqlStore(uri=None))')
+ self.assertEqual(repr(Bar(self.backend, self.ac)),
+ 'Bar(SparqlStore(uri=None), NullAC(http://example.com/me))')
def test_equality(self):
- graph = Graph(self.backend, self.user)
+ graph = Graph(self.backend, self.ac)
# instance is equal to itself
self.assertEqual(graph, graph)
self.assertEqual(hash(graph), hash(graph))
# instance is equal to a clone
- self.assertEqual(graph, Graph(self.backend, self.user))
- self.assertEqual(hash(graph), hash(Graph(self.backend, self.user)))
+ self.assertEqual(graph, Graph(self.backend, self.ac))
+ self.assertEqual(hash(graph), hash(Graph(self.backend, self.ac)))
# equality respects backend
- self.assertNotEqual(graph, Graph(SparqlStore.Open(), self.user))
- self.assertNotEqual(hash(graph), hash(Graph(SparqlStore.Open(), self.user)))
+ self.assertNotEqual(graph, Graph(SparqlStore.Open(), self.ac))
+ self.assertNotEqual(hash(graph), hash(Graph(SparqlStore.Open(), self.ac)))
# equality respects user
self.assertNotEqual(graph, Graph(self.backend, URI('http://example.com/you')))
self.assertNotEqual(hash(graph), hash(Graph(self.backend, URI('http://example.com/you'))))
def test_essentials(self):
- graph = Graph(self.backend, self.user)
+ graph = Graph(self.backend, self.ac)
# schema
self.assertEqual(graph.schema, self.backend.schema)
self.assertRaises(AttributeError, setattr, graph, 'schema', None)
def test_node(self):
- graph = Graph(self.backend, self.user)
+ graph = Graph(self.backend, self.ac)
guid = URI('http://example.com/me/entity#1234')
# returns a Nodes instance
self.assertEqual(
graph.node(ns.bsfs.Entity, guid),
- Nodes(self.backend, self.user, graph.schema.node(ns.bsfs.Entity), {guid}))
+ Nodes(self.backend, self.ac, graph.schema.node(ns.bsfs.Entity), {guid}))
# node_type must be in the schema
self.assertRaises(KeyError, graph.node, ns.bsfs.Invalid, guid)
def test_nodes(self):
- graph = Graph(self.backend, self.user)
+ graph = Graph(self.backend, self.ac)
guids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
# returns a Nodes instance
self.assertEqual(
graph.nodes(ns.bsfs.Entity, guids),
- Nodes(self.backend, self.user, graph.schema.node(ns.bsfs.Entity), guids))
+ Nodes(self.backend, self.ac, graph.schema.node(ns.bsfs.Entity), guids))
# node_type must be in the schema
self.assertRaises(KeyError, graph.nodes, ns.bsfs.Invalid, guids)
- def test_all(self):
- graph = Graph(self.backend, self.user)
- # resulting nodes can be empty
- self.assertEqual(graph.all(ns.bsfs.Entity),
- Nodes(self.backend, self.user, graph.schema.node(ns.bsfs.Entity), set()))
- # resulting nodes contains all nodes of the respective type
- guids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
- self.backend.create(graph.schema.node(ns.bsfs.Entity), guids)
- self.assertEqual(graph.all(ns.bsfs.Entity),
- Nodes(self.backend, self.user, graph.schema.node(ns.bsfs.Entity), guids))
+ def test_empty(self):
+ graph = Graph(self.backend, self.ac)
+ # returns a Nodes instance
+ self.assertEqual(
+ graph.empty(ns.bsfs.Entity),
+ Nodes(self.backend, self.ac, graph.schema.node(ns.bsfs.Entity), set()))
# node_type must be in the schema
- self.assertRaises(KeyError, graph.all, ns.bsfs.Invalid)
+ self.assertRaises(KeyError, graph.empty, ns.bsfs.Invalid)
def test_migrate(self):
# setup
- graph = Graph(self.backend, self.user)
+ graph = Graph(self.backend, self.ac)
# argument must be a schema
class Foo(): pass
@@ -134,12 +131,13 @@ class TestGraph(unittest.TestCase):
target_1 = schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
bse:filename rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -159,13 +157,14 @@ class TestGraph(unittest.TestCase):
self.assertEqual(graph.schema, target_1 + schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bsm: <http://bsfs.ai/schema/Meta#>
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
- bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bsn: <https://schema.bsfs.io/core/Node#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:float rdfs:subClassOf bsl:Number .
+ bsn:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
- rdfs:range xsd:integer ;
+ rdfs:range xsd:float ;
bsfs:unique "true"^^xsd:boolean .
'''))
@@ -173,12 +172,13 @@ class TestGraph(unittest.TestCase):
target_2 = schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <http://schema.bsfs.io/core/>
+ prefix bse: <http://schema.bsfs.io/core/Node/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
bse:filename rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -200,24 +200,25 @@ class TestGraph(unittest.TestCase):
self.assertEqual(graph.schema, target_2 + schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bsm: <http://bsfs.ai/schema/Meta#>
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
- bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bsn: <https://schema.bsfs.io/core/Node#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:float rdfs:subClassOf bsl:Number .
+ bsn:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
- rdfs:range xsd:integer ;
+ rdfs:range xsd:float ;
bsfs:unique "true"^^xsd:boolean .
'''))
def test_get(self):
# setup
- graph = Graph(self.backend, self.user)
+ graph = Graph(self.backend, self.ac)
graph.migrate(schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
@@ -246,10 +247,10 @@ class TestGraph(unittest.TestCase):
graph.node(ns.bsfs.Tag, URI('http://example.com/tag#1234')).set(ns.bse.comment, 'foo')
graph.node(ns.bsfs.Tag, URI('http://example.com/tag#4321')).set(ns.bse.comment, 'bar')
- # get exception for invalid query
+ # invalid query raises exception
self.assertRaises(errors.ConsistencyError, graph.get, ns.bsfs.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Equals('hello world')))
- # query returns nodes
+ # get returns nodes
self.assertEqual(graph.get(ns.bsfs.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Is(tags))), ents)
self.assertEqual(graph.get(ns.bsfs.Entity, ast.filter.Any(ns.bse.comment, ast.filter.StartsWith('foo'))),
graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')))
@@ -260,6 +261,81 @@ class TestGraph(unittest.TestCase):
ast.filter.Any(ns.bse.tag, ast.filter.All(ns.bse.comment, ast.filter.Equals('bar'))))),
ents)
+ # query can be None
+ self.assertEqual(graph.get(ns.bsfs.Entity, None), ents)
+
+ def test_sorted(self):
+ # setup
+ graph = Graph(self.backend, self.ac)
+ graph.migrate(schema.from_string('''
+ prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ prefix xsd: <http://www.w3.org/2001/XMLSchema#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+
+ bsfs:Entity rdfs:subClassOf bsfs:Node .
+ bsfs:Tag rdfs:subClassOf bsfs:Node .
+ xsd:string rdfs:subClassOf bsfs:Literal .
+
+ bse:tag rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Entity ;
+ rdfs:range bsfs:Tag ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ bse:comment rdfs:subClassOf bsfs:Predicate ;
+ rdfs:domain bsfs:Node ;
+ rdfs:range xsd:string ;
+ bsfs:unique "false"^^xsd:boolean .
+
+ '''))
+ # add some instances
+ ents = [
+ # default is alphabetical order
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')),
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#4321')),
+ ]
+ tags = graph.nodes(ns.bsfs.Tag, {URI('http://example.com/tag#1234'), URI('http://example.com/tag#4321')})
+ # add some node links
+ reduce(operator.add, ents).set(ns.bse.tag, tags)
+ # add some literals
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')).set(ns.bse.comment, 'hello world')
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')).set(ns.bse.comment, 'foo')
+ graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234')).set(ns.bse.comment, 'foobar')
+ graph.node(ns.bsfs.Tag, URI('http://example.com/tag#1234')).set(ns.bse.comment, 'foo')
+ graph.node(ns.bsfs.Tag, URI('http://example.com/tag#4321')).set(ns.bse.comment, 'bar')
+
+ # invalid query raises exception
+ self.assertRaises(errors.ConsistencyError, list, graph.sorted(ns.bsfs.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Equals('hello world'))))
+
+ # get returns nodes
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Entity, ast.filter.Any(ns.bse.tag, ast.filter.Is(tags)))), ents)
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Entity, ast.filter.Any(ns.bse.comment, ast.filter.StartsWith('foo')))),
+ [graph.node(ns.bsfs.Entity, URI('http://example.com/entity#1234'))])
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Node, ast.filter.Any(ns.bse.comment, ast.filter.StartsWith('foo')))), [
+ graph.node(ns.bsfs.Node, URI('http://example.com/entity#1234')),
+ graph.node(ns.bsfs.Node, URI('http://example.com/tag#1234')),
+ ])
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Entity, ast.filter.Or(
+ ast.filter.Any(ns.bse.comment, ast.filter.EndsWith('bar')),
+ ast.filter.Any(ns.bse.tag, ast.filter.All(ns.bse.comment, ast.filter.Equals('bar')))))),
+ ents)
+
+ # query can be None
+ self.assertListEqual(list(graph.sorted(ns.bsfs.Entity, None)), ents)
+
+
+ def test_all(self):
+ graph = Graph(self.backend, self.ac)
+ # resulting nodes can be empty
+ self.assertEqual(graph.all(ns.bsfs.Entity),
+ Nodes(self.backend, self.ac, graph.schema.node(ns.bsfs.Entity), set()))
+ # resulting nodes contains all nodes of the respective type
+ guids = {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')}
+ self.backend.create(graph.schema.node(ns.bsfs.Entity), guids)
+ self.assertEqual(graph.all(ns.bsfs.Entity),
+ Nodes(self.backend, self.ac, graph.schema.node(ns.bsfs.Entity), guids))
+ # node_type must be in the schema
+ self.assertRaises(KeyError, graph.all, ns.bsfs.Invalid)
diff --git a/test/graph/test_nodes.py b/test/graph/test_nodes.py
index 6bb3ef3..afe7522 100644
--- a/test/graph/test_nodes.py
+++ b/test/graph/test_nodes.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
from functools import partial
import operator
@@ -14,6 +9,7 @@ import rdflib
# bsie imports
from bsfs import schema as bsc
+from bsfs.graph.ac import NullAC
from bsfs.graph.walk import Walk
from bsfs.namespace import Namespace, ns
from bsfs.triple_store.sparql import SparqlStore
@@ -25,7 +21,8 @@ from bsfs.graph.nodes import Nodes
## code ##
-bst = Namespace('http://bsfs.ai/schema/Tag')
+ns.bse = ns.bsfs.Entity()
+ns.bst = ns.bsfs.Tag()
class TestNodes(unittest.TestCase):
def setUp(self):
@@ -35,20 +32,21 @@ class TestNodes(unittest.TestCase):
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bsm: <http://bsfs.ai/schema/Meta#>
- prefix bse: <http://bsfs.ai/schema/Entity#>
- prefix bst: <http://bsfs.ai/schema/Tag#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsn: <https://schema.bsfs.io/core/Node#>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
bsfs:User rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
# predicates mandated by Nodes
- bsm:t_created rdfs:subClassOf bsfs:Predicate ;
+ bsn:t_created rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Node ;
rdfs:range xsd:integer ;
bsfs:unique "true"^^xsd:boolean .
@@ -91,22 +89,23 @@ class TestNodes(unittest.TestCase):
(rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.xsd.string), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Array)),
- (rdflib.URIRef(ns.bsfs.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Number)),
- (rdflib.URIRef(ns.bsm.t_created), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bsl.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Array)),
+ (rdflib.URIRef(ns.bsl.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Number)),
+ (rdflib.URIRef(ns.bsn.t_created), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.comment), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.author), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
- (rdflib.URIRef(bst.representative), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
- (rdflib.URIRef(bst.label), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bst.representative), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef(ns.bst.label), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
}
# Nodes constructor args
self.user = URI('http://example.com/me')
+ self.ac = NullAC(self.backend, self.user)
# set args
self.tag_type = self.backend.schema.node(ns.bsfs.Tag)
self.ent_type = self.backend.schema.node(ns.bsfs.Entity)
@@ -114,9 +113,9 @@ class TestNodes(unittest.TestCase):
self.p_filesize = self.backend.schema.predicate(ns.bse.filesize)
self.p_author = self.backend.schema.predicate(ns.bse.author)
self.p_tag = self.backend.schema.predicate(ns.bse.tag)
- self.p_representative = self.backend.schema.predicate(bst.representative)
- self.p_label = self.backend.schema.predicate(bst.label)
- self.t_created = self.backend.schema.predicate(ns.bsm.t_created)
+ self.p_representative = self.backend.schema.predicate(ns.bst.representative)
+ self.p_label = self.backend.schema.predicate(ns.bst.label)
+ self.t_created = self.backend.schema.predicate(ns.bsn.t_created)
self.ent_ids = {
URI('http://example.com/me/entity#1234'),
URI('http://example.com/me/entity#4321'),
@@ -126,67 +125,71 @@ class TestNodes(unittest.TestCase):
URI('http://example.com/me/tag#4321'),
}
+ def test_construct(self):
+ self.assertIsInstance(Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me-and-you'}), Nodes)
+ self.assertRaises(ValueError, Nodes, self.backend, self.ac, self.ent_type, {'http://example.com/me and you'})
+
def test_str(self):
# str baseline
- nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
- self.assertEqual(str(nodes), f'Nodes({self.ent_type}, {self.ent_ids})')
- self.assertEqual(repr(nodes), f'Nodes({self.backend}, {self.user}, {self.ent_type}, {self.ent_ids})')
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')})
+ self.assertEqual(str(nodes), f"Nodes({self.ent_type}, {{'http://example.com/me/entity#1234'}})")
+ self.assertEqual(repr(nodes), f"Nodes({self.backend}, {self.ac}, {self.ent_type}, {{'http://example.com/me/entity#1234'}})")
# str respects node_type
- nodes = Nodes(self.backend, self.user, self.tag_type, self.tag_ids)
- self.assertEqual(str(nodes), f'Nodes({self.tag_type}, {self.tag_ids})')
- self.assertEqual(repr(nodes), f'Nodes({self.backend}, {self.user}, {self.tag_type}, {self.tag_ids})')
+ nodes = Nodes(self.backend, self.ac, self.tag_type, {URI('http://example.com/me/tag#1234')})
+ self.assertEqual(str(nodes), f"Nodes({self.tag_type}, {{'http://example.com/me/tag#1234'}})")
+ self.assertEqual(repr(nodes), f"Nodes({self.backend}, {self.ac}, {self.tag_type}, {{'http://example.com/me/tag#1234'}})")
# str respects guids
- nodes = Nodes(self.backend, self.user, self.ent_type, {URI('http://example.com/me/entity#foo')})
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#foo')})
self.assertEqual(str(nodes), f'Nodes({self.ent_type}, {{\'http://example.com/me/entity#foo\'}})')
- self.assertEqual(repr(nodes), f'Nodes({self.backend}, {self.user}, {self.ent_type}, {{\'http://example.com/me/entity#foo\'}})')
+ self.assertEqual(repr(nodes), f'Nodes({self.backend}, {self.ac}, {self.ent_type}, {{\'http://example.com/me/entity#foo\'}})')
# repr respects backend
class Foo(SparqlStore): pass
backend = Foo.Open()
backend.schema = self.backend.schema
- nodes = Nodes(backend, self.user, self.ent_type, self.ent_ids)
- self.assertEqual(repr(nodes), f'Nodes({backend}, {self.user}, {self.ent_type}, {self.ent_ids})')
+ nodes = Nodes(backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')})
+ self.assertEqual(repr(nodes), f"Nodes({backend}, {self.ac}, {self.ent_type}, {{'http://example.com/me/entity#1234'}})")
# repr respects user
- nodes = Nodes(self.backend, URI('http://example.com/you'), self.ent_type, self.ent_ids)
- self.assertEqual(repr(nodes), f'Nodes({self.backend}, http://example.com/you, {self.ent_type}, {self.ent_ids})')
+ nodes = Nodes(self.backend, NullAC(self.backend, URI('http://example.com/you')), self.ent_type, {URI('http://example.com/me/entity#1234')})
+ self.assertEqual(repr(nodes), f"Nodes({self.backend}, NullAC(http://example.com/you), {self.ent_type}, {{'http://example.com/me/entity#1234'}})")
def test_equality(self):
- nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)
# instance is equal to itself
self.assertEqual(nodes, nodes)
self.assertEqual(hash(nodes), hash(nodes))
# instance is equal to a clone
- self.assertEqual(nodes, Nodes(self.backend, self.user, self.ent_type, self.ent_ids))
- self.assertEqual(Nodes(self.backend, self.user, self.ent_type, self.ent_ids), nodes)
- self.assertEqual(hash(nodes), hash(Nodes(self.backend, self.user, self.ent_type, self.ent_ids)))
+ self.assertEqual(nodes, Nodes(self.backend, self.ac, self.ent_type, self.ent_ids))
+ self.assertEqual(Nodes(self.backend, self.ac, self.ent_type, self.ent_ids), nodes)
+ self.assertEqual(hash(nodes), hash(Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)))
# equality respects backend
backend = SparqlStore.Open()
backend.schema = self.backend.schema
- self.assertNotEqual(nodes, Nodes(backend, self.user, self.ent_type, self.ent_ids))
- self.assertNotEqual(hash(nodes), hash(Nodes(backend, self.user, self.ent_type, self.ent_ids)))
+ self.assertNotEqual(nodes, Nodes(backend, self.ac, self.ent_type, self.ent_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(backend, self.ac, self.ent_type, self.ent_ids)))
# equality respects user
- self.assertNotEqual(nodes, Nodes(self.backend, URI('http://example.com/you'), self.ent_type, self.ent_ids))
- self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, URI('http://example.com/you'), self.ent_type, self.ent_ids)))
+ self.assertNotEqual(nodes, Nodes(self.backend, NullAC(self.backend, URI('http://example.com/you')), self.ent_type, self.ent_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, NullAC(self.backend, URI('http://example.com/you')), self.ent_type, self.ent_ids)))
# equality respects node_type
- self.assertNotEqual(nodes, Nodes(self.backend, self.user, self.tag_type, self.ent_ids))
- self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.user, self.tag_type, self.ent_ids)))
+ self.assertNotEqual(nodes, Nodes(self.backend, self.ac, self.tag_type, self.ent_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.ac, self.tag_type, self.ent_ids)))
# equality respects guids
- self.assertNotEqual(nodes, Nodes(self.backend, self.user, self.ent_type, self.tag_ids))
- self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.user, self.ent_type, self.tag_ids)))
+ self.assertNotEqual(nodes, Nodes(self.backend, self.ac, self.ent_type, self.tag_ids))
+ self.assertNotEqual(hash(nodes), hash(Nodes(self.backend, self.ac, self.ent_type, self.tag_ids)))
def test_properties(self):
# node_type
self.assertEqual(self.ent_type, Nodes(
- self.backend, self.user, self.ent_type, self.ent_ids).node_type)
+ self.backend, self.ac, self.ent_type, self.ent_ids).node_type)
self.assertEqual(self.tag_type, Nodes(
- self.backend, self.user, self.tag_type, self.tag_ids).node_type)
+ self.backend, self.ac, self.tag_type, self.tag_ids).node_type)
# guids
self.assertSetEqual(self.ent_ids, set(Nodes(
- self.backend, self.user, self.ent_type, self.ent_ids).guids))
+ self.backend, self.ac, self.ent_type, self.ent_ids).guids))
self.assertSetEqual(self.tag_ids, set(Nodes(
- self.backend, self.user, self.tag_type, self.tag_ids).guids))
+ self.backend, self.ac, self.tag_type, self.tag_ids).guids))
def test__ensure_nodes(self):
- nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)
# missing nodes are created
self.assertSetEqual(self.ent_ids, nodes._ensure_nodes(self.ent_type, self.ent_ids))
@@ -196,8 +199,8 @@ class TestNodes(unittest.TestCase):
# check triples
self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# entity definitions
- (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
# bookkeeping
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
@@ -207,8 +210,8 @@ class TestNodes(unittest.TestCase):
self.assertSetEqual(self.ent_ids, nodes._ensure_nodes(self.ent_type, self.ent_ids))
self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# entity definitions
- (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
# bookkeeping
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
@@ -222,20 +225,20 @@ class TestNodes(unittest.TestCase):
# check triples
self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# previous triples
- (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
# new triples
- (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
- (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
(rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)),
})
def test___set(self):
# setup
- nodes = Nodes(self.backend, self.user, self.ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
self.assertSetEqual(set(self.backend._graph), self.schema_triples | set())
set_ = nodes._Nodes__set
@@ -253,8 +256,8 @@ class TestNodes(unittest.TestCase):
# verify triples
self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# entity definitions
- (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
# bookkeeping
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
@@ -264,7 +267,7 @@ class TestNodes(unittest.TestCase):
})
# set node value
- tags = Nodes(self.backend, self.user, self.tag_type, {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
+ tags = Nodes(self.backend, self.ac, self.tag_type, {URI('http://example.com/me/tag#1234'), URI('http://example.com/me/tag#4321')})
set_(self.p_tag.uri, tags)
# get creation time from backend manually
time_triples = list(self.backend._graph.objects(rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri)))
@@ -272,15 +275,15 @@ class TestNodes(unittest.TestCase):
# verify triples
self.assertSetEqual(set(self.backend._graph), self.schema_triples | {
# previous values
- (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_ent_created, datatype=rdflib.XSD.integer)),
# tag definitions
- (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
- (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
# tag bookkeeping
(rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.URIRef(self.t_created.uri), rdflib.Literal(t_tag_created, datatype=rdflib.XSD.integer)),
@@ -296,29 +299,29 @@ class TestNodes(unittest.TestCase):
self.assertRaises(TypeError, set_, self.p_tag.uri, URI('http://example.com/me/tag#1234'))
# value's node_type must match the predicate's range
self.assertRaises(errors.ConsistencyError, set_, self.p_tag.uri,
- Nodes(self.backend, self.user, self.ent_type, self.ent_ids))
+ Nodes(self.backend, self.ac, self.ent_type, self.ent_ids))
def test_set(self):
self.assertSetEqual(set(self.backend._graph), self.schema_triples | set())
- nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)
# can set literal values
self.assertEqual(nodes, nodes.set(self.p_filesize.uri, 1234))
self.assertTrue(set(self.backend._graph).issuperset({
# nodes exist
- (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
# links exist
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
}))
# can set node values
- self.assertEqual(nodes, nodes.set(self.p_tag.uri, Nodes(self.backend, self.user, self.tag_type, self.tag_ids)))
+ self.assertEqual(nodes, nodes.set(self.p_tag.uri, Nodes(self.backend, self.ac, self.tag_type, self.tag_ids)))
self.assertTrue(set(self.backend._graph).issuperset({
# nodes exist
- (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
- (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
# links exist
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#1234')),
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_tag.uri), rdflib.URIRef('http://example.com/me/tag#4321')),
@@ -341,24 +344,28 @@ class TestNodes(unittest.TestCase):
self.assertSetEqual(curr, set(self.backend._graph))
# cannot assing multiple values to unique predicate
self.assertRaises(ValueError, nodes.set, self.p_author.uri,
- Nodes(self.backend, self.user, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')}))
+ Nodes(self.backend, self.ac, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')}))
self.assertSetEqual(curr, set(self.backend._graph))
+ # can set on empty nodes
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {})
+ self.assertEqual(nodes, nodes.set(self.p_filesize.uri, 1234))
+
def test_set_from_iterable(self):
self.assertSetEqual(set(self.backend._graph), self.schema_triples | set())
- nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)
# can set literal and node values simultaneously
self.assertEqual(nodes, nodes.set_from_iterable({
self.p_filesize.uri: 1234,
- self.p_tag.uri: Nodes(self.backend, self.user, self.tag_type, self.tag_ids),
+ self.p_tag.uri: Nodes(self.backend, self.ac, self.tag_type, self.tag_ids),
}.items()))
self.assertTrue(set(self.backend._graph).issuperset({
# nodes exist
- (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')),
- (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
- (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')),
+ (rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')),
+ (rdflib.URIRef('http://example.com/me/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
+ (rdflib.URIRef('http://example.com/me/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')),
# links exist
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.URIRef(self.p_filesize.uri), rdflib.Literal(1234, datatype=rdflib.XSD.integer)),
@@ -383,24 +390,30 @@ class TestNodes(unittest.TestCase):
self.assertSetEqual(curr, set(self.backend._graph))
# cannot assing multiple values to unique predicate
self.assertRaises(ValueError, nodes.set_from_iterable, ((self.p_filesize.uri, 1234),
- (self.p_author.uri, Nodes(self.backend, self.user, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')}))))
+ (self.p_author.uri, Nodes(self.backend, self.ac, self.user_type, {URI('http://example.com/me/user#1234'), URI('http://example.com/me/user#4321')}))))
self.assertSetEqual(curr, set(self.backend._graph))
+ # can set on empty nodes
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {})
+ self.assertEqual(nodes, nodes.set_from_iterable([(self.p_filesize.uri, 1234)]))
+
+
def test_get(self):
# setup: add some instances
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}) \
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}) \
.set(ns.bse.comment, 'hello world') \
.set(ns.bse.filesize, 1234) \
- .set(ns.bse.tag, Nodes(self.backend, self.user, self.tag_type, {'http://example.com/me/tag#1234'}))
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#4321'}) \
+ .set(ns.bse.tag, Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#1234'}))
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}) \
.set(ns.bse.filesize, 4321) \
- .set(ns.bse.tag, Nodes(self.backend, self.user, self.tag_type, {'http://example.com/me/tag#4321'}))
- Nodes(self.backend, self.user, self.tag_type, {'http://example.com/me/tag#1234'}) \
- .set(bst.label, 'tag_label_1234')
- Nodes(self.backend, self.user, self.tag_type, {'http://example.com/me/tag#4321'}) \
- .set(bst.label, 'tag_label_4321')
+ .set(ns.bse.tag, Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#4321'}))
+ Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#1234'}) \
+ .set(ns.bst.label, 'tag_label_1234')
+ Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#4321'}) \
+ .set(ns.bst.label, 'tag_label_4321')
# setup: get nodes instance
- nodes = Nodes(self.backend, self.user, self.ent_type, self.ent_ids)
+ nodes = Nodes(self.backend, self.ac, self.ent_type, self.ent_ids)
+
# must pass at least one path
self.assertRaises(AttributeError, nodes.get)
# view must be list or dict
@@ -409,69 +422,112 @@ class TestNodes(unittest.TestCase):
self.assertRaises(ValueError, nodes.get, ns.bse.filesize, view=tuple)
# can pass path as URI
self.assertDictEqual(nodes.get(ns.bse.filesize), {
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}): 1234,
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#4321'}): 4321,
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): 1234,
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): 4321,
})
# can pass path as sequence of URI
- self.assertDictEqual(nodes.get((ns.bse.tag, bst.label)), {
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}): {'tag_label_1234'},
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#4321'}): {'tag_label_4321'},
+ self.assertDictEqual(nodes.get((ns.bse.tag, ns.bst.label)), {
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {'tag_label_1234'},
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): {'tag_label_4321'},
})
# get returns the same path that was passed
- self.assertCountEqual(list(nodes.get((ns.bse.tag, bst.label), path=True, view=list)), [
- (Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}), (ns.bse.tag, bst.label), 'tag_label_1234'),
- (Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#4321'}), (ns.bse.tag, bst.label), 'tag_label_4321'),
+ self.assertCountEqual(list(nodes.get((ns.bse.tag, ns.bst.label), path=True, view=list)), [
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}), (ns.bse.tag, ns.bst.label), 'tag_label_1234'),
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}), (ns.bse.tag, ns.bst.label), 'tag_label_4321'),
])
- self.assertCountEqual(list(nodes.get([ns.bse.tag, bst.label], path=True, view=list)), [
- (Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}), [ns.bse.tag, bst.label], 'tag_label_1234'),
- (Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#4321'}), [ns.bse.tag, bst.label], 'tag_label_4321'),
+ self.assertCountEqual(list(nodes.get([ns.bse.tag, ns.bst.label], path=True, view=list)), [
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}), [ns.bse.tag, ns.bst.label], 'tag_label_1234'),
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}), [ns.bse.tag, ns.bst.label], 'tag_label_4321'),
])
# paths must be URI or sequence thereof
self.assertRaises(TypeError, nodes.get, 1234)
self.assertRaises(TypeError, nodes.get, (ns.bse.tag, 1234))
self.assertRaises(TypeError, nodes.get, (1234, ns.bse.tag))
- self.assertRaises(errors.ConsistencyError, nodes.get, 'hello world')
+ self.assertRaises(ValueError, nodes.get, 'hello world')
+ self.assertRaises(errors.ConsistencyError, nodes.get, 'hello_world')
self.assertRaises(errors.ConsistencyError, nodes.get, ns.bse.invalid)
- self.assertRaises(errors.ConsistencyError, nodes.get, (ns.bse.tag, bst.invalid))
+ self.assertRaises(errors.ConsistencyError, nodes.get, (ns.bse.tag, ns.bst.invalid))
# can pass multiple paths
- self.assertDictEqual(nodes.get(ns.bse.filesize, (ns.bse.tag, bst.label)), {
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}): {
+ self.assertDictEqual(nodes.get(ns.bse.filesize, (ns.bse.tag, ns.bst.label)), {
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {
ns.bse.filesize: 1234,
- (ns.bse.tag, bst.label): {'tag_label_1234'},
+ (ns.bse.tag, ns.bst.label): {'tag_label_1234'},
},
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#4321'}): {
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): {
ns.bse.filesize: 4321,
- (ns.bse.tag, bst.label): {'tag_label_4321'},
+ (ns.bse.tag, ns.bst.label): {'tag_label_4321'},
},
})
# get respects view
self.assertDictEqual(nodes.get(ns.bse.filesize, view=dict), {
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}): 1234,
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#4321'}): 4321,
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): 1234,
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}): 4321,
})
self.assertSetEqual(set(nodes.get(ns.bse.filesize, view=list)), {
- (Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}), 1234),
- (Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#4321'}), 4321),
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}), 1234),
+ (Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}), 4321),
})
# get returns Nodes instance when fetching a node
self.assertDictEqual(nodes.get(ns.bse.tag), {
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}):
- {Nodes(self.backend, self.user, self.tag_type, {'http://example.com/me/tag#1234'})},
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#4321'}):
- {Nodes(self.backend, self.user, self.tag_type, {'http://example.com/me/tag#4321'})},
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}):
+ {Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#1234'})},
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}):
+ {Nodes(self.backend, self.ac, self.tag_type, {'http://example.com/me/tag#4321'})},
})
# get returns a value when fetching a value and omits missing values
self.assertDictEqual(nodes.get(ns.bse.comment), {
- Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'}): {'hello world'},
+ Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'}): {'hello world'},
})
- # FIXME: What if I call `get` with a single predicate and a single node, but
- # that node has no value for that predicate?
- # so, essentially, what if triples is empty? -> Also check in test_result!
- raise NotImplementedError()
+ # results can be empty
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#4321'}) # has filesize, tag but no comment
+ # unique paths return the default value
+ self.assertIsNone(nodes.get(ns.bse.author))
+ self.assertEqual(nodes.get(ns.bse.author, default=1234), 1234)
+ # non-unique paths return an empty set
+ self.assertSetEqual(nodes.get(ns.bse.comment), set())
+
+ # nodes can have no guids
+ nodes = Nodes(self.backend, self.ac, self.ent_type, set())
+ # empty nodes does not excuse an invalid request
+ self.assertRaises(TypeError, nodes.get, 1234)
+ self.assertRaises(errors.ConsistencyError, nodes.get, ns.bse.invalid)
+ # list view always returns an empty list
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, path=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=True, path=True, value=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, path=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, view=list, node=False, path=False, value=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, path=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=True, path=True, value=True)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, path=False)), [])
+ self.assertListEqual(list(nodes.get(ns.bse.comment, ns.bse.filesize, view=list, node=False, path=False, value=False)), [])
+ # dict view returns an empty dict or an empty set
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, node=True), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, path=True), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, node=True, path=True, value=True, default=None), {})
+ self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False), set())
+ self.assertDictEqual(nodes.get(ns.bse.comment, view=dict, path=False), {})
+ self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False, path=False), set())
+ self.assertSetEqual(nodes.get(ns.bse.comment, view=dict, node=False, path=False, value=False, default=None), set())
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=True), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, path=True), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=True, path=True, value=True, default=None), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False), {})
+ self.assertDictEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, path=False), {})
+ self.assertSetEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False, path=False), set())
+ self.assertSetEqual(nodes.get(ns.bse.comment, ns.bse.filesize, view=dict, node=False, path=False, value=False, default=None), set())
+
def test_getattr(self):
- nodes = Nodes(self.backend, self.user, self.ent_type, {'http://example.com/me/entity#1234'})
+ nodes = Nodes(self.backend, self.ac, self.ent_type, {'http://example.com/me/entity#1234'})
# can get walks to values
self.assertEqual(nodes.filesize, Walk(nodes, (self.p_filesize, )))
# can get walks to nodes
@@ -482,11 +538,11 @@ class TestNodes(unittest.TestCase):
self.assertRaises(ValueError, getattr, nodes, 'foobar')
def test_schema(self):
- self.assertEqual(Nodes(self.backend, self.user, self.ent_type,
+ self.assertEqual(Nodes(self.backend, self.ac, self.ent_type,
{URI('http://example.com/me/entity#1234')}).schema, self.backend.schema)
def test_operators(self): # __add__, __or__, __sub__, __and__
- gen = partial(Nodes, self.backend, self.user, self.ent_type)
+ gen = partial(Nodes, self.backend, self.ac, self.ent_type)
nodes = gen({URI('http://example.com/me/entity#1234')})
# add/or concatenates guids
self.assertEqual(
@@ -544,23 +600,24 @@ class TestNodes(unittest.TestCase):
self.assertRaises(TypeError, op, nodes, 'hello world')
# backend must match
self.assertRaises(ValueError, op, nodes,
- Nodes(None, self.user, self.ent_type, {URI('http://example.com/me/entity#1234')}))
- # user must match
+ Nodes(None, self.ac, self.ent_type, {URI('http://example.com/me/entity#1234')}))
+ # ac must match
self.assertRaises(ValueError, op, nodes,
- Nodes(self.backend, '', self.ent_type, {URI('http://example.com/me/entity#1234')}))
+ Nodes(self.backend, NullAC(self.backend, ''),
+ self.ent_type, {URI('http://example.com/me/entity#1234')}))
# node type must match
self.assertRaises(ValueError, op, nodes,
- Nodes(self.backend, self.user, self.tag_type, {URI('http://example.com/me/entity#1234')}))
+ Nodes(self.backend, self.ac, self.tag_type, {URI('http://example.com/me/entity#1234')}))
def test_len(self):
- self.assertEqual(1, len(Nodes(self.backend, self.user, self.ent_type, {
+ self.assertEqual(1, len(Nodes(self.backend, self.ac, self.ent_type, {
URI('http://example.com/me/entity#1234'),
})))
- self.assertEqual(2, len(Nodes(self.backend, self.user, self.ent_type, {
+ self.assertEqual(2, len(Nodes(self.backend, self.ac, self.ent_type, {
URI('http://example.com/me/entity#1234'),
URI('http://example.com/me/entity#4321'),
})))
- self.assertEqual(4, len(Nodes(self.backend, self.user, self.ent_type, {
+ self.assertEqual(4, len(Nodes(self.backend, self.ac, self.ent_type, {
URI('http://example.com/me/entity#1234'),
URI('http://example.com/me/entity#4321'),
URI('http://example.com/me/entity#5678'),
@@ -568,8 +625,8 @@ class TestNodes(unittest.TestCase):
})))
def test_iter(self): # __iter__
- gen = partial(Nodes, self.backend, self.user, self.ent_type)
- self.assertSetEqual(set(Nodes(self.backend, self.user, self.ent_type, {
+ gen = partial(Nodes, self.backend, self.ac, self.ent_type)
+ self.assertSetEqual(set(Nodes(self.backend, self.ac, self.ent_type, {
URI('http://example.com/me/entity#1234'),
URI('http://example.com/me/entity#4321'),
URI('http://example.com/me/entity#5678'),
diff --git a/test/graph/test_resolve.py b/test/graph/test_resolve.py
index 0918b02..e09b1cc 100644
--- a/test/graph/test_resolve.py
+++ b/test/graph/test_resolve.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
@@ -21,6 +16,8 @@ from bsfs.graph.resolve import Filter
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestFilter(unittest.TestCase):
"""
@@ -30,23 +27,25 @@ class TestFilter(unittest.TestCase):
"""
- def test_call(self):
+ def test_call(self): # tests resolve implicitly
schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array .
+ xsd:integer rdfs:subClassOf bsl:Number .
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:dimension "5"^^xsd:integer .
bse:colors rdfs:subClassOf bsfs:Predicate ;
@@ -79,6 +78,9 @@ class TestFilter(unittest.TestCase):
{'http://example.com/you/invalid#1234', 'http://example.com/you/invalid#4321'})
resolver = Filter(schema)
+ # query can be None
+ self.assertIsNone(resolver(schema.node(ns.bsfs.Entity), None))
+
# immediate Is
self.assertEqual(resolver(schema.node(ns.bsfs.Entity),
ast.filter.Is(ents)),
diff --git a/test/graph/test_result.py b/test/graph/test_result.py
index 749b8ad..8960ef6 100644
--- a/test/graph/test_result.py
+++ b/test/graph/test_result.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
@@ -18,6 +13,8 @@ from bsfs.graph.result import to_list_view, to_dict_view
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestListView(unittest.TestCase):
def setUp(self):
self.triples_111 = [('ent#1234', ns.bse.iso, 123)]
diff --git a/test/graph/test_walk.py b/test/graph/test_walk.py
index f9dbc7a..4b844da 100644
--- a/test/graph/test_walk.py
+++ b/test/graph/test_walk.py
@@ -1,15 +1,11 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
# bsfs imports
from bsfs import schema as bsc
from bsfs.graph import Graph
+from bsfs.graph.ac import NullAC
from bsfs.namespace import Namespace, ns
from bsfs.triple_store.sparql import SparqlStore
from bsfs.utils import URI
@@ -19,8 +15,8 @@ from bsfs.graph.walk import Walk
## code ##
-bse = ns.bse
-bst = Namespace('http://bsfs.ai/schema/Tag')
+ns.bse = ns.bsfs.Entity()
+ns.bst = ns.bsfs.Tag()
class TestWalk(unittest.TestCase):
def setUp(self):
@@ -28,9 +24,9 @@ class TestWalk(unittest.TestCase):
self.schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
- prefix bst: <http://bsfs.ai/schema/Tag#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
@@ -65,7 +61,8 @@ class TestWalk(unittest.TestCase):
''')
self.backend = SparqlStore.Open()
self.user = URI('http://example.com/me')
- self.graph = Graph(self.backend, self.user)
+ self.ac = NullAC(self.backend, self.user)
+ self.graph = Graph(self.backend, self.ac)
self.graph.migrate(self.schema)
# nodes setup
@@ -76,14 +73,14 @@ class TestWalk(unittest.TestCase):
URI('http://example.com/me/tag#1234'),
URI('http://example.com/me/tag#4321')})
# add some instances
- self.ents.set(bse.tag, self.tags)
- self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#1234')).set(bst.label, 'hello')
- self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#4321')).set(bst.label, 'world')
+ self.ents.set(ns.bse.tag, self.tags)
+ self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#1234')).set(ns.bst.label, 'hello')
+ self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#4321')).set(ns.bst.label, 'world')
def test_essentials(self): # __eq__, __hash__, __str__, __repr__
- p_author = self.schema.predicate(bse.author)
- p_tag = self.schema.predicate(bse.tag)
- p_main = self.schema.predicate(bst.main)
+ p_author = self.schema.predicate(ns.bse.author)
+ p_tag = self.schema.predicate(ns.bse.tag)
+ p_main = self.schema.predicate(ns.bst.main)
# comparison
self.assertEqual(Walk(self.ents, [p_tag]), Walk(self.ents, [p_tag]))
self.assertEqual(hash(Walk(self.ents, [p_tag])), hash(Walk(self.ents, [p_tag])))
@@ -99,18 +96,18 @@ class TestWalk(unittest.TestCase):
self.assertNotEqual(hash(Walk(self.tags, [p_author])), hash(Walk(self.tags, [p_main])))
# string conversion
self.assertEqual(str(Walk(self.ents, [p_tag, p_main])),
- 'Walk(@http://bsfs.ai/schema/Entity: http://bsfs.ai/schema/Entity#tag, http://bsfs.ai/schema/Tag#main)')
+ 'Walk(@https://schema.bsfs.io/core/Entity: https://schema.bsfs.io/core/Entity#tag, https://schema.bsfs.io/core/Tag#main)')
self.assertEqual(repr(Walk(self.ents, [p_tag, p_main])),
- 'Walk(http://bsfs.ai/schema/Entity, (http://bsfs.ai/schema/Entity#tag, http://bsfs.ai/schema/Tag#main))')
+ 'Walk(https://schema.bsfs.io/core/Entity, (https://schema.bsfs.io/core/Entity#tag, https://schema.bsfs.io/core/Tag#main))')
def test_tail(self):
self.assertEqual(Walk(self.ents, (
- self.schema.predicate(bse.tag),
+ self.schema.predicate(ns.bse.tag),
)).tail,
self.schema.node(ns.bsfs.Tag))
self.assertEqual(Walk(self.ents, (
- self.schema.predicate(bse.tag),
- self.schema.predicate(bst.main),
+ self.schema.predicate(ns.bse.tag),
+ self.schema.predicate(ns.bst.main),
)).tail,
self.schema.node(ns.bsfs.Entity))
@@ -118,24 +115,24 @@ class TestWalk(unittest.TestCase):
tag_type = self.schema.node(ns.bsfs.Tag)
# step returns a predicate
self.assertEqual(Walk.step(self.schema, tag_type, 'subTagOf'),
- (self.schema.predicate(bst.subTagOf), ))
+ (self.schema.predicate(ns.bst.subTagOf), ))
# invalid step raises an error
self.assertRaises(ValueError, Walk.step, self.schema, tag_type, 'foobar')
# ambiguous step raises an error
self.assertRaises(ValueError, Walk.step, self.schema, tag_type, 'author')
def test_getattr(self): # __getattr__
- walk = Walk(self.ents, (self.schema.predicate(bse.tag), ))
+ walk = Walk(self.ents, (self.schema.predicate(ns.bse.tag), ))
# first step
self.assertEqual(walk.subTagOf, Walk(self.ents, (
- self.schema.predicate(bse.tag),
- self.schema.predicate(bst.subTagOf),
+ self.schema.predicate(ns.bse.tag),
+ self.schema.predicate(ns.bst.subTagOf),
)))
# second step
self.assertEqual(walk.subTagOf.main, Walk(self.ents, (
- self.schema.predicate(bse.tag),
- self.schema.predicate(bst.subTagOf),
- self.schema.predicate(bst.main),
+ self.schema.predicate(ns.bse.tag),
+ self.schema.predicate(ns.bst.subTagOf),
+ self.schema.predicate(ns.bst.main),
)))
# invalid step raises an error
self.assertRaises(ValueError, getattr, walk, 'foobar')
@@ -143,7 +140,7 @@ class TestWalk(unittest.TestCase):
self.assertRaises(ValueError, getattr, walk, 'author')
def test_get(self): # get, __call__
- walk = Walk(self.ents, (self.schema.predicate(bse.tag), ))
+ walk = Walk(self.ents, (self.schema.predicate(ns.bse.tag), ))
tags = {
self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#1234')),
self.graph.node(ns.bsfs.Tag, URI('http://example.com/me/tag#4321'))}
diff --git a/test/namespace/test_namespace.py b/test/namespace/test_namespace.py
index 2536203..f7bf02a 100644
--- a/test/namespace/test_namespace.py
+++ b/test/namespace/test_namespace.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import operator
import unittest
@@ -12,7 +7,7 @@ import unittest
from bsfs.utils import URI
# objects to test
-from bsfs.namespace.namespace import Namespace, ClosedNamespace
+from bsfs.namespace.namespace import Namespace, FinalNamespace
## code ##
@@ -20,108 +15,48 @@ from bsfs.namespace.namespace import Namespace, ClosedNamespace
class TestNamespace(unittest.TestCase):
def test_essentials(self):
# string conversion
- self.assertEqual(str(Namespace('http://example.org/')), 'http://example.org')
- self.assertEqual(str(Namespace('http://example.org#')), 'http://example.org')
- self.assertEqual(repr(Namespace('http://example.org/')), 'Namespace(http://example.org, #, /)')
- self.assertEqual(repr(Namespace('http://example.org#')), 'Namespace(http://example.org, #, /)')
- self.assertEqual(repr(Namespace('http://example.org', fsep='.')), 'Namespace(http://example.org, ., /)')
- self.assertEqual(repr(Namespace('http://example.org', psep='.')), 'Namespace(http://example.org, #, .)')
- # repeated separators are truncated
- self.assertEqual(str(Namespace('http://example.org////')), 'http://example.org')
- self.assertEqual(str(Namespace('http://example.org####')), 'http://example.org')
- self.assertEqual(repr(Namespace('http://example.org///##')), 'Namespace(http://example.org, #, /)')
+ self.assertEqual(str(Namespace('http://example.org')), 'http://example.org')
+ self.assertEqual(repr(Namespace('http://example.org')), "'http://example.org'")
# comparison
- class Foo(Namespace): pass
- self.assertEqual(Namespace('http://example.org/'), Namespace('http://example.org/'))
- self.assertEqual(Namespace('http://example.org/'), Namespace('http://example.org'))
- self.assertEqual(Namespace('http://example.org/'), Namespace('http://example.org#'))
- self.assertNotEqual(Namespace('http://example.org'), Namespace('http://example.org', fsep='.'))
- self.assertNotEqual(Namespace('http://example.org'), Namespace('http://example.org', psep='.'))
- self.assertNotEqual(Namespace('http://example.org/'), Foo('http://example.org/'))
- self.assertNotEqual(Foo('http://example.org/'), Namespace('http://example.org/'))
- # hashing
- self.assertEqual(hash(Namespace('http://example.org/')), hash(Namespace('http://example.org/')))
- self.assertEqual(hash(Namespace('http://example.org/')), hash(Namespace('http://example.org')))
- self.assertEqual(hash(Namespace('http://example.org/')), hash(Namespace('http://example.org#')))
+ self.assertEqual(Namespace('http://example.org'), Namespace('http://example.org'))
+ self.assertEqual(hash(Namespace('http://example.org')), hash(Namespace('http://example.org')))
+ # Namespace compares to string
+ self.assertEqual(Namespace('http://example.org'), 'http://example.org')
+ self.assertEqual(hash(Namespace('http://example.org')), hash('http://example.org'))
+ # URI must match
+ self.assertNotEqual(Namespace('http://example.org'), Namespace('http://example.com'))
self.assertNotEqual(hash(Namespace('http://example.org')), hash(Namespace('http://example.com')))
- self.assertNotEqual(hash(Namespace('http://example.org')), hash(Namespace('http://example.org', fsep='.')))
- self.assertNotEqual(hash(Namespace('http://example.org')), hash(Namespace('http://example.org', psep='.')))
- self.assertNotEqual(hash(Namespace('http://example.org/')), hash(Foo('http://example.org/')))
- self.assertNotEqual(hash(Foo('http://example.org/')), hash(Namespace('http://example.org/')))
def test_getattr(self):
- self.assertEqual(Namespace('http://example.org/').foo, 'http://example.org#foo')
- self.assertEqual(Namespace('http://example.org/').bar, 'http://example.org#bar')
- self.assertEqual(Namespace('http://example.org/', fsep='/').foo, 'http://example.org/foo')
- self.assertEqual(Namespace('http://example.org/', fsep='/').bar, 'http://example.org/bar')
- self.assertEqual(Namespace('http://example.org', fsep='/').foo, 'http://example.org/foo')
- self.assertEqual(Namespace('http://example.org', fsep='/').bar, 'http://example.org/bar')
- self.assertEqual(Namespace('http://example.org#', fsep='/').foo, 'http://example.org#/foo')
- self.assertEqual(Namespace('http://example.org#', fsep='/').bar, 'http://example.org#/bar')
- self.assertEqual(Namespace('http://example.org/me#').foo, 'http://example.org/me#foo')
- self.assertEqual(Namespace('http://example.org/me#').bar, 'http://example.org/me#bar')
+ self.assertEqual(Namespace('http://example.org').foo, Namespace('http://example.org/foo'))
+ self.assertEqual(Namespace('http://example.org').bar, Namespace('http://example.org/bar'))
- def test_getitem(self):
- self.assertEqual(Namespace('http://example.org')['foo'], 'http://example.org#foo')
- self.assertEqual(Namespace('http://example.org')['bar'], 'http://example.org#bar')
- self.assertEqual(Namespace('http://example.org', fsep='/')['foo'], 'http://example.org/foo')
- self.assertEqual(Namespace('http://example.org', fsep='/')['bar'], 'http://example.org/bar')
- self.assertEqual(Namespace('http://example.org/me#')['foo'], 'http://example.org/me#foo')
- self.assertEqual(Namespace('http://example.org/me#')['bar'], 'http://example.org/me#bar')
+ def test_call(self):
+ self.assertEqual(Namespace('http://example.org')(), FinalNamespace('http://example.org', sep='#'))
+ self.assertEqual(Namespace('http://example.org').foo(), FinalNamespace('http://example.org/foo', sep='#'))
- def test_add(self):
- self.assertEqual(Namespace('http://example.org') + 'foo', Namespace('http://example.org/foo'))
- self.assertEqual(Namespace('http://example.org', psep='.') + 'foo', Namespace('http://example.org.foo', psep='.'))
- self.assertEqual(Namespace('http://example.org') + 'foo' + 'bar', Namespace('http://example.org/foo/bar'))
- # can add URIs
- self.assertEqual(Namespace('http://example.org') + URI('foo'), Namespace('http://example.org/foo'))
- # can only add strings
- self.assertRaises(TypeError, operator.add, Namespace('http://example.org'), 1234)
- self.assertRaises(TypeError, operator.add, Namespace('http://example.org'), Namespace('http://example.com'))
-
-
-class TestClosedNamespace(unittest.TestCase):
+class TestFinalNamespace(unittest.TestCase):
def test_essentials(self):
- # string conversion
- self.assertEqual(str(ClosedNamespace('http://example.org/')), 'http://example.org')
- self.assertEqual(str(ClosedNamespace('http://example.org#')), 'http://example.org')
- self.assertEqual(repr(ClosedNamespace('http://example.org/')), 'ClosedNamespace(http://example.org, #, /)')
- self.assertEqual(repr(ClosedNamespace('http://example.org#')), 'ClosedNamespace(http://example.org, #, /)')
- self.assertEqual(repr(ClosedNamespace('http://example.org', fsep='.')), 'ClosedNamespace(http://example.org, ., /)')
- self.assertEqual(repr(ClosedNamespace('http://example.org', psep='.')), 'ClosedNamespace(http://example.org, #, .)')
+ # string conversion
+ self.assertEqual(str(FinalNamespace('http://example.org')), 'http://example.org')
+ self.assertEqual(repr(FinalNamespace('http://example.org')), "'http://example.org'")
# comparison
- class Foo(ClosedNamespace): pass
- self.assertEqual(ClosedNamespace('http://example.org'), ClosedNamespace('http://example.org#'))
- self.assertEqual(ClosedNamespace('http://example.org'), ClosedNamespace('http://example.org'))
- self.assertEqual(ClosedNamespace('http://example.org'), ClosedNamespace('http://example.org/'))
- self.assertEqual(ClosedNamespace('http://example.org/', 'foo', 'bar'), ClosedNamespace('http://example.org/', 'foo', 'bar'))
- self.assertNotEqual(ClosedNamespace('http://example.org/', 'foo'), ClosedNamespace('http://example.org/', 'bar'))
- self.assertNotEqual(ClosedNamespace('http://example.org/'), Foo('http://example.org/'))
- self.assertNotEqual(Foo('http://example.org/'), ClosedNamespace('http://example.org/'))
- # hashing
- self.assertEqual(hash(ClosedNamespace('http://example.org')), hash(ClosedNamespace('http://example.org')))
- self.assertEqual(hash(ClosedNamespace('http://example.org')), hash(ClosedNamespace('http://example.org/')))
- self.assertEqual(hash(ClosedNamespace('http://example.org')), hash(ClosedNamespace('http://example.org#')))
- self.assertEqual(hash(ClosedNamespace('http://example.org/', 'foo', 'bar')), hash(ClosedNamespace('http://example.org/', 'foo', 'bar')))
- self.assertNotEqual(hash(ClosedNamespace('http://example.org/', 'foo')), hash(ClosedNamespace('http://example.org/', 'bar')))
- self.assertNotEqual(hash(ClosedNamespace('http://example.org/')), hash(Foo('http://example.org/')))
- self.assertNotEqual(hash(Foo('http://example.org/')), hash(ClosedNamespace('http://example.org/')))
+ self.assertEqual(FinalNamespace('http://example.org'), FinalNamespace('http://example.org'))
+ self.assertEqual(hash(FinalNamespace('http://example.org')), hash(FinalNamespace('http://example.org')))
+ # FinalNamespace compares to string
+ self.assertEqual(FinalNamespace('http://example.org'), 'http://example.org')
+ self.assertEqual(hash(FinalNamespace('http://example.org')), hash('http://example.org'))
+ # URI must match
+ self.assertNotEqual(FinalNamespace('http://example.org'), FinalNamespace('http://example.com'))
+ self.assertNotEqual(hash(FinalNamespace('http://example.org')), hash(FinalNamespace('http://example.com')))
+ # separator is ignored
+ self.assertEqual(FinalNamespace('http://example.org'), FinalNamespace('http://example.org', sep='/'))
+ self.assertEqual(hash(FinalNamespace('http://example.org')), hash(FinalNamespace('http://example.org', sep='/')))
def test_getattr(self):
- self.assertEqual(ClosedNamespace('http://example.org/', 'foo', 'bar').foo, 'http://example.org#foo')
- self.assertEqual(ClosedNamespace('http://example.org/', 'bar', 'bar').bar, 'http://example.org#bar')
- self.assertEqual(ClosedNamespace('http://example.org/me#', 'foo', 'bar').foo, 'http://example.org/me#foo')
- self.assertEqual(ClosedNamespace('http://example.org/me#', 'foo', 'bar').bar, 'http://example.org/me#bar')
- self.assertRaises(KeyError, getattr, ClosedNamespace('http://example.org/', 'bar', 'bar'), 'foobar')
- self.assertRaises(KeyError, getattr, ClosedNamespace('http://example.org#', 'bar', 'bar'), 'foobar')
-
- def test_getitem(self):
- self.assertEqual(ClosedNamespace('http://example.org/', 'foo', 'bar')['foo'], 'http://example.org#foo')
- self.assertEqual(ClosedNamespace('http://example.org/', 'foo', 'bar')['bar'], 'http://example.org#bar')
- self.assertEqual(ClosedNamespace('http://example.org/me#', 'foo', 'bar')['foo'], 'http://example.org/me#foo')
- self.assertEqual(ClosedNamespace('http://example.org/me#', 'foo', 'bar')['bar'], 'http://example.org/me#bar')
- self.assertRaises(KeyError, ClosedNamespace('http://example.org/', 'bar', 'bar').__getitem__, 'foobar')
- self.assertRaises(KeyError, ClosedNamespace('http://example.org#', 'bar', 'bar').__getitem__, 'foobar')
+ self.assertEqual(FinalNamespace('http://example.org').foo, FinalNamespace('http://example.org#foo'))
+ self.assertEqual(FinalNamespace('http://example.org').bar, FinalNamespace('http://example.org#bar'))
+ self.assertEqual(FinalNamespace('http://example.org', sep='/').bar, FinalNamespace('http://example.org/bar'))
## main ##
diff --git a/test/query/ast_test/test_fetch.py b/test/query/ast_test/test_fetch.py
index 0c48a1f..ccb680e 100644
--- a/test/query/ast_test/test_fetch.py
+++ b/test/query/ast_test/test_fetch.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
diff --git a/test/query/ast_test/test_filter_.py b/test/query/ast_test/test_filter_.py
index 39b98f8..d0d42ea 100644
--- a/test/query/ast_test/test_filter_.py
+++ b/test/query/ast_test/test_filter_.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
@@ -387,7 +382,7 @@ class TestPredicate(unittest.TestCase):
# member returns predicate
# predicate must be an URI
self.assertEqual(Predicate(ns.bse.filesize).predicate, ns.bse.filesize)
- self.assertEqual(Predicate(URI('hello world')).predicate, URI('hello world'))
+ self.assertEqual(Predicate(URI('hello_world')).predicate, URI('hello_world'))
self.assertRaises(TypeError, Predicate, 1234)
self.assertRaises(TypeError, Predicate, FilterExpression())
self.assertRaises(TypeError, Predicate, FilterExpression())
diff --git a/test/query/test_matcher.py b/test/query/test_matcher.py
index e830cf8..6b975b2 100644
--- a/test/query/test_matcher.py
+++ b/test/query/test_matcher.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import operator
import unittest
diff --git a/test/query/test_validator.py b/test/query/test_validator.py
index fec3d23..418463e 100644
--- a/test/query/test_validator.py
+++ b/test/query/test_validator.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
@@ -19,26 +14,29 @@ from bsfs.query.validator import Filter, Fetch
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestFilter(unittest.TestCase):
def setUp(self):
self.schema = _schema.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:URI rdfs:subClassOf bsfs:Literal .
bsfs:Tag rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ <https://schema.bsfs.io/core/Literal/Array/Feature> rdfs:subClassOf bsl:Array .
+ xsd:integer rdfs:subClassOf bsl:Number .
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf <https://schema.bsfs.io/core/Literal/Array/Feature> ;
bsfs:dimension "5"^^xsd:integer ;
bsfs:dtype bsfs:f32 .
@@ -75,7 +73,7 @@ class TestFilter(unittest.TestCase):
''')
self.validate = Filter(self.schema)
- def test_call(self):
+ def test_call(self): # tests validate implicitly
# root_type must be a _schema.Node
self.assertRaises(TypeError, self.validate, 1234, None)
self.assertRaises(TypeError, self.validate, '1234', None)
@@ -272,10 +270,10 @@ class TestFilter(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.node(ns.bsfs.Node),
ast.filter.Distance([1,2,3], 1, False))
# type must be a feature
- self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsfs.Array),
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsl.Array),
ast.filter.Distance([1,2,3], 1, False))
# type must be in the schema
- self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsfs.Feature).child(ns.bsfs.Invalid),
+ self.assertRaises(errors.ConsistencyError, self.validate._distance, self.schema.literal(ns.bsl.Array.Feature).child(ns.bsfs.Invalid),
ast.filter.Distance([1,2,3], 1, False))
# FIXME: reference must be a numpy array
# reference must have the correct dimension
@@ -292,8 +290,8 @@ class TestFetch(unittest.TestCase):
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
@@ -314,7 +312,7 @@ class TestFetch(unittest.TestCase):
''')
self.validate = Fetch(self.schema)
- def test_call(self):
+ def test_call(self): # tests validate implicitly
# call accepts correct expressions
self.assertTrue(self.validate(self.schema.node(ns.bsfs.Entity),
ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bse.label, 'value'))))
diff --git a/test/schema/test_schema.py b/test/schema/test_schema.py
index 414e542..f52cf95 100644
--- a/test/schema/test_schema.py
+++ b/test/schema/test_schema.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import operator
import unittest
@@ -19,6 +14,8 @@ from bsfs.schema.schema import Schema
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestSchema(unittest.TestCase):
def setUp(self):
@@ -26,8 +23,9 @@ class TestSchema(unittest.TestCase):
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
@@ -36,7 +34,7 @@ class TestSchema(unittest.TestCase):
xsd:string rdfs:subClassOf bsfs:Literal .
bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ xsd:integer rdfs:subClassOf bsl:Number .
xsd:boolean rdfs:subClassOf bsfs:Literal .
bse:tag rdfs:subClassOf bsfs:Predicate ;
@@ -179,13 +177,13 @@ class TestSchema(unittest.TestCase):
self.assertEqual(str(Schema(self.predicates, self.nodes, self.literals)), 'Schema()')
# repr conversion with only default nodes, literals, and predicates
n = [ns.bsfs.Node]
- l = [ns.bsfs.Array, ns.bsfs.BinaryBlob, ns.bsfs.Feature, ns.bsfs.Literal, ns.bsfs.Number, ns.bsfs.Time]
+ l = [ns.bsfs.Literal, ns.bsl.Array, ns.bsl.Array.Feature, ns.bsl.BinaryBlob, ns.bsl.Number, ns.bsl.Time]
p = [ns.bsfs.Predicate]
self.assertEqual(repr(Schema()), f'Schema({n}, {l}, {p})')
self.assertEqual(repr(Schema([], [], [])), f'Schema({n}, {l}, {p})')
# repr conversion
n = [ns.bsfs.Entity, ns.bsfs.Image, ns.bsfs.Node, ns.bsfs.Tag, ns.bsfs.Unused]
- l = [ns.bsfs.Array, ns.bsfs.BinaryBlob, ns.bsfs.Feature, ns.bsfs.Literal, ns.bsfs.Number, ns.bsfs.Time, ns.xsd.boolean, ns.xsd.integer, ns.xsd.string]
+ l = [ns.xsd.boolean, ns.xsd.integer, ns.xsd.string, ns.bsfs.Literal, ns.bsl.Array, ns.bsl.Array.Feature, ns.bsl.BinaryBlob, ns.bsl.Number, ns.bsl.Time]
p = [ns.bse.comment, ns.bse.group, ns.bse.tag, ns.bsfs.Predicate]
self.assertEqual(repr(Schema(self.predicates, self.nodes, self.literals)), f'Schema({n}, {l}, {p})')
diff --git a/test/schema/test_serialize.py b/test/schema/test_serialize.py
index fc6b20a..7d5d3ae 100644
--- a/test/schema/test_serialize.py
+++ b/test/schema/test_serialize.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import re
import unittest
@@ -19,6 +14,8 @@ from bsfs.schema.serialize import from_string, to_string
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestFromString(unittest.TestCase):
def test_empty(self):
@@ -30,7 +27,7 @@ class TestFromString(unittest.TestCase):
# must not have circular dependencies
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
# ah, a nice circular dependency
bsfs:Entity rdfs:subClassOf bsfs:Document .
@@ -44,8 +41,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
xsd:string rdfs:subClassOf bsfs:Literal .
@@ -59,7 +56,7 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Document rdfs:subClassOf bsfs:Node .
@@ -71,8 +68,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({}, {n_unused}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:unused rdfs:subClassOf bsfs:Node . # unused symbol
'''))
@@ -85,8 +82,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({}, {n_ent, n_tag, n_doc, n_image}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
# nodes inherit from same parent
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -105,8 +102,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({p_filename}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
@@ -121,7 +118,7 @@ class TestFromString(unittest.TestCase):
self.assertDictEqual(from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -129,7 +126,7 @@ class TestFromString(unittest.TestCase):
self.assertDictEqual(from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
bsfs:Entity rdfs:subClassOf bsfs:Node ;
rdfs:label "hello world"^^xsd:string ;
@@ -146,8 +143,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -161,7 +158,7 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
xsd:string rdfs:subClassOf bsfs:Literal .
xsd:name rdfs:subClassOf bsfs:Literal .
@@ -173,8 +170,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({}, {}, {l_unused}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
xsd:unused rdfs:subClassOf bsfs:Literal . # unused symbol
'''))
@@ -187,13 +184,14 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({}, {}, {l_string, l_integer, l_unsigned, l_signed}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
# literals inherit from same parent
xsd:string rdfs:subClassOf bsfs:Literal .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
# literals inherit from same parent
xsd:unsigned rdfs:subClassOf xsd:integer .
@@ -208,8 +206,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({p_filename}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
@@ -224,7 +222,7 @@ class TestFromString(unittest.TestCase):
self.assertDictEqual(from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
xsd:string rdfs:subClassOf bsfs:Literal .
@@ -232,7 +230,7 @@ class TestFromString(unittest.TestCase):
self.assertDictEqual(from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
xsd:string rdfs:subClassOf bsfs:Literal ;
rdfs:label "hello world"^^xsd:string ;
@@ -249,8 +247,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
xsd:string rdfs:subClassOf bsfs:Literal .
@@ -263,8 +261,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Literal .
xsd:string rdfs:subClassOf bsfs:Literal .
@@ -279,8 +277,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -293,8 +291,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -307,8 +305,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -325,8 +323,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({p_comment}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
@@ -345,8 +343,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({p_comment}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
@@ -367,8 +365,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({p_comment}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
@@ -388,8 +386,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({p_foo}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -410,8 +408,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({p_foobar}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Image rdfs:subClassOf bsfs:Entity .
@@ -426,8 +424,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Image rdfs:subClassOf bsfs:Entity .
@@ -449,8 +447,8 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema({p_foobar}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Image rdfs:subClassOf bsfs:Entity .
@@ -465,8 +463,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Image rdfs:subClassOf bsfs:Entity .
@@ -483,8 +481,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Annotation rdfs:subClassOf bsfs:Predicate .
@@ -504,8 +502,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -521,8 +519,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -538,8 +536,8 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -556,8 +554,8 @@ class TestFromString(unittest.TestCase):
self.assertDictEqual(from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bse:comment rdfs:subClassOf bsfs:Predicate ;
rdfs:range bsfs:Node .
@@ -566,8 +564,8 @@ class TestFromString(unittest.TestCase):
self.assertDictEqual(from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bse:comment rdfs:subClassOf bsfs:Predicate ;
rdfs:range bsfs:Node ;
@@ -586,70 +584,78 @@ class TestFromString(unittest.TestCase):
self.assertEqual(Schema(literals={f_colors}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
- bsfs:Colors rdfs:subClassOf bsfs:Feature .
+ bsfs:Colors rdfs:subClassOf bsa:Feature .
'''))
# features inherit properties from parents
- f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors, dimension=1234, dtype=ns.bsfs.i32)
- f_main_colors = f_colors.child(ns.bsfs.MainColor, distance=ns.bsfs.cosine, dtype=ns.bsfs.f16)
+ f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors, dimension=1234, dtype=ns.bsfs.dtype().i32)
+ f_main_colors = f_colors.child(ns.bsfs.MainColor, distance=ns.bsfs.cosine, dtype=ns.bsfs.dtype().f16)
self.assertEqual(Schema(literals={f_colors, f_main_colors}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
- bsfs:Colors rdfs:subClassOf bsfs:Feature ; # inherits distance from bsfs:Feature
- bsfs:dimension "1234"^^xsd:integer ; # overwrites bsfs:Feature
- bsfs:dtype bsfs:i32 . # overwrites bsfs:Feature
+ bsfs:Colors rdfs:subClassOf bsa:Feature ; # inherits distance from bsa:Feature
+ bsfs:dimension "1234"^^xsd:integer ; # overwrites bsa:Feature
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#i32> . # overwrites bsa:Feature
bsfs:MainColor rdfs:subClassOf bsfs:Colors ; # inherits dimension from bsfs:Colors
- bsfs:distance bsfs:cosine ; # overwrites bsfs:Feature
- bsfs:dtype bsfs:f16 . # overwrites bsfs:Colors
+ bsfs:distance bsfs:cosine ; # overwrites bsa:Feature
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#f16> . # overwrites bsfs:Colors
'''))
# feature definition can be split across multiple statements.
# statements can be repeated
- f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors, dimension=1234, dtype=ns.bsfs.f32)
+ f_colors = types.ROOT_FEATURE.child(ns.bsfs.Colors, dimension=1234, dtype=ns.bsfs.dtype().f32)
self.assertEqual(Schema(literals={f_colors}), from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:dimension "1234"^^xsd:integer .
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:dimension "1234"^^xsd:integer ; # non-conflicting repetition
- bsfs:dtype bsfs:f32 .
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#f32> .
'''))
# cannot define the same feature from multiple parents
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
- bsfs:ColorSpace rdfs:subClassOf bsfs:Feature .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+ bsfs:ColorSpace rdfs:subClassOf bsa:Feature .
- bsfs:Colors rdfs:subClassOf bsfs:Feature .
+ bsfs:Colors rdfs:subClassOf bsa:Feature .
bsfs:Colors rdfs:subClassOf bsfs:ColorSpace .
''')
@@ -657,16 +663,18 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:dimension "1234"^^xsd:integer .
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:dimension "4321"^^xsd:integer . # conflicting dimension
''')
@@ -674,32 +682,36 @@ class TestFromString(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
- bsfs:dtype bsfs:f32 .
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#i32> .
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
- bsfs:dtype bsfs:f16 . # conflicting dtype
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#f16> . # conflicting dtype
''')
# cannot assign multiple conflicting distance metrics to the same feature
self.assertRaises(errors.ConsistencyError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:distance bsfs:euclidean .
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:distance bsfs:cosine . # conflicting distance
''')
@@ -707,26 +719,30 @@ class TestFromString(unittest.TestCase):
self.assertDictEqual(from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:dimension "1234"^^xsd:integer .
''').literal(ns.bsfs.Colors).annotations, {})
self.assertDictEqual(from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:dimension "1234"^^xsd:integer ;
rdfs:label "hello world"^^xsd:string ;
bsfs:foo "1234"^^xsd:integer .
@@ -753,10 +769,10 @@ class TestFromString(unittest.TestCase):
p_group = p_tag.child(ns.bse.group, domain=n_image, unique=True)
p_comment = p_annotation.child(ns.bse.comment, range=l_string)
# features
- f_colors = types.ROOT_FEATURE.child(URI('http://bsfs.ai/schema/Feature/colors_spatial'),
- dtype=ns.bsfs.f16, distance=ns.bsfs.euclidean)
- f_colors1234 = f_colors.child(URI('http://bsfs.ai/schema/Feature/colors_spatial#1234'), dimension=1024)
- f_colors4321 = f_colors.child(URI('http://bsfs.ai/schema/Feature/colors_spatial#4321'), dimension=2048)
+ f_colors = types.ROOT_FEATURE.child(URI('https://schema.bsfs.io/core/Feature/colors_spatial'),
+ dtype=ns.bsfs.dtype().f16, distance=ns.bsfs.euclidean)
+ f_colors1234 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors_spatial#1234'), dimension=1024)
+ f_colors4321 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors_spatial#4321'), dimension=2048)
# schema
ref = Schema(
{p_annotation, p_tag, p_group, p_comment},
@@ -769,8 +785,10 @@ class TestFromString(unittest.TestCase):
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
# bsfs prefixes
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
# nodes
bsfs:Entity rdfs:subClassOf bsfs:Node ;
@@ -782,10 +800,10 @@ class TestFromString(unittest.TestCase):
# literals
xsd:string rdfs:subClassOf bsfs:Literal ;
rdfs:label "A sequence of characters"^^xsd:string .
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array.
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array.
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
xsd:boolean rdfs:subClassOf bsfs:Literal .
@@ -794,19 +812,19 @@ class TestFromString(unittest.TestCase):
rdfs:label "node annotation"^^xsd:string .
# feature instances
- <http://bsfs.ai/schema/Feature/colors_spatial> rdfs:subClassOf bsfs:Feature ;
- bsfs:dtype bsfs:f16 ;
+ <https://schema.bsfs.io/core/Feature/colors_spatial> rdfs:subClassOf bsa:Feature ;
+ bsfs:dtype <https://schema.bsfs.io/core/dtype#f16> ;
bsfs:distance bsfs:euclidean ;
# annotations
rdfs:label "ColorsSpatial instances. Dimension depends on instance."^^xsd:string ;
bsfs:first_arg "1234"^^xsd:integer ;
bsfs:second_arg "hello world"^^xsd:string .
- <http://bsfs.ai/schema/Feature/colors_spatial#1234> rdfs:subClassOf <http://bsfs.ai/schema/Feature/colors_spatial> ;
+ <https://schema.bsfs.io/core/Feature/colors_spatial#1234> rdfs:subClassOf <https://schema.bsfs.io/core/Feature/colors_spatial> ;
bsfs:dimension "1024"^^xsd:integer ;
rdfs:label "Main colors spatial instance"^^xsd:string .
- <http://bsfs.ai/schema/Feature/colors_spatial#4321> rdfs:subClassOf <http://bsfs.ai/schema/Feature/colors_spatial> ;
+ <https://schema.bsfs.io/core/Feature/colors_spatial#4321> rdfs:subClassOf <https://schema.bsfs.io/core/Feature/colors_spatial> ;
bsfs:dimension "2048"^^xsd:integer .
# predicate instances
@@ -834,19 +852,19 @@ class TestFromString(unittest.TestCase):
self.assertDictEqual(gen.node(ns.bsfs.Tag).annotations, {ns.rdfs.label: 'Tag'})
self.assertDictEqual(gen.literal(ns.xsd.string).annotations, {ns.rdfs.label: 'A sequence of characters'})
self.assertDictEqual(gen.predicate(ns.bsfs.Annotation).annotations, {ns.rdfs.label: 'node annotation'})
- self.assertDictEqual(gen.literal(URI('http://bsfs.ai/schema/Feature/colors_spatial')).annotations, {
+ self.assertDictEqual(gen.literal(URI('https://schema.bsfs.io/core/Feature/colors_spatial')).annotations, {
ns.rdfs.label: 'ColorsSpatial instances. Dimension depends on instance.',
ns.bsfs.first_arg: 1234,
ns.bsfs.second_arg: 'hello world',
})
- self.assertDictEqual(gen.literal(URI('http://bsfs.ai/schema/Feature/colors_spatial#1234')).annotations, {
+ self.assertDictEqual(gen.literal(URI('https://schema.bsfs.io/core/Feature/colors_spatial#1234')).annotations, {
ns.rdfs.label: 'Main colors spatial instance'})
self.assertDictEqual(gen.predicate(ns.bse.tag).annotations, {ns.rdfs.label: 'connect entity to a tag'})
# blank nodes result in an error
self.assertRaises(errors.BackendError, from_string, '''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
bsfs:Entity rdfs:subClassOf bsfs:Node ;
bsfs:foo _:bar .
''')
@@ -981,29 +999,29 @@ class TestToString(unittest.TestCase):
def test_feature(self):
# root features
- f_colors = types.ROOT_FEATURE.child(URI('http://bsfs.ai/schema/Feature/colors'),
+ f_colors = types.ROOT_FEATURE.child(URI('https://schema.bsfs.io/core/Feature/colors'),
distance=ns.bsfs.cosine)
# derived features
- f_colors1234 = f_colors.child(URI('http://bsfs.ai/schema/Feature/colors#1234'),
+ f_colors1234 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors#1234'),
dimension=1024) # inherits dtype, distance
- f_colors4321 = f_colors.child(URI('http://bsfs.ai/schema/Feature/colors#4321'),
+ f_colors4321 = f_colors.child(URI('https://schema.bsfs.io/core/Feature/colors#4321'),
dimension=2048, distance=ns.bsfs.euclidean) # inherits dtype
# create schema
schema = Schema(literals={f_colors, f_colors1234, f_colors4321})
schema_str = to_string(schema)
# all symbols are serialized
- self.assertIn('bsfs:Array', schema_str)
- self.assertIn('<http://bsfs.ai/schema/Feature/colors', schema_str)
- self.assertIn('<http://bsfs.ai/schema/Feature/colors#1234', schema_str)
- self.assertIn('<http://bsfs.ai/schema/Feature/colors#4321', schema_str)
+ self.assertIn('bsl:Array', schema_str)
+ self.assertIn('<https://schema.bsfs.io/core/Feature/colors', schema_str)
+ self.assertIn('<https://schema.bsfs.io/core/Feature/colors#1234', schema_str)
+ self.assertIn('<https://schema.bsfs.io/core/Feature/colors#4321', schema_str)
# inherited properties are not serialized
- self.assertIsNotNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#1234>[^\.]*bsfs:dimension[^\.]', schema_str))
- self.assertIsNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#1234>[^\.]*bsfs:dtype[^\.]', schema_str))
- self.assertIsNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#1234>[^\.]*bsfs:distance[^\.]', schema_str))
- self.assertIsNotNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#4321>[^\.]*bsfs:dimension[^\.]', schema_str))
- self.assertIsNotNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#4321>[^\.]*bsfs:distance[^\.]', schema_str))
- self.assertIsNone(re.search(r'<http://bsfs\.ai/schema/Feature/colors#4321>[^\.]*bsfs:dtype[^\.]', schema_str))
+ self.assertIsNotNone(re.search(r'<https://schema.bsfs\.io/core/Feature/colors#1234>.*[^\.]*bsfs:dimension[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#1234>.*[^\.]*bsfs:dtype[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#1234>.*[^\.]*bsfs:distance[^\.]', schema_str))
+ self.assertIsNotNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#4321>.*[^\.]*bsfs:dimension[^\.]', schema_str))
+ self.assertIsNotNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#4321>.*[^\.]*bsfs:distance[^\.]', schema_str))
+ self.assertIsNone(re.search(r'<https://schema\.bsfs\.io/core/Feature/colors#4321>.*[^\.]*bsfs:dtype[^\.]', schema_str))
# unserialize yields the original schema
self.assertEqual(schema, from_string(schema_str))
@@ -1014,12 +1032,12 @@ class TestToString(unittest.TestCase):
ns.bsfs.foo: 1234,
ns.bsfs.bar: False,
}
- f_colors = types.ROOT_FEATURE.child(URI('http://bsfs.ai/schema/Feature/colors'),
- dtype=ns.bsfs.f16, distance=ns.bsfs.euclidean,
+ f_colors = types.ROOT_FEATURE.child(URI('https://schema.bsfs.io/core/Feature/colors'),
+ dtype=ns.bsfs.dtype().f16, distance=ns.bsfs.euclidean,
**annotations)
self.assertDictEqual(
annotations,
- from_string(to_string(Schema(literals={f_colors}))).literal(URI('http://bsfs.ai/schema/Feature/colors')).annotations)
+ from_string(to_string(Schema(literals={f_colors}))).literal(URI('https://schema.bsfs.io/core/Feature/colors')).annotations)
## main ##
diff --git a/test/schema/test_types.py b/test/schema/test_types.py
index c5895d2..9bfa8c5 100644
--- a/test/schema/test_types.py
+++ b/test/schema/test_types.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import operator
import unittest
@@ -19,6 +14,8 @@ from bsfs.schema.types import _Type, Vertex, Node, Literal, Predicate, Feature
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestType(unittest.TestCase):
def test_parents(self):
diff --git a/test/triple_store/sparql/test_distance.py b/test/triple_store/sparql/test_distance.py
index 0659459..e95be5a 100644
--- a/test/triple_store/sparql/test_distance.py
+++ b/test/triple_store/sparql/test_distance.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import numpy as np
import unittest
diff --git a/test/triple_store/sparql/test_parse_fetch.py b/test/triple_store/sparql/test_parse_fetch.py
index 0961789..1d793e7 100644
--- a/test/triple_store/sparql/test_parse_fetch.py
+++ b/test/triple_store/sparql/test_parse_fetch.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import rdflib
import unittest
@@ -20,10 +15,9 @@ from bsfs.triple_store.sparql.parse_fetch import Fetch
## code ##
-bsfs = Namespace('http://bsfs.ai/schema', fsep='/')
-bse = Namespace('http://bsfs.ai/schema/Entity')
-bst = Namespace('http://bsfs.ai/schema/Tag')
-bsc = Namespace('http://bsfs.ai/schema/Collection')
+ns.bse = ns.bsfs.Entity()
+ns.bst = ns.bsfs.Tag()
+ns.bsc = ns.bsfs.Collection()
class TestParseFetch(unittest.TestCase):
@@ -32,10 +26,10 @@ class TestParseFetch(unittest.TestCase):
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
- prefix bst: <http://bsfs.ai/schema/Tag#>
- prefix bsc: <http://bsfs.ai/schema/Collection#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
+ prefix bsc: <https://schema.bsfs.io/core/Collection#>
# nodes
bsfs:Entity rdfs:subClassOf bsfs:Node .
@@ -88,43 +82,43 @@ class TestParseFetch(unittest.TestCase):
# graph to test queries
self.graph = rdflib.Graph()
# schema hierarchies
- self.graph.add((rdflib.URIRef('http://bsfs.ai/schema/Entity'), rdflib.RDFS.subClassOf, rdflib.URIRef('http://bsfs.ai/schema/Node')))
- self.graph.add((rdflib.URIRef('http://bsfs.ai/schema/Collection'), rdflib.RDFS.subClassOf, rdflib.URIRef('http://bsfs.ai/schema/Node')))
- self.graph.add((rdflib.URIRef('http://bsfs.ai/schema/Tag'), rdflib.RDFS.subClassOf, rdflib.URIRef('http://bsfs.ai/schema/Node')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Entity'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Collection'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Tag'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
# entities
- self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')))
- self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
# tags
- self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')))
- self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')))
# collections
- self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Collection')))
- self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Collection')))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Collection')))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Collection')))
# entity literals
- self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(bse.rank), rdflib.Literal('1234', datatype=rdflib.XSD.integer)))
- self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(bse.filename), rdflib.Literal('filename_1234', datatype=rdflib.XSD.string)))
- #self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(bse.rank), rdflib.Literal('4321', datatype=rdflib.XSD.integer)))
- self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(bse.filename), rdflib.Literal('filename_4321', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.rank), rdflib.Literal('1234', datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.filename), rdflib.Literal('filename_1234', datatype=rdflib.XSD.string)))
+ #self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.rank), rdflib.Literal('4321', datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.filename), rdflib.Literal('filename_4321', datatype=rdflib.XSD.string)))
# tag literals
- self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.URIRef(bst.label), rdflib.Literal('tag_label_1234', datatype=rdflib.XSD.string)))
- self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.URIRef(bst.label), rdflib.Literal('tag_label_4321', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.URIRef(ns.bst.label), rdflib.Literal('tag_label_1234', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.URIRef(ns.bst.label), rdflib.Literal('tag_label_4321', datatype=rdflib.XSD.string)))
# collection literals
- self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.URIRef(bsc.label), rdflib.Literal('collection_label_1234', datatype=rdflib.XSD.string)))
- self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.URIRef(bsc.rating), rdflib.Literal('1234', datatype=rdflib.XSD.integer)))
- self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.URIRef(bsc.label), rdflib.Literal('collection_label_4321', datatype=rdflib.XSD.string)))
- self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.URIRef(bsc.rating), rdflib.Literal('4321', datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.URIRef(ns.bsc.label), rdflib.Literal('collection_label_1234', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.URIRef(ns.bsc.rating), rdflib.Literal('1234', datatype=rdflib.XSD.integer)))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.URIRef(ns.bsc.label), rdflib.Literal('collection_label_4321', datatype=rdflib.XSD.string)))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.URIRef(ns.bsc.rating), rdflib.Literal('4321', datatype=rdflib.XSD.integer)))
# entity-tag links
- self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(bse.tag), rdflib.URIRef('http://example.com/tag#1234')))
- self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(bse.tag), rdflib.URIRef('http://example.com/tag#4321')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.tag), rdflib.URIRef('http://example.com/tag#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.tag), rdflib.URIRef('http://example.com/tag#4321')))
# entity-collection links
- self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(bse.collection), rdflib.URIRef('http://example.com/collection#1234')))
- self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(bse.collection), rdflib.URIRef('http://example.com/collection#4321')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.collection), rdflib.URIRef('http://example.com/collection#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef(ns.bse.collection), rdflib.URIRef('http://example.com/collection#4321')))
# collection-tag links
- self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.URIRef(bsc.tag), rdflib.URIRef('http://example.com/tag#1234')))
- self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.URIRef(bsc.tag), rdflib.URIRef('http://example.com/tag#4321')))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#1234'), rdflib.URIRef(ns.bsc.tag), rdflib.URIRef('http://example.com/tag#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/collection#4321'), rdflib.URIRef(ns.bsc.tag), rdflib.URIRef('http://example.com/tag#4321')))
# tag-entity links # NOTE: cross-over
- self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.URIRef(bst.main), rdflib.URIRef('http://example.com/entity#4321')))
- self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.URIRef(bst.main), rdflib.URIRef('http://example.com/entity#1234')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.URIRef(ns.bst.main), rdflib.URIRef('http://example.com/entity#4321')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.URIRef(ns.bst.main), rdflib.URIRef('http://example.com/entity#1234')))
# default parser
self.parser = Fetch(self.schema)
@@ -140,7 +134,7 @@ class TestParseFetch(unittest.TestCase):
# __call__ requires a parseable root
self.assertRaises(errors.BackendError, self.parser, self.ent, ast.filter.FilterExpression())
# __call__ returns an executable query
- q = self.parser(self.ent, ast.fetch.Fetch(bse.tag, ast.fetch.Value(bst.label, 'label')))
+ q = self.parser(self.ent, ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bst.label, 'label')))
self.assertSetEqual(set(q(self.graph)), {
(rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('tag_label_1234', datatype=rdflib.XSD.string)),
(rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('tag_label_4321', datatype=rdflib.XSD.string)),
@@ -154,8 +148,8 @@ class TestParseFetch(unittest.TestCase):
def test_all(self):
# multiple values query
q = self.parser(self.ent, ast.fetch.All(
- ast.fetch.Value(bse.filename, name='filename'),
- ast.fetch.Value(bse.rank, name='rank')),
+ ast.fetch.Value(ns.bse.filename, name='filename'),
+ ast.fetch.Value(ns.bse.rank, name='rank')),
)
self.assertSetEqual(set(q.names), {'filename', 'rank'})
if q.names == ('filename', 'rank'):
@@ -170,8 +164,8 @@ class TestParseFetch(unittest.TestCase):
})
# mixed values and node query
q = self.parser(self.ent, ast.fetch.All(
- ast.fetch.Value(bse.filename, name='filename'),
- ast.fetch.Node(bse.tag, name='tag'),
+ ast.fetch.Value(ns.bse.filename, name='filename'),
+ ast.fetch.Node(ns.bse.tag, name='tag'),
))
self.assertSetEqual(set(q.names), {'filename', 'tag'})
if q.names == ('filename', 'tag'):
@@ -185,9 +179,9 @@ class TestParseFetch(unittest.TestCase):
(rdflib.URIRef('http://example.com/entity#4321'), rdflib.URIRef('http://example.com/tag#4321'), rdflib.Literal('filename_4321', datatype=rdflib.XSD.string)),
})
# multiple values and second hop
- q = self.parser(self.ent, ast.fetch.Fetch(bse.tag, ast.fetch.All(
+ q = self.parser(self.ent, ast.fetch.Fetch(ns.bse.tag, ast.fetch.All(
ast.fetch.This(name='tag'),
- ast.fetch.Value(bst.label, name='label'),
+ ast.fetch.Value(ns.bst.label, name='label'),
)))
self.assertSetEqual(set(q.names), {'tag', 'label'})
if q.names == ('tag', 'label'):
@@ -205,13 +199,13 @@ class TestParseFetch(unittest.TestCase):
def test_fetch(self):
# two-hop query
- q = self.parser(self.ent, ast.fetch.Fetch(bse.tag, ast.fetch.Value(bst.label, 'tag_label')))
+ q = self.parser(self.ent, ast.fetch.Fetch(ns.bse.tag, ast.fetch.Value(ns.bst.label, 'tag_label')))
self.assertSetEqual(set(q(self.graph)), {
(rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('tag_label_1234', datatype=rdflib.XSD.string)),
(rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('tag_label_4321', datatype=rdflib.XSD.string)),
})
# three-hop-query
- q = self.parser(self.ent, ast.fetch.Fetch(bse.tag, ast.fetch.Fetch(bst.main, ast.fetch.Value(bse.rank, 'entity_rank'))))
+ q = self.parser(self.ent, ast.fetch.Fetch(ns.bse.tag, ast.fetch.Fetch(ns.bst.main, ast.fetch.Value(ns.bse.rank, 'entity_rank'))))
self.assertSetEqual(set(q(self.graph)), {
(rdflib.URIRef('http://example.com/entity#1234'), None),
(rdflib.URIRef('http://example.com/entity#4321'), rdflib.Literal('1234', datatype=rdflib.XSD.integer)),
@@ -220,9 +214,9 @@ class TestParseFetch(unittest.TestCase):
def test_node(self):
# cannot use the internal hop name
- self.assertRaises(errors.BackendError, self.parser, self.ent, ast.fetch.Node(bse.tag, self.parser.ngen.prefix[1:] + '123'))
+ self.assertRaises(errors.BackendError, self.parser, self.ent, ast.fetch.Node(ns.bse.tag, self.parser.ngen.prefix[1:] + '123'))
# a simple Node statement
- q = self.parser(self.ent, ast.fetch.Node(bse.tag, 'tag'))
+ q = self.parser(self.ent, ast.fetch.Node(ns.bse.tag, 'tag'))
self.assertSetEqual(set(q.names), {'tag'})
self.assertSetEqual(set(q(self.graph)), {
(rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef('http://example.com/tag#1234')),
@@ -232,9 +226,9 @@ class TestParseFetch(unittest.TestCase):
def test_value(self):
# cannot use the internal hop name
- self.assertRaises(errors.BackendError, self.parser, self.schema.node(ns.bsfs.Entity), ast.fetch.Value(bse.filename, self.parser.ngen.prefix[1:] + '123'))
+ self.assertRaises(errors.BackendError, self.parser, self.schema.node(ns.bsfs.Entity), ast.fetch.Value(ns.bse.filename, self.parser.ngen.prefix[1:] + '123'))
# a simple Value statement
- q = self.parser(self.ent, ast.fetch.Value(bse.filename, 'filename'))
+ q = self.parser(self.ent, ast.fetch.Value(ns.bse.filename, 'filename'))
self.assertSetEqual(set(q.names), {'filename'})
self.assertSetEqual(set(q(self.graph)), {
(rdflib.URIRef('http://example.com/entity#1234'), rdflib.Literal('filename_1234', datatype=rdflib.XSD.string)),
diff --git a/test/triple_store/sparql/test_parse_filter.py b/test/triple_store/sparql/test_parse_filter.py
index 6fa0cd3..a45f2ef 100644
--- a/test/triple_store/sparql/test_parse_filter.py
+++ b/test/triple_store/sparql/test_parse_filter.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import rdflib
import unittest
@@ -20,6 +15,8 @@ from bsfs.triple_store.sparql.parse_filter import Filter
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestParseFilter(unittest.TestCase):
def setUp(self):
# schema
@@ -27,25 +24,28 @@ class TestParseFilter(unittest.TestCase):
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsd: <https://schema.bsfs.io/core/distance#>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Image rdfs:subClassOf bsfs:Entity .
bsfs:Tag rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ xsd:integer rdfs:subClassOf bsl:Number .
bsfs:URI rdfs:subClassOf bsfs:Literal .
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:dimension "4"^^xsd:integer ;
bsfs:dtype xsd:integer ;
- bsfs:distance bsfs:euclidean .
+ bsfs:distance bsd:euclidean .
bse:colors rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -86,18 +86,18 @@ class TestParseFilter(unittest.TestCase):
# graph to test queries
self.graph = rdflib.Graph()
# schema hierarchies
- self.graph.add((rdflib.URIRef('http://bsfs.ai/schema/Entity'), rdflib.RDFS.subClassOf, rdflib.URIRef('http://bsfs.ai/schema/Node')))
- self.graph.add((rdflib.URIRef('http://bsfs.ai/schema/Image'), rdflib.RDFS.subClassOf, rdflib.URIRef('http://bsfs.ai/schema/Entity')))
- self.graph.add((rdflib.URIRef('http://bsfs.ai/schema/Tag'), rdflib.RDFS.subClassOf, rdflib.URIRef('http://bsfs.ai/schema/Node')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Entity'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Image'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ self.graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Tag'), rdflib.RDFS.subClassOf, rdflib.URIRef('https://schema.bsfs.io/core/Node')))
# entities
- self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')))
- self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Entity')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ self.graph.add((rdflib.URIRef('http://example.com/entity#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
# tags
- self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')))
- self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Tag')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')))
+ self.graph.add((rdflib.URIRef('http://example.com/tag#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Tag')))
# images
- self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Image')))
- self.graph.add((rdflib.URIRef('http://example.com/image#4321'), rdflib.RDF.type, rdflib.URIRef('http://bsfs.ai/schema/Image')))
+ self.graph.add((rdflib.URIRef('http://example.com/image#1234'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Image')))
+ self.graph.add((rdflib.URIRef('http://example.com/image#4321'), rdflib.RDF.type, rdflib.URIRef('https://schema.bsfs.io/core/Image')))
# node comments
self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('Me, Myself, and I', datatype=rdflib.XSD.string)))
self.graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.comment), rdflib.Literal('hello world', datatype=rdflib.XSD.string)))
@@ -162,6 +162,8 @@ class TestParseFilter(unittest.TestCase):
def test_is(self):
# _is requires a node
self.assertRaises(errors.BackendError, self.parser._is, self.schema.literal(ns.bsfs.Literal), ast.filter.Is('http://example.com/entity#1234'), '?ent')
+ # _is requires a serializable guid
+ self.assertRaises(ValueError, self.parser._is, self.schema.node(ns.bsfs.Entity), ast.filter.Is('http://example.com/entity#foo and bar'), '?ent')
# a single Is statement
q = self.parser(self.schema.node(ns.bsfs.Entity), ast.filter.Is('http://example.com/entity#1234'))
self.assertSetEqual({str(guid) for guid, in q(self.graph)},
diff --git a/test/triple_store/sparql/test_sparql.py b/test/triple_store/sparql/test_sparql.py
index 30876f2..a7e7d37 100644
--- a/test/triple_store/sparql/test_sparql.py
+++ b/test/triple_store/sparql/test_sparql.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import rdflib
import unittest
@@ -20,22 +15,25 @@ from bsfs.triple_store.sparql.sparql import SparqlStore
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestSparqlStore(unittest.TestCase):
def setUp(self):
self.schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
bsfs:User rdfs:subClassOf bsfs:Node .
xsd:string rdfs:subClassOf bsfs:Literal .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- bsfs:BinaryBlob rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ bsl:BinaryBlob rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
# non-unique literal
bse:comment rdfs:subClassOf bsfs:Predicate ;
@@ -64,7 +62,7 @@ class TestSparqlStore(unittest.TestCase):
# binary range
bse:asset rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
- rdfs:range bsfs:BinaryBlob .
+ rdfs:range bsl:BinaryBlob .
''')
self.schema_triples = {
@@ -73,12 +71,12 @@ class TestSparqlStore(unittest.TestCase):
(rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.xsd.string), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Array)),
- (rdflib.URIRef(ns.bsfs.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Number)),
+ (rdflib.URIRef(ns.bsl.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Array)),
+ (rdflib.URIRef(ns.bsl.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Number)),
(rdflib.URIRef(ns.bse.comment), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
@@ -106,7 +104,7 @@ class TestSparqlStore(unittest.TestCase):
store.schema = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
+ prefix bsfs: <https://schema.bsfs.io/core/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Document rdfs:subClassOf bsfs:Entity .
@@ -211,10 +209,10 @@ class TestSparqlStore(unittest.TestCase):
curr = curr + bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
- prefix bst: <http://bsfs.ai/schema/Tag#>
- prefix bsc: <http://bsfs.ai/schema/Collection#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
+ prefix bsc: <https://schema.bsfs.io/core/Collection#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
@@ -260,16 +258,16 @@ class TestSparqlStore(unittest.TestCase):
(rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
(rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.partOf), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
- (rdflib.URIRef('http://bsfs.ai/schema/Tag#usedIn'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
- (rdflib.URIRef('http://bsfs.ai/schema/Collection#tag'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
- (rdflib.URIRef('http://bsfs.ai/schema/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#usedIn'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Collection#tag'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
})
# add some instances of the new classes
p_partOf = curr.predicate(ns.bse.partOf)
p_shared = curr.predicate(ns.bse.shared)
- p_usedIn = curr.predicate('http://bsfs.ai/schema/Tag#usedIn')
- p_ctag = curr.predicate('http://bsfs.ai/schema/Collection#tag')
- p_principal = curr.predicate('http://bsfs.ai/schema/Tag#principal')
+ p_usedIn = curr.predicate('https://schema.bsfs.io/core/Tag#usedIn')
+ p_ctag = curr.predicate('https://schema.bsfs.io/core/Collection#tag')
+ p_principal = curr.predicate('https://schema.bsfs.io/core/Tag#principal')
store.create(curr.node(ns.bsfs.Collection), {URI('http://example.com/me/collection#1234'), URI('http://example.com/me/collection#4321')})
# add some more triples
store.set(curr.node(ns.bsfs.Entity), ent_ids, p_shared, {True})
@@ -288,9 +286,9 @@ class TestSparqlStore(unittest.TestCase):
(rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
(rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.partOf), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
- (rdflib.URIRef('http://bsfs.ai/schema/Tag#usedIn'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
- (rdflib.URIRef('http://bsfs.ai/schema/Collection#tag'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
- (rdflib.URIRef('http://bsfs.ai/schema/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#usedIn'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Collection#tag'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
# collections
(rdflib.URIRef('http://example.com/me/collection#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Collection)),
(rdflib.URIRef('http://example.com/me/collection#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Collection)),
@@ -314,17 +312,18 @@ class TestSparqlStore(unittest.TestCase):
curr = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
- prefix bst: <http://bsfs.ai/schema/Tag#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bst: <https://schema.bsfs.io/core/Tag#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Node .
bsfs:User rdfs:subClassOf bsfs:Node .
xsd:boolean rdfs:subClassOf bsfs:Literal .
- bsfs:Number rdfs:subClassOf bsfs:Literal .
- xsd:integer rdfs:subClassOf bsfs:Number .
+ bsl:Number rdfs:subClassOf bsfs:Literal .
+ xsd:integer rdfs:subClassOf bsl:Number .
bse:filesize rdfs:subClassOf bsfs:Predicate ;
rdfs:domain bsfs:Entity ;
@@ -365,16 +364,16 @@ class TestSparqlStore(unittest.TestCase):
(rdflib.URIRef(ns.bsfs.Tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.bsfs.User), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Node)),
(rdflib.URIRef(ns.xsd.boolean), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Array)),
- (rdflib.URIRef(ns.bsfs.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.bsfs.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
- (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Number)),
+ (rdflib.URIRef(ns.bsl.Array), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.BinaryBlob), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Array.Feature), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Array)),
+ (rdflib.URIRef(ns.bsl.Number), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.bsl.Time), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Literal)),
+ (rdflib.URIRef(ns.xsd.integer), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsl.Number)),
(rdflib.URIRef(ns.bse.shared), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.tag), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
(rdflib.URIRef(ns.bse.filesize), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
- (rdflib.URIRef('http://bsfs.ai/schema/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
+ (rdflib.URIRef('https://schema.bsfs.io/core/Tag#principal'), rdflib.RDFS.subClassOf, rdflib.URIRef(ns.bsfs.Predicate)),
# node instances
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
(rdflib.URIRef('http://example.com/me/entity#4321'), rdflib.RDF.type, rdflib.URIRef(ns.bsfs.Entity)),
@@ -405,13 +404,15 @@ class TestSparqlStore(unittest.TestCase):
invalid = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
+ prefix bsl: <https://schema.bsfs.io/core/Literal/>
+ prefix bsa: <https://schema.bsfs.io/core/Literal/Array/>
- bsfs:Array rdfs:subClassOf bsfs:Literal .
- bsfs:Feature rdfs:subClassOf bsfs:Array .
+ bsl:Array rdfs:subClassOf bsfs:Literal .
+ bsa:Feature rdfs:subClassOf bsl:Array .
- bsfs:Colors rdfs:subClassOf bsfs:Feature ;
+ bsfs:Colors rdfs:subClassOf bsa:Feature ;
bsfs:dimension "4"^^xsd:integer ;
bsfs:distance bsfs:foobar .
@@ -422,8 +423,8 @@ class TestSparqlStore(unittest.TestCase):
invalid = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:Tag rdfs:subClassOf bsfs:Entity . # inconsistent with previous tag definition
@@ -438,8 +439,8 @@ class TestSparqlStore(unittest.TestCase):
invalid = bsc.from_string('''
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
- prefix bsfs: <http://bsfs.ai/schema/>
- prefix bse: <http://bsfs.ai/schema/Entity#>
+ prefix bsfs: <https://schema.bsfs.io/core/>
+ prefix bse: <https://schema.bsfs.io/core/Entity#>
bsfs:Entity rdfs:subClassOf bsfs:Node .
bsfs:User rdfs:subClassOf bsfs:Node .
@@ -683,6 +684,9 @@ class TestSparqlStore(unittest.TestCase):
self.assertRaises(errors.ConsistencyError, store.create, self.schema.node(ns.bsfs.Entity).child(ns.bsfs.invalid), {
URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
+ # guid must be valid
+ self.assertRaises(ValueError, store.create, self.schema.node(ns.bsfs.Entity), {'http://example.com/me/foo and bar'})
+
# can create some nodes
ent_type = store.schema.node(ns.bsfs.Entity)
store.create(ent_type, {URI('http://example.com/me/entity#1234'), URI('http://example.com/me/entity#4321')})
@@ -771,6 +775,9 @@ class TestSparqlStore(unittest.TestCase):
# invalid predicate is not permitted
self.assertRaises(errors.ConsistencyError, store.set, ent_type, ent_ids, p_invalid, {'http://example.com/me/tag#1234'})
+ # invalid guid is not permitted
+ self.assertRaises(ValueError, store.set, ent_type, {'http://example.com/me/foo and bar'}, p_filesize, {1234})
+
# predicate must match node_type
self.assertRaises(errors.ConsistencyError, store.set, tag_type, tag_ids, p_filesize, {1234})
@@ -944,9 +951,9 @@ class TestSparqlStore(unittest.TestCase):
p_asset = store.schema.predicate(ns.bse.asset)
store.set(ent_type, ent_ids, p_asset, {bytes(range(128)), bytes(range(128, 256))})
blob1 = rdflib.Literal('AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8=',
- datatype=rdflib.URIRef(ns.bsfs.BinaryBlob))
+ datatype=rdflib.URIRef(ns.bsl.BinaryBlob))
blob2 = rdflib.Literal('gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8zNzs/Q0dLT1NXW19jZ2tvc3d7f4OHi4+Tl5ufo6err7O3u7/Dx8vP09fb3+Pn6+/z9/v8=',
- datatype=rdflib.URIRef(ns.bsfs.BinaryBlob))
+ datatype=rdflib.URIRef(ns.bsl.BinaryBlob))
self.assertTrue(set(store._graph).issuperset({
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_asset.uri), blob1),
(rdflib.URIRef('http://example.com/me/entity#1234'), rdflib.URIRef(p_asset.uri), blob2),
diff --git a/test/triple_store/sparql/test_utils.py b/test/triple_store/sparql/test_utils.py
index 073b8f8..44a1299 100644
--- a/test/triple_store/sparql/test_utils.py
+++ b/test/triple_store/sparql/test_utils.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# standard imports
import operator
import re
@@ -21,6 +16,8 @@ from bsfs.triple_store.sparql.utils import GenHopName, Query
## code ##
+ns.bse = ns.bsfs.Entity()
+
class TestGenHopName(unittest.TestCase):
def test_next(self):
# baseline
@@ -45,7 +42,7 @@ class TestGenHopName(unittest.TestCase):
class TestQuery(unittest.TestCase):
def setUp(self):
- self.root_type = 'http://bsfs.ai/schema/Entity'
+ self.root_type = 'https://schema.bsfs.io/core/Entity'
self.root_head = '?root'
self.select = (('?head', 'name'), )
self.where = f'?root <{ns.bse.tag}> ?head'
@@ -61,8 +58,8 @@ class TestQuery(unittest.TestCase):
self.assertEqual(q, Query(self.root_type, self.root_head, self.select, self.where))
self.assertEqual(hash(q), hash(Query(self.root_type, self.root_head, self.select, self.where)))
# comparison respects root_type
- self.assertNotEqual(q, Query('http://bsfs.ai/schema/Tag', self.root_head, self.select, self.where))
- self.assertNotEqual(hash(q), hash(Query('http://bsfs.ai/schema/Tag', self.root_head, self.select, self.where)))
+ self.assertNotEqual(q, Query('https://schema.bsfs.io/core/Tag', self.root_head, self.select, self.where))
+ self.assertNotEqual(hash(q), hash(Query('https://schema.bsfs.io/core/Tag', self.root_head, self.select, self.where)))
# comparison respects root_head
self.assertNotEqual(q, Query(self.root_type, '?foo', self.select, self.where))
self.assertNotEqual(hash(q), hash(Query(self.root_type, '?foo', self.select, self.where)))
@@ -74,7 +71,7 @@ class TestQuery(unittest.TestCase):
self.assertNotEqual(hash(q), hash(Query(self.root_type, self.root_head, self.select, '?root bse:filename ?head')))
# string conversion
self.assertEqual(str(q), q.query)
- self.assertEqual(repr(q), "Query(http://bsfs.ai/schema/Entity, ?root, (('?head', 'name'),), ?root <http://bsfs.ai/schema/Entity#tag> ?head)")
+ self.assertEqual(repr(q), "Query(https://schema.bsfs.io/core/Entity, ?root, (('?head', 'name'),), ?root <https://schema.bsfs.io/core/Entity#tag> ?head)")
def test_add(self):
q = Query(self.root_type, self.root_head, self.select, self.where)
@@ -82,7 +79,7 @@ class TestQuery(unittest.TestCase):
self.assertRaises(TypeError, operator.add, q, 1234)
self.assertRaises(TypeError, operator.add, q, 'foobar')
# root type and head must match
- self.assertRaises(ValueError, operator.add, q, Query('http://bsfs.ai/schema/Tag', self.root_head))
+ self.assertRaises(ValueError, operator.add, q, Query('https://schema.bsfs.io/core/Node/Tag', self.root_head))
self.assertRaises(ValueError, operator.add, q, Query(self.root_type, '?foobar'))
# select and were are combined
combo = q + Query(self.root_type, self.root_head, (('?foo', 'bar'), ), f'?root <{ns.bse.filename}> ?foo')
@@ -118,23 +115,23 @@ class TestQuery(unittest.TestCase):
return value
# query composes a valid query
q = Query(self.root_type, self.root_head, self.select, self.where)
- self.assertEqual(normalize(q.query), normalize(f'select ?root (?head as ?name) where {{ ?root <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <http://bsfs.ai/schema/Entity> . ?root <{ns.bse.tag}> ?head }}'))
+ self.assertEqual(normalize(q.query), normalize(f'select distinct ?root (?head as ?name) where {{ ?root <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <https://schema.bsfs.io/core/Entity> . ?root <{ns.bse.tag}> ?head }} order by str(?root)'))
# select and where are optional
q = Query(self.root_type, self.root_head)
- self.assertEqual(normalize(q.query), normalize(f'select ?root where {{ ?root <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <http://bsfs.ai/schema/Entity> . }}'))
+ self.assertEqual(normalize(q.query), normalize(f'select distinct ?root where {{ ?root <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <https://schema.bsfs.io/core/Entity> . }} order by str(?root)'))
# select and where need not to correspond
q = Query(self.root_type, self.root_head, (('?head', 'name'), ))
- self.assertEqual(normalize(q.query), normalize(f'select ?root (?head as ?name) where {{ ?root <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <http://bsfs.ai/schema/Entity> . }}'))
+ self.assertEqual(normalize(q.query), normalize(f'select distinct ?root (?head as ?name) where {{ ?root <{ns.rdf.type}>/<{ns.rdfs.subClassOf}>* <https://schema.bsfs.io/core/Entity> . }} order by str(?root)'))
# query is used for string representation
self.assertEqual(str(q), q.query)
def test_call(self):
graph = rdflib.Graph()
# schema
- graph.add((rdflib.URIRef('http://bsfs.ai/schema/Document'), rdflib.URIRef(ns.rdfs.subClassOf), rdflib.URIRef('http://bsfs.ai/schema/Entity')))
+ graph.add((rdflib.URIRef('https://schema.bsfs.io/core/Document'), rdflib.URIRef(ns.rdfs.subClassOf), rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
# nodes
- graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.rdf.type), rdflib.URIRef('http://bsfs.ai/schema/Entity')))
- graph.add((rdflib.URIRef('http://example.com/doc#1234'), rdflib.URIRef(ns.rdf.type), rdflib.URIRef('http://bsfs.ai/schema/Document')))
+ graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.rdf.type), rdflib.URIRef('https://schema.bsfs.io/core/Entity')))
+ graph.add((rdflib.URIRef('http://example.com/doc#1234'), rdflib.URIRef(ns.rdf.type), rdflib.URIRef('https://schema.bsfs.io/core/Document')))
# links
graph.add((rdflib.URIRef('http://example.com/entity#1234'), rdflib.URIRef(ns.bse.tag), rdflib.Literal('tag#1234', datatype=rdflib.XSD.string)))
graph.add((rdflib.URIRef('http://example.com/doc#1234'), rdflib.URIRef(ns.bse.tag), rdflib.Literal('tag#1234', datatype=rdflib.XSD.string)))
diff --git a/test/triple_store/test_base.py b/test/triple_store/test_base.py
index 56a2539..4c4a9b6 100644
--- a/test/triple_store/test_base.py
+++ b/test/triple_store/test_base.py
@@ -1,9 +1,4 @@
-"""
-Part of the bsfs test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
diff --git a/test/utils/test_commons.py b/test/utils/test_commons.py
index 3ad6dea..29e3046 100644
--- a/test/utils/test_commons.py
+++ b/test/utils/test_commons.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import unittest
diff --git a/test/utils/test_uri.py b/test/utils/test_uri.py
index 770e65a..1c4c9f9 100644
--- a/test/utils/test_uri.py
+++ b/test/utils/test_uri.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import operator
import unittest
@@ -40,6 +35,16 @@ class TestURI(unittest.TestCase):
self.assertTrue(URI.is_parseable('telnet://192.0.2.16:80/'))
self.assertTrue(URI.is_parseable('urn:oasis:names:specification:docbook:dtd:xml:4.1.2'))
+ # some characters are prohibited
+ self.assertFalse(URI.is_parseable('http://example.com/foo<bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo>bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo{bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo}bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo|bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo^bar'))
+ self.assertFalse(URI.is_parseable('http://example.com/foo\\bar'))
+
# uri cannot end with a scheme delimiter
self.assertFalse(URI.is_parseable('http://'))
# port must be a number
@@ -164,10 +169,10 @@ class TestURI(unittest.TestCase):
def test_overloaded(self):
# composition
- self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment') + 'hello', URI)
- self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment') * 2, URI)
- self.assertIsInstance(2 * URI('http://user@www.example.com:1234/{}/path1?{}#fragment'), URI) # rmul
- self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment').join(['hello', 'world']) , URI)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment') + 'hello', URI)
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment') * 2, URI)
+ self.assertIsInstance(2 * URI('http://user@www.example.com:1234/path0/path1?query#fragment'), URI) # rmul
+ self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').join(['hello', 'world']) , URI)
# stripping
self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').strip(), URI)
self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').lstrip(), URI)
@@ -176,7 +181,6 @@ class TestURI(unittest.TestCase):
self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').lower(), URI)
self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').upper(), URI)
# formatting
- self.assertIsInstance(URI('http://user@www.example.com:1234/{}/path1?{}#fragment').format('hello', 'world'), URI)
self.assertIsInstance(URI('http://user@www.example.com:1234/%s/path1?%s#fragment') % ('hello', 'world'), URI)
self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').replace('path0', 'pathX'), URI)
diff --git a/test/utils/test_uuid.py b/test/utils/test_uuid.py
index 804b063..8f519d9 100644
--- a/test/utils/test_uuid.py
+++ b/test/utils/test_uuid.py
@@ -1,9 +1,4 @@
-"""
-Part of the tagit test suite.
-A copy of the license is provided with the project.
-Author: Matthias Baumgartner, 2022
-"""
# imports
import os
import re