Skip to content

Commit f56629a

Browse files
committed
version of PR !244 that uses curies
1 parent d7aee85 commit f56629a

File tree

4 files changed

+88
-46
lines changed

4 files changed

+88
-46
lines changed
+66-25
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,81 @@
11
from logging import warning
2-
from typing import Optional, Union, Dict
2+
from typing import Optional, Union, Dict, List
33

44
from rdflib import Namespace, URIRef
5+
from curies import Converter, Record
6+
7+
class CurieNamespaceCatalog(object):
8+
catalog: Dict[str, "CurieNamespace"]
9+
def __init__(self) -> None:
10+
self.namespaces = []
11+
self._converter: Optional[Converter] = None
12+
13+
@property
14+
def converter(self):
15+
if not self._converter:
16+
self._converter = self._buildConverter()
17+
return self._converter
18+
19+
def _buildConverter(self):
20+
records = []
21+
namespaces_to_treat = self.namespaces[:]
22+
while len(namespaces_to_treat) > 0:
23+
ns = namespaces_to_treat.pop(0)
24+
prefix = ns.prefix
25+
uri = str(ns)
26+
all_prefixes = [prefix]
27+
all_uris = [uri]
28+
iteration_needed = True
29+
while iteration_needed:
30+
iteration_needed = False
31+
for possible_synonym in namespaces_to_treat[:]:
32+
if possible_synonym.prefix in all_prefixes:
33+
all_uris.append(str(possible_synonym))
34+
namespaces_to_treat.remove(possible_synonym)
35+
iteration_needed = True
36+
if str(possible_synonym) in all_uris:
37+
all_prefixes.append(possible_synonym.prefix)
38+
namespaces_to_treat.remove(possible_synonym)
39+
iteration_needed = True
40+
records.append(Record(prefix, uri , [x for x in all_prefixes if not x == prefix], [x for x in all_uris if not x == uri]))
41+
return Converter(records=records)
542

643

7-
class CurieNamespace(Namespace):
8-
# We would prefer to use curies.Converter here, but there doesn't appear to be any way to build it incrementally
9-
catalog: Dict[str, "CurieNamespace"] = dict()
1044

11-
@classmethod
12-
def to_curie(cls, uri: Union[str, URIRef]) -> str:
13-
uri = str(uri)
14-
candidate_ns = ""
15-
for prefix, ns in cls.catalog.items():
16-
if uri.startswith(ns) and len(ns) > len(candidate_ns):
17-
candidate_ns = ns
18-
if candidate_ns:
19-
return candidate_ns.curie(uri[len(candidate_ns):])
20-
return None
2145

46+
def to_curie(self, uri: Union[str, URIRef]) -> str:
47+
return self.converter.compress(uri)
48+
49+
def to_uri(self, curie: str) -> Optional[URIRef]:
50+
expanded = self.converter.expand(curie)
51+
return None if expanded is None else URIRef(expanded)
52+
53+
def add_namespace(self,ns: "CurieNamespace"):
54+
self.namespaces.append(ns)
55+
self._converter = None
56+
2257
@classmethod
23-
def to_uri(cls, curie: str) -> Optional[URIRef]:
24-
prefix, localname = curie.split(':', 1)
25-
ns = CurieNamespace.catalog.get(prefix, None)
26-
return ns[localname] if ns else None
58+
def create(cls, *namespaces: List["CurieNamespace"]):
59+
cat = CurieNamespaceCatalog()
60+
[cat.add_namespace(x) for x in namespaces]
61+
return cat
62+
63+
def clear(self):
64+
self.catalog = dict()
65+
66+
def as_dict(self):
67+
return self.catalog.copy()
68+
2769

28-
def __new__(cls, prefix: str, ns: Union[str, bytes, URIRef]) -> "CurieNamespace":
70+
class CurieNamespace(Namespace):
71+
def __new__(cls, prefix: str, ns: Union[str, URIRef]):
2972
rt = Namespace.__new__(cls, str(ns) if not isinstance(ns, bytes) else ns)
3073
rt.prefix = prefix
31-
if prefix in CurieNamespace.catalog:
32-
if CurieNamespace.catalog[prefix] != str(rt):
33-
# prefix is bound to a different namespace
34-
warning(f"Prefix: {prefix} already references {CurieNamespace.catalog[prefix]} - not updated to {rt}")
35-
else:
36-
CurieNamespace.catalog[prefix] = rt
3774
return rt
3875

3976
def curie(self, reference: Optional[str] = '') -> str:
4077
return self.prefix + ':' + reference
78+
79+
def addTo(self, catalog: CurieNamespaceCatalog) -> "CurieNamespace":
80+
catalog.add_namespace(self)
81+
return self

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pyyaml = "*"
5353
rdflib = ">=6.0.0"
5454
requests = "*"
5555
prefixmaps = ">=0.1.4"
56-
curies = "^0.4.0"
56+
curies = "^0.4.3"
5757

5858
[tool.poetry.dev-dependencies]
5959
coverage = "^6.2"

tests/test_utils/input/CurieNamespace_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from linkml_runtime.utils.formatutils import camelcase, underscore, sfx
2424
from linkml_runtime.utils.enumerations import EnumDefinitionImpl
2525
from rdflib import Namespace, URIRef, DC
26-
from linkml_runtime.utils.curienamespace import CurieNamespace
26+
from linkml_runtime.utils.curienamespace import CurieNamespace, CurieNamespaceCatalog
2727
from linkml_runtime.utils.metamodelcore import Bool, Decimal, ElementIdentifier, NCName, NodeIdentifier, URI, URIorCURIE, XSDDate, XSDDateTime, XSDTime
2828

