From 0e52514639b043454425a9cc2317d27e628a1027 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Sun, 18 Dec 2022 13:42:34 +0100 Subject: namespace and uri extensions --- bsfs/namespace/namespace.py | 48 ++++++++++++++++------ bsfs/namespace/predefined.py | 14 +++---- bsfs/utils/uri.py | 50 +++++++++++++++++++++++ test/namespace/test_namespace.py | 87 ++++++++++++++++++++++++++++------------ test/utils/test_uri.py | 18 +++++++++ 5 files changed, 172 insertions(+), 45 deletions(-) diff --git a/bsfs/namespace/namespace.py b/bsfs/namespace/namespace.py index 8080f5d..f652dcd 100644 --- a/bsfs/namespace/namespace.py +++ b/bsfs/namespace/namespace.py @@ -29,29 +29,55 @@ class Namespace(): # namespace prefix. prefix: URI - def __init__(self, prefix: URI): - self.prefix = URI(prefix) + # 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 + return isinstance(other, type(self)) \ + and self.prefix == other.prefix \ + and self.fsep == other.fsep \ + and self.psep == other.psep def __hash__(self) -> int: - return hash((type(self), self.prefix)) + return hash((type(self), self.prefix, self.fsep, self.psep)) def __str__(self) -> str: return f'{typename(self)}({self.prefix})' def __repr__(self) -> str: - return f'{typename(self)}({self.prefix})' + return f'{typename(self)}({self.prefix}, {self.fsep}, {self.psep})' def __getattr__(self, fragment: str) -> URI: """Return prefix + fragment.""" - return URI(self.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) + class ClosedNamespace(Namespace): """Namespace that covers a restricted set of URIs.""" @@ -59,8 +85,8 @@ class ClosedNamespace(Namespace): # set of permissible fragments. fragments: typing.Set[str] - def __init__(self, prefix: URI, *args: str): - super().__init__(prefix) + def __init__(self, prefix: URI, *args: str, fsep: str = '#', psep: str = '/'): + super().__init__(prefix, fsep, psep) self.fragments = set(args) def __eq__(self, other: typing.Any) -> bool: @@ -70,11 +96,9 @@ class ClosedNamespace(Namespace): return hash((type(self), self.prefix, tuple(sorted(self.fragments)))) def __getattr__(self, fragment: str) -> URI: - """Return prefix + fragment. - Raises a KeyError if the fragment is not allowed in this namespace. - """ + """Return prefix + fragment or raise a KeyError if the fragment is not part of this namespace.""" if fragment not in self.fragments: - raise KeyError('fragment') + raise KeyError(f'{fragment} is not a valid fragment of namespace {self.prefix}') return super().__getattr__(fragment) ## EOF ## diff --git a/bsfs/namespace/predefined.py b/bsfs/namespace/predefined.py index 21ca560..cd48a46 100644 --- a/bsfs/namespace/predefined.py +++ b/bsfs/namespace/predefined.py @@ -14,17 +14,17 @@ from bsfs.utils import URI from . import namespace # essential bsfs namespaces -bsfs: namespace.Namespace = namespace.Namespace(URI('http://bsfs.ai/schema/')) +bsfs: namespace.Namespace = namespace.Namespace(URI('http://bsfs.ai/schema'), fsep='/') # 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#')) +bse: namespace.Namespace = namespace.Namespace(URI('http://bsfs.ai/schema/Entity')) +bsm: namespace.Namespace = namespace.Namespace(URI('http://bsfs.ai/schema/Meta')) # 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/')) -xsd: namespace.Namespace = namespace.Namespace(URI('http://www.w3.org/2001/XMLSchema#')) +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')) __all__: typing.Sequence[str] = ( 'bse', diff --git a/bsfs/utils/uri.py b/bsfs/utils/uri.py index a56423a..84854a4 100644 --- a/bsfs/utils/uri.py +++ b/bsfs/utils/uri.py @@ -193,4 +193,54 @@ class URI(str): # return the default value return default + + # overload composition methods + + def __add__(self, *args) -> 'URI': + return URI(super().__add__(*args)) + + def join(self, *args) -> 'URI': + return URI(super().join(*args)) + + def __mul__(self, *args) -> 'URI': + return URI(super().__mul__(*args)) + + def __rmul__(self, *args) -> 'URI': + return URI(super().__rmul__(*args)) + + + # overload casefold methods + + def lower(self, *args) -> 'URI': + return URI(super().lower(*args)) + + def upper(self, *args) -> 'URI': + return URI(super().upper(*args)) + + + # overload stripping methods + + def strip(self, *args) -> 'URI': + return URI(super().strip(*args)) + + def lstrip(self, *args) -> 'URI': + return URI(super().lstrip(*args)) + + def rstrip(self, *args) -> 'URI': + return URI(super().rstrip(*args)) + + + # 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)) + + def replace(self, *args) -> 'URI': + return URI(super().replace(*args)) + + + ## EOF ## diff --git a/test/namespace/test_namespace.py b/test/namespace/test_namespace.py index 1ad53e3..f109653 100644 --- a/test/namespace/test_namespace.py +++ b/test/namespace/test_namespace.py @@ -5,8 +5,12 @@ A copy of the license is provided with the project. Author: Matthias Baumgartner, 2022 """ # imports +import operator import unittest +# bsfs imports +from bsfs.utils import URI + # objects to test from bsfs.namespace.namespace import Namespace, ClosedNamespace @@ -16,73 +20,104 @@ from bsfs.namespace.namespace import Namespace, ClosedNamespace class TestNamespace(unittest.TestCase): def test_essentials(self): # string conversion - self.assertEqual(str(Namespace('http://example.org/')), 'Namespace(http://example.org/)') - self.assertEqual(str(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#')), 'Namespace(http://example.org#)') + self.assertEqual(str(Namespace('http://example.org/')), 'Namespace(http://example.org)') + self.assertEqual(str(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#')), '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////')), 'Namespace(http://example.org)') + self.assertEqual(str(Namespace('http://example.org####')), 'Namespace(http://example.org)') + self.assertEqual(repr(Namespace('http://example.org///##')), 'Namespace(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/')) - self.assertNotEqual(Namespace('http://example.org/'), Namespace('http://example.org#')) - self.assertNotEqual(Namespace('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.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/'))) - self.assertNotEqual(hash(Namespace('http://example.org/')), hash(Namespace('http://example.org#'))) - self.assertNotEqual(hash(Namespace('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/').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') 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')['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_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): def test_essentials(self): # string conversion - self.assertEqual(str(ClosedNamespace('http://example.org/')), 'ClosedNamespace(http://example.org/)') - self.assertEqual(str(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#')), 'ClosedNamespace(http://example.org#)') + self.assertEqual(str(ClosedNamespace('http://example.org/')), 'ClosedNamespace(http://example.org)') + self.assertEqual(str(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#')), '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, #, .)') # 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'), 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/')) - self.assertNotEqual(ClosedNamespace('http://example.org/'), ClosedNamespace('http://example.org#')) - self.assertNotEqual(ClosedNamespace('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')), 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.assertNotEqual(hash(ClosedNamespace('http://example.org/')), hash(ClosedNamespace('http://example.org#'))) - self.assertNotEqual(hash(ClosedNamespace('http://example.org#')), hash(ClosedNamespace('http://example.org/'))) 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/', '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/', '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') diff --git a/test/utils/test_uri.py b/test/utils/test_uri.py index 976e75d..770e65a 100644 --- a/test/utils/test_uri.py +++ b/test/utils/test_uri.py @@ -5,6 +5,7 @@ A copy of the license is provided with the project. Author: Matthias Baumgartner, 2022 """ # imports +import operator import unittest # objects to test @@ -161,6 +162,23 @@ class TestURI(unittest.TestCase): # empty URI self.assertRaises(ValueError, getattr, URI(''), 'fragment') + 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) + # 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) + self.assertIsInstance(URI('http://user@www.example.com:1234/path0/path1?query#fragment').rstrip(), URI) + # case fold + 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) ## main ## -- cgit v1.2.3