11from __future__ import annotations
22
33from collections .abc import Iterable , Iterator , Sequence
4- from typing import Any , Callable , ClassVar , Generic , Protocol , Tuple , cast
4+ from typing import (
5+ Any ,
6+ Callable ,
7+ ClassVar ,
8+ Generic ,
9+ Protocol ,
10+ Tuple ,
11+ TypeVar ,
12+ cast ,
13+ )
514from urllib .parse import unquote , urldefrag , urljoin
615
716from attrs import evolve , field
@@ -252,16 +261,8 @@ def __getitem__(self, uri: URI) -> Resource[D]:
252261 """
253262 try :
254263 return self ._resources [uri ]
255- except LookupError :
256- try :
257- return self ._retrieve (uri )
258- except (
259- exceptions .CannotDetermineSpecification ,
260- exceptions .NoSuchResource ,
261- ):
262- raise
263- except Exception :
264- raise exceptions .Unretrievable (ref = uri )
264+ except KeyError :
265+ raise exceptions .NoSuchResource (ref = uri )
265266
266267 def __iter__ (self ) -> Iterator [URI ]:
267268 """
@@ -288,6 +289,32 @@ def __repr__(self) -> str:
288289 summary = f"{ pluralized } "
289290 return f"<Registry ({ size } { summary } )>"
290291
292+ def get_or_retrieve (self , uri : URI ):
293+ """
294+ Get a resource from the registry, crawling or retrieving if necessary.
295+ """
296+ resource = self ._resources .get (uri )
297+ if resource is not None :
298+ return Retrieved (registry = self , value = resource )
299+
300+ registry = self .crawl ()
301+ resource = registry ._resources .get (uri )
302+ if resource is not None :
303+ return Retrieved (registry = registry , value = resource )
304+
305+ try :
306+ resource = registry ._retrieve (uri )
307+ except (
308+ exceptions .CannotDetermineSpecification ,
309+ exceptions .NoSuchResource ,
310+ ):
311+ raise
312+ except Exception :
313+ raise exceptions .Unretrievable (ref = uri )
314+ else :
315+ registry = registry .with_resource (uri , resource )
316+ return Retrieved (registry = registry , value = resource )
317+
291318 def remove (self , uri : URI ):
292319 """
293320 Return a registry with the resource identified by a given URI removed.
@@ -308,7 +335,15 @@ def anchor(self, uri: URI, name: str):
308335 """
309336 Retrieve the given anchor, which must already have been found.
310337 """
311- return self ._anchors [uri , name ]
338+ value = self ._anchors .get ((uri , name ))
339+ if value is not None :
340+ return Retrieved (value = value , registry = self )
341+
342+ registry = self .crawl ()
343+ value = registry ._anchors .get ((uri , name ))
344+ if value is not None :
345+ return Retrieved (value = value , registry = registry )
346+ raise exceptions .NoSuchAnchor (ref = uri , resource = self [uri ], anchor = name )
312347
313348 def contents (self , uri : URI ) -> D :
314349 """
@@ -424,10 +459,23 @@ def resolver_with_root(self, resource: Resource[D]) -> Resolver[D]:
424459 )
425460
426461
462+ T = TypeVar ("T" , AnchorType [Any ], Resource [Any ])
463+
464+
465+ @frozen
466+ class Retrieved (Generic [D , T ]):
467+ """
468+ A value retrieved from a `Registry`.
469+ """
470+
471+ value : T
472+ registry : Registry [D ]
473+
474+
427475@frozen
428476class Resolved (Generic [D ]):
429477 """
430- A resolved reference .
478+ A reference resolved to its contents by a `Resolver` .
431479 """
432480
433481 contents : D
@@ -486,44 +534,24 @@ def lookup(self, ref: URI) -> Resolved[D]:
486534 uri , fragment = self ._base_uri , ref [1 :]
487535 else :
488536 uri , fragment = urldefrag (urljoin (self ._base_uri , ref ))
489- registry = self ._registry
490- resource = registry .get (uri )
491- if resource is None :
492- registry = registry .crawl ()
493- try :
494- resource = registry [uri ]
495- except exceptions .NoSuchResource :
496- raise exceptions .Unresolvable (ref = ref ) from None
497- except exceptions .Unretrievable :
498- raise exceptions .Unresolvable (ref = ref )
537+ try :
538+ retrieved = self ._registry .get_or_retrieve (uri )
539+ except exceptions .NoSuchResource :
540+ raise exceptions .Unresolvable (ref = ref ) from None
541+ except exceptions .Unretrievable :
542+ raise exceptions .Unresolvable (ref = ref )
499543
500544 if fragment .startswith ("/" ):
501- return resource .pointer (
502- pointer = fragment ,
503- resolver = self ._evolve (registry = registry , base_uri = uri ),
504- )
545+ resolver = self ._evolve (registry = retrieved .registry , base_uri = uri )
546+ return retrieved .value .pointer (pointer = fragment , resolver = resolver )
505547
506548 if fragment :
507- try :
508- anchor = registry .anchor (uri , fragment )
509- except LookupError :
510- registry = registry .crawl ()
511- try :
512- anchor = registry .anchor (uri , fragment )
513- except LookupError :
514- raise exceptions .NoSuchAnchor (
515- ref = ref ,
516- resource = resource ,
517- anchor = fragment ,
518- )
519- return anchor .resolve (
520- resolver = self ._evolve (registry = registry , base_uri = uri ),
521- )
549+ retrieved = retrieved .registry .anchor (uri , fragment )
550+ resolver = self ._evolve (registry = retrieved .registry , base_uri = uri )
551+ return retrieved .value .resolve (resolver = resolver )
522552
523- return Resolved (
524- contents = resource .contents ,
525- resolver = self ._evolve (registry = registry , base_uri = uri ),
526- )
553+ resolver = self ._evolve (registry = retrieved .registry , base_uri = uri )
554+ return Resolved (contents = retrieved .value .contents , resolver = resolver )
527555
528556 def in_subresource (self , subresource : Resource [D ]) -> Resolver [D ]:
529557 """
0 commit comments