diff options
Diffstat (limited to 'bsie/matcher')
-rw-r--r-- | bsie/matcher/__init__.py | 17 | ||||
-rw-r--r-- | bsie/matcher/default_matcher.py | 76 | ||||
-rw-r--r-- | bsie/matcher/matcher.py | 61 | ||||
-rw-r--r-- | bsie/matcher/nodes.py | 49 |
4 files changed, 203 insertions, 0 deletions
diff --git a/bsie/matcher/__init__.py b/bsie/matcher/__init__.py new file mode 100644 index 0000000..836bacf --- /dev/null +++ b/bsie/matcher/__init__.py @@ -0,0 +1,17 @@ + +# standard imports +import typing + +# inner-module imports +from . import nodes +from .default_matcher import DefaultMatcher +from .matcher import Matcher + +# exports +__all__: typing.Sequence[str] = ( + 'DefaultMatcher', + 'Matcher', + 'nodes', + ) + +## EOF ## diff --git a/bsie/matcher/default_matcher.py b/bsie/matcher/default_matcher.py new file mode 100644 index 0000000..94bbe2c --- /dev/null +++ b/bsie/matcher/default_matcher.py @@ -0,0 +1,76 @@ + +# standard imports +import os +import typing +import urllib + +# bsie imports +from bsie.utils import bsfs + +# inner-module imports +from . import nodes +from .matcher import Matcher + +# exports +__all__: typing.Sequence[str] = ( + 'DefaultMatcher', + ) + + +## code ## + +class DefaultMatcher(Matcher): + """Compose URIs as <host/user/node_type#fragment> + + What information is used as fragment depends on the node type. + Typically, the default is to use the "ucid" hint. + The fallback in all cases is to generate a random uuid. + + Never changes previously assigned uris. Sets uris in-place. + + """ + + def __init__( + self, + host: bsfs.URI, + user: str, + ): + self._prefix = bsfs.Namespace(os.path.join(host, user)) + + def match_node(self, node: nodes.Node) -> nodes.Node: + if node.uri is not None: + return node + if isinstance(node, nodes.Entity): + return self.match_entity(node) + if isinstance(node, nodes.Preview): + return self.match_preview(node) + if isinstance(node, nodes.Tag): + return self.match_tag(node) + if isinstance(node, nodes.Face): + return self.match_face(node) + raise ValueError(f'no matching policy available for bsfs.typename{node}') + + def match_entity(self, node: nodes.Entity) -> nodes.Entity: + """Set a bsn:Entity node's uri fragment to its ucid.""" + node.uri = getattr(self._prefix.file(), node.ucid) + return node + + def match_preview(self, node: nodes.Preview) -> nodes.Preview: + """Set a bsn:Preview node's uri fragment to its ucid and size suffix.""" + fragment = node.ucid + '_s' + str(node.size) + node.uri = getattr(self._prefix.preview(), fragment) + return node + + def match_tag(self, node: nodes.Tag) -> nodes.Tag: + """Set a bsn:Tag node's uri to its label.""" + # FIXME: match to existing tags in bsfs storage?! + fragment = urllib.parse.quote(node.label) + node.uri = getattr(self._prefix.tag(), fragment) + return node + + def match_face(self, node: nodes.Face) -> nodes.Face: + """Set a bsn:Face node's uri to its ucid.""" + node.uri = getattr(self._prefix.face(), node.ucid) + return node + +## EOF ## diff --git a/bsie/matcher/matcher.py b/bsie/matcher/matcher.py new file mode 100644 index 0000000..a89626f --- /dev/null +++ b/bsie/matcher/matcher.py @@ -0,0 +1,61 @@ + +# standard imports +import abc +import typing + +# bsie imports +from bsie.utils import bsfs + +# inner-module imports +from . import nodes + +# exports +__all__: typing.Sequence[str] = ( + 'Matcher', + ) + + +## code ## + +class Matcher(): + """Determine node uri's from node hints.""" + def __call__( + self, + iterable: typing.Iterable[typing.Tuple[nodes.Node, bsfs.URI, typing.Any]], + ): + """Apply the matcher on a triple iterator.""" + return MatcherIterator(self, iterable) + + @abc.abstractmethod + def match_node(self, node: nodes.Node) -> nodes.Node: + """Apply the matcher on a node.""" + + +class MatcherIterator(): + """Iterates over triples, determines uris according to a *matcher* as it goes.""" + + # source triple iterator. + _iterable: typing.Iterable[typing.Tuple[nodes.Node, bsfs.URI, typing.Any]] + + # node matcher + _matcher: Matcher + + def __init__( + self, + matcher: Matcher, + iterable: typing.Iterable[typing.Tuple[nodes.Node, bsfs.URI, typing.Any]], + ): + self._iterable = iterable + self._matcher = matcher + + def __iter__(self): + for node, pred, value in self._iterable: + # handle subject + self._matcher.match_node(node) + # handle value + if isinstance(value, nodes.Node): + self._matcher.match_node(value) + # yield triple + yield node, pred, value + +## EOF ## diff --git a/bsie/matcher/nodes.py b/bsie/matcher/nodes.py new file mode 100644 index 0000000..047e7d1 --- /dev/null +++ b/bsie/matcher/nodes.py @@ -0,0 +1,49 @@ + +# standard imports +from dataclasses import dataclass +import typing + +# bsie imports +from bsie.utils import bsfs, ns + +# exports +__all__: typing.Sequence[str] = ( + 'Entity', + 'Face', + 'Node', + 'Person', + 'Preview', + 'Tag', + ) + +@dataclass(kw_only=True, unsafe_hash=True) +class Node: # pylint: disable=missing-class-docstring + # FIXME: Only allow changes to uri after init + uri: typing.Optional[bsfs.URI] = None + +@dataclass(kw_only=True, unsafe_hash=True) +class Entity(Node): # pylint: disable=missing-class-docstring + node_type: bsfs.URI = ns.bsn.Entity + ucid: str + +@dataclass(kw_only=True, unsafe_hash=True) +class Face(Node): # pylint: disable=missing-class-docstring + node_type: bsfs.URI = ns.bsn.Face + ucid: str + +@dataclass(kw_only=True, unsafe_hash=True) +class Person(Node): # pylint: disable=missing-class-docstring + node_type: bsfs.URI = ns.bsn.Person + +@dataclass(kw_only=True, unsafe_hash=True) +class Preview(Node): # pylint: disable=missing-class-docstring + node_type: bsfs.URI = ns.bsn.Preview + ucid: str + size: int + +@dataclass(kw_only=True, unsafe_hash=True) +class Tag(Node): # pylint: disable=missing-class-docstring + node_type: bsfs.URI = ns.bsn.Tag + label: str + +## EOF ## |