diff options
author | Matthias Baumgartner <dev@igsor.net> | 2023-03-04 13:31:11 +0100 |
---|---|---|
committer | Matthias Baumgartner <dev@igsor.net> | 2023-03-04 13:44:26 +0100 |
commit | 4fead04055be4967d9ea3b24ff61fe37a93108dd (patch) | |
tree | 40fb5ea2874466cae1b3fb6ad84d1d133992bf34 /bsfs | |
parent | 2c6c23f85e7f2123c508f9ff8a4aa776948bb589 (diff) | |
download | bsfs-4fead04055be4967d9ea3b24ff61fe37a93108dd.tar.gz bsfs-4fead04055be4967d9ea3b24ff61fe37a93108dd.tar.bz2 bsfs-4fead04055be4967d9ea3b24ff61fe37a93108dd.zip |
namespace refactoring and cleanup
Diffstat (limited to 'bsfs')
-rw-r--r-- | bsfs/graph/ac/null.py | 2 | ||||
-rw-r--r-- | bsfs/graph/nodes.py | 4 | ||||
-rw-r--r-- | bsfs/graph/resolve.py | 4 | ||||
-rw-r--r-- | bsfs/graph/schema.nt | 11 | ||||
-rw-r--r-- | bsfs/namespace/__init__.py | 3 | ||||
-rw-r--r-- | bsfs/namespace/namespace.py | 97 | ||||
-rw-r--r-- | bsfs/namespace/predefined.py | 27 | ||||
-rw-r--r-- | bsfs/query/ast/filter_.py | 3 | ||||
-rw-r--r-- | bsfs/query/matcher.py | 4 | ||||
-rw-r--r-- | bsfs/query/validator.py | 4 | ||||
-rw-r--r-- | bsfs/schema/serialize.py | 15 | ||||
-rw-r--r-- | bsfs/schema/types.py | 14 | ||||
-rw-r--r-- | bsfs/triple_store/sparql/distance.py | 6 | ||||
-rw-r--r-- | bsfs/triple_store/sparql/sparql.py | 6 |
14 files changed, 78 insertions, 122 deletions
diff --git a/bsfs/graph/ac/null.py b/bsfs/graph/ac/null.py index 3a391aa..c9ec7d0 100644 --- a/bsfs/graph/ac/null.py +++ b/bsfs/graph/ac/null.py @@ -24,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.""" diff --git a/bsfs/graph/nodes.py b/bsfs/graph/nodes.py index 74f4c4f..47b0217 100644 --- a/bsfs/graph/nodes.py +++ b/bsfs/graph/nodes.py @@ -170,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 @@ -394,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 95dcfc1..a58eb67 100644 --- a/bsfs/graph/resolve.py +++ b/bsfs/graph/resolve.py @@ -27,8 +27,8 @@ 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))) """ diff --git a/bsfs/graph/schema.nt b/bsfs/graph/schema.nt index cba5e80..37bba5e 100644 --- a/bsfs/graph/schema.nt +++ b/bsfs/graph/schema.nt @@ -4,15 +4,16 @@ 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:float 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:float ; bsfs:unique "true"^^xsd:boolean . diff --git a/bsfs/namespace/__init__.py b/bsfs/namespace/__init__.py index 1784808..76f39a2 100644 --- a/bsfs/namespace/__init__.py +++ b/bsfs/namespace/__init__.py @@ -4,11 +4,10 @@ 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 0a62b78..b388f53 100644 --- a/bsfs/namespace/namespace.py +++ b/bsfs/namespace/namespace.py @@ -3,97 +3,52 @@ 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 15f12ac..8b60d39 100644 --- a/bsfs/namespace/predefined.py +++ b/bsfs/namespace/predefined.py @@ -2,29 +2,28 @@ # 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/ast/filter_.py b/bsfs/query/ast/filter_.py index 56c982e..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'))), ... ) diff --git a/bsfs/query/matcher.py b/bsfs/query/matcher.py index 5f3b07e..17c9c8e 100644 --- a/bsfs/query/matcher.py +++ b/bsfs/query/matcher.py @@ -215,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 1ce44e9..10ca492 100644 --- a/bsfs/query/validator.py +++ b/bsfs/query/validator.py @@ -177,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 @@ -218,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_ diff --git a/bsfs/schema/serialize.py b/bsfs/schema/serialize.py index b05b289..ea8b2f4 100644 --- a/bsfs/schema/serialize.py +++ b/bsfs/schema/serialize.py @@ -241,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 104580d..5834df8 100644 --- a/bsfs/schema/types.py +++ b/bsfs/schema/types.py @@ -376,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/sparql/distance.py b/bsfs/triple_store/sparql/distance.py index 9b58088..2c2f355 100644 --- a/bsfs/triple_store/sparql/distance.py +++ b/bsfs/triple_store/sparql/distance.py @@ -43,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/sparql.py b/bsfs/triple_store/sparql/sparql.py index 68c0027..99e67d6 100644 --- a/bsfs/triple_store/sparql/sparql.py +++ b/bsfs/triple_store/sparql/sparql.py @@ -28,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.""" @@ -335,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): |