2929
metamodel_version = "1.7.0"
@@ -37,7 +37,7 @@
3737
SHEX = CurieNamespace('shex', 'http://www.w3.org/ns/shex#')
3838
XSD = CurieNamespace('xsd', 'http://www.w3.org/2001/XMLSchema#')
3939
DEFAULT_ = CurieNamespace('', 'http://example.org/')
40-
40+
namespaceCatalog = CurieNamespaceCatalog.create(LINKML, SHEX, XSD, DEFAULT_)
4141

4242
# Types
4343
class String(str):

tests/test_utils/test_curienamespace.py

+19-18
Original file line numberDiff line numberDiff line change
@@ -90,48 +90,49 @@ def test_curie_as_curie(self):
9090

9191
def test_curie_catalog(self):
9292
""" Test the CurieNamespace curie to uri and uri to curi conversions"""
93-
from tests.test_utils.input.CurieNamespace_test import Person
93+
from tests.test_utils.input.CurieNamespace_test import Person, namespaceCatalog
9494
# Make sure the import doesn't get factored out
9595
Person(id="Fred")
9696

9797
# Test bidirectional conversion
98+
CurieNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#").addTo(namespaceCatalog)
9899
self.assertEqual('rdf:chaos',
99-
CurieNamespace.to_curie('http://www.w3.org/1999/02/22-rdf-syntax-ns#chaos'))
100+
namespaceCatalog.to_curie('http://www.w3.org/1999/02/22-rdf-syntax-ns#chaos'))
100101
self.assertEqual(URIRef('http://www.w3.org/1999/02/22-rdf-syntax-ns#penguins'),
101-
CurieNamespace.to_uri('rdf:penguins'))
102+
namespaceCatalog.to_uri('rdf:penguins'))
102103

103104
# Test missing URI and CURIE
104-
self.assertIsNone(CurieNamespace.to_curie('http://nothing.org/never'))
105-
self.assertIsNone(CurieNamespace.to_uri('abcd:efgh'))
105+
self.assertIsNone(namespaceCatalog.to_curie('http://nothing.org/never'))
106+
self.assertIsNone(namespaceCatalog.to_uri('abcd:efgh'))
106107

107108
# Test the default namespace
108-
self.assertEqual('http://example.org/ttfn', str(CurieNamespace.to_uri(':ttfn')))
109-
self.assertEqual(':foul_soap', str(CurieNamespace.to_curie('http://example.org/foul_soap')))
109+
self.assertEqual('http://example.org/ttfn', str(namespaceCatalog.to_uri(':ttfn')))
110+
self.assertEqual(':foul_soap', str(namespaceCatalog.to_curie('http://example.org/foul_soap')))
110111

111112
# Make sure we pick the longest path
112-
self.assertEqual(':inst#probe', CurieNamespace.to_curie(URIRef('http://example.org/inst#probe')))
113-
CurieNamespace('long_ex', 'http://example.org/inst#')
114-
self.assertEqual('long_ex:probe', CurieNamespace.to_curie(URIRef('http://example.org/inst#probe')))
113+
self.assertEqual(':inst#probe', namespaceCatalog.to_curie(URIRef('http://example.org/inst#probe')))
114+
CurieNamespace('long_ex', 'http://example.org/inst#').addTo(namespaceCatalog)
115+
self.assertEqual('long_ex:probe', namespaceCatalog.to_curie(URIRef('http://example.org/inst#probe')))
115116

116117
# Test incremental add
117-
CurieNamespace('tester', URIRef('http://fester.bester/tester#'))
118-
self.assertEqual('tester:hip_boots', CurieNamespace.to_curie('http://fester.bester/tester#hip_boots'))
118+
CurieNamespace('tester', URIRef('http://fester.bester/tester#')).addTo(namespaceCatalog)
119+
self.assertEqual('tester:hip_boots', namespaceCatalog.to_curie('http://fester.bester/tester#hip_boots'))
119120

120121
# Test multiple prefixes for same suffix
121-
CurieNamespace('ns17', URIRef('http://fester.bester/tester#'))
122-
self.assertEqual('tester:hip_boots', CurieNamespace.to_curie('http://fester.bester/tester#hip_boots'))
123-
self.assertEqual('http://fester.bester/tester#hip_boots', str(CurieNamespace.to_uri('tester:hip_boots')))
124-
self.assertEqual('http://fester.bester/tester#hip_boots', str(CurieNamespace.to_uri('ns17:hip_boots')))
122+
CurieNamespace('ns17', URIRef('http://fester.bester/tester#')).addTo(namespaceCatalog)
123+
self.assertEqual('tester:hip_boots', namespaceCatalog.to_curie('http://fester.bester/tester#hip_boots'))
124+
self.assertEqual('http://fester.bester/tester#hip_boots', str(namespaceCatalog.to_uri('tester:hip_boots')))
125+
self.assertEqual('http://fester.bester/tester#hip_boots', str(namespaceCatalog.to_uri('ns17:hip_boots')))
125126

126127
# Test multiple uris for same prefix
127128
# The following should be benign
128-
CurieNamespace('tester', URIRef('http://fester.bester/tester#'))
129+
CurieNamespace('tester', URIRef('http://fester.bester/tester#')).addTo(namespaceCatalog)
129130

130131
# Issue warnings for now on this
131132
# TODO: test that we log the following
132133
# 'Prefix: tester already references http://fester.bester/tester# -
133134
# not updated to http://fester.notsogood/tester#'
134-
CurieNamespace('tester', URIRef('http://fester.notsogood/tester#'))
135+
CurieNamespace('tester', URIRef('http://fester.notsogood/tester#')).addTo(namespaceCatalog)
135136

136137

137138

0 commit comments

Comments
 (0)