Skip to content

Commit 93e5985

Browse files
committed
Add a way to dynamically retrieve resources in a Registry.
Allows for e.g. hooking up an HTTP client to retrieve resources from the network, the filesystem, a database, etc. Of course none of this happens by default.
1 parent ebc8d3c commit 93e5985

File tree

3 files changed

+63
-2
lines changed

3 files changed

+63
-2
lines changed

referencing/_core.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ def pointer(self, pointer: str, resolver: Resolver[D]) -> Resolved[D]:
174174
return Resolved(contents=contents, resolver=resolver) # type: ignore[reportUnknownArgumentType] # noqa: E501
175175

176176

177+
def _fail_to_retrieve(uri: URI):
178+
raise exceptions.NoSuchResource(ref=uri)
179+
180+
177181
@frozen
178182
class Registry(Mapping[URI, Resource[D]]):
179183
r"""
@@ -190,17 +194,32 @@ class Registry(Mapping[URI, Resource[D]]):
190194
191195
Registries are immutable, and their methods return new instances of the
192196
registry with the additional resources added to them.
197+
198+
The ``retrieve`` argument can be used to configure retrieval of resources
199+
dynamically, either over the network, from a database, or the like.
200+
Pass it a callable which will be called if any URI not present in the
201+
registry is accessed. It must either return a `Resource` or else raise an
202+
exception indicating that the resource is not retrievable.
193203
"""
194204

195205
_resources: PMap[URI, Resource[D]] = field(default=m(), converter=pmap) # type: ignore[reportUnknownArgumentType] # noqa: E501
196206
_anchors: PMap[tuple[URI, str], AnchorType[D]] = field(default=m()) # type: ignore[reportUnknownArgumentType] # noqa: E501
197207
_uncrawled: PSet[URI] = field(default=s()) # type: ignore[reportUnknownArgumentType] # noqa: E501
208+
_retrieve: Callable[[URI], Resource[D]] = field(default=_fail_to_retrieve)
198209

199210
def __getitem__(self, uri: URI) -> Resource[D]:
200211
"""
201212
Return the `Resource` identified by the given URI.
202213
"""
203-
return self._resources[uri]
214+
try:
215+
return self._resources[uri]
216+
except LookupError:
217+
try:
218+
return self._retrieve(uri)
219+
except exceptions.NoSuchResource:
220+
raise
221+
except Exception:
222+
raise exceptions.NoSuchResource(ref=uri)
204223

205224
def __iter__(self) -> Iterator[URI]:
206225
"""
@@ -393,7 +412,7 @@ def lookup(self, ref: URI) -> Resolved[D]:
393412
registry = registry.crawl()
394413
try:
395414
resource = registry[uri]
396-
except KeyError:
415+
except exceptions.NoSuchResource:
397416
raise exceptions.Unresolvable(ref=ref) from None
398417

399418
if fragment.startswith("/"):

referencing/exceptions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@
99
from referencing.typing import URI
1010

1111

12+
@frozen
13+
class NoSuchResource(KeyError):
14+
"""
15+
The given URI is not present in a registry.
16+
"""
17+
18+
ref: URI
19+
20+
1221
@frozen
1322
class CannotDetermineSpecification(Exception):
1423
"""

referencing/tests/test_core.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,39 @@ def test_opaque(self):
375375
specification=Specification.OPAQUE,
376376
)
377377

378+
def test_retrieve(self):
379+
foo = Resource.opaque({"foo": "bar"})
380+
registry = Registry(retrieve=lambda uri: foo)
381+
assert registry["urn:example"] == foo
382+
383+
def test_retrieve_error(self):
384+
def retrieve(uri):
385+
if uri == "urn:succeed":
386+
return {}
387+
raise Exception("Oh no!")
388+
389+
registry = Registry(retrieve=retrieve)
390+
assert registry["urn:succeed"] == {}
391+
with pytest.raises(exceptions.NoSuchResource):
392+
registry["urn:uhoh"]
393+
394+
def test_retrieve_already_available_resource(self):
395+
def retrieve(uri):
396+
raise Exception("Oh no!")
397+
398+
foo = Resource.opaque({"foo": "bar"})
399+
registry = Registry({"urn:example": foo})
400+
assert registry["urn:example"] == foo
401+
402+
def test_retrieve_crawlable_resource(self):
403+
def retrieve(uri):
404+
raise Exception("Oh no!")
405+
406+
child = ID_AND_CHILDREN.create_resource({"ID": "urn:child", "foo": 12})
407+
root = ID_AND_CHILDREN.create_resource({"children": [child.contents]})
408+
registry = Registry(retrieve=retrieve).with_resource("urn:root", root)
409+
assert registry.crawl()["urn:child"] == child
410+
378411

379412
class TestResolver:
380413
def test_lookup_exact_uri(self):

0 commit comments

Comments
 (0)