koichi12 commited on
Commit
68d3336
·
verified ·
1 Parent(s): 4550c95

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. .venv/lib/python3.11/site-packages/__pycache__/pynvml.cpython-311.pyc +3 -0
  3. .venv/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc +3 -0
  4. .venv/lib/python3.11/site-packages/prometheus_client/__pycache__/__init__.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/prometheus_client/__pycache__/core.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/prometheus_client/__pycache__/gc_collector.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/prometheus_client/__pycache__/metrics.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/prometheus_client/__pycache__/multiprocess.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/prometheus_client/__pycache__/process_collector.cpython-311.pyc +0 -0
  10. .venv/lib/python3.11/site-packages/prometheus_client/__pycache__/samples.cpython-311.pyc +0 -0
  11. .venv/lib/python3.11/site-packages/prometheus_client/__pycache__/utils.cpython-311.pyc +0 -0
  12. .venv/lib/python3.11/site-packages/prometheus_client/bridge/__init__.py +0 -0
  13. .venv/lib/python3.11/site-packages/prometheus_client/bridge/__pycache__/__init__.cpython-311.pyc +0 -0
  14. .venv/lib/python3.11/site-packages/prometheus_client/bridge/__pycache__/graphite.cpython-311.pyc +0 -0
  15. .venv/lib/python3.11/site-packages/prometheus_client/bridge/graphite.py +94 -0
  16. .venv/lib/python3.11/site-packages/prometheus_client/twisted/__init__.py +3 -0
  17. .venv/lib/python3.11/site-packages/prometheus_client/twisted/__pycache__/__init__.cpython-311.pyc +0 -0
  18. .venv/lib/python3.11/site-packages/prometheus_client/twisted/__pycache__/_exposition.cpython-311.pyc +0 -0
  19. .venv/lib/python3.11/site-packages/prometheus_client/twisted/_exposition.py +8 -0
  20. .venv/lib/python3.11/site-packages/referencing/__init__.py +7 -0
  21. .venv/lib/python3.11/site-packages/referencing/__pycache__/__init__.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/referencing/__pycache__/_attrs.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/referencing/__pycache__/_core.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/referencing/__pycache__/exceptions.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/referencing/__pycache__/jsonschema.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/referencing/__pycache__/retrieval.cpython-311.pyc +0 -0
  27. .venv/lib/python3.11/site-packages/referencing/__pycache__/typing.cpython-311.pyc +0 -0
  28. .venv/lib/python3.11/site-packages/referencing/_attrs.py +31 -0
  29. .venv/lib/python3.11/site-packages/referencing/_attrs.pyi +20 -0
  30. .venv/lib/python3.11/site-packages/referencing/_core.py +739 -0
  31. .venv/lib/python3.11/site-packages/referencing/exceptions.py +165 -0
  32. .venv/lib/python3.11/site-packages/referencing/jsonschema.py +642 -0
  33. .venv/lib/python3.11/site-packages/referencing/py.typed +0 -0
  34. .venv/lib/python3.11/site-packages/referencing/retrieval.py +92 -0
  35. .venv/lib/python3.11/site-packages/referencing/tests/__init__.py +0 -0
  36. .venv/lib/python3.11/site-packages/referencing/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  37. .venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_core.cpython-311.pyc +0 -0
  38. .venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_exceptions.cpython-311.pyc +0 -0
  39. .venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_jsonschema.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_referencing_suite.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_retrieval.cpython-311.pyc +0 -0
  42. .venv/lib/python3.11/site-packages/referencing/tests/test_core.py +1057 -0
  43. .venv/lib/python3.11/site-packages/referencing/tests/test_exceptions.py +34 -0
  44. .venv/lib/python3.11/site-packages/referencing/tests/test_jsonschema.py +382 -0
  45. .venv/lib/python3.11/site-packages/referencing/tests/test_referencing_suite.py +66 -0
  46. .venv/lib/python3.11/site-packages/referencing/tests/test_retrieval.py +106 -0
  47. .venv/lib/python3.11/site-packages/referencing/typing.py +61 -0
  48. .venv/lib/python3.11/site-packages/starlette/_exception_handler.py +65 -0
  49. .venv/lib/python3.11/site-packages/starlette/applications.py +249 -0
  50. .venv/lib/python3.11/site-packages/starlette/concurrency.py +62 -0
.gitattributes CHANGED
@@ -205,3 +205,5 @@ tuning-competition-baseline/.venv/lib/python3.11/site-packages/torch/_inductor/_
205
  .venv/lib/python3.11/site-packages/vllm/__pycache__/utils.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
206
  .venv/lib/python3.11/site-packages/grpc/_cython/cygrpc.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
207
  .venv/lib/python3.11/site-packages/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
 
 
 
205
  .venv/lib/python3.11/site-packages/vllm/__pycache__/utils.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
206
  .venv/lib/python3.11/site-packages/grpc/_cython/cygrpc.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
207
  .venv/lib/python3.11/site-packages/wrapt/_wrappers.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
208
+ .venv/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
209
+ .venv/lib/python3.11/site-packages/__pycache__/pynvml.cpython-311.pyc filter=lfs diff=lfs merge=lfs -text
.venv/lib/python3.11/site-packages/__pycache__/pynvml.cpython-311.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8ec8fbd54c733bec6399caf5cd7da39d61df25426c6376b9e78be8d375f12722
3
+ size 285515
.venv/lib/python3.11/site-packages/__pycache__/typing_extensions.cpython-311.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4270657b146f00b5210a7cbf963ff4b514a08a0e7303eef5ba0a9e3a6c9a5e5b
3
+ size 151467
.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (2.54 kB). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/core.cpython-311.pyc ADDED
Binary file (1.11 kB). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/gc_collector.cpython-311.pyc ADDED
Binary file (2.51 kB). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/metrics.cpython-311.pyc ADDED
Binary file (43.2 kB). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/multiprocess.cpython-311.pyc ADDED
Binary file (10.1 kB). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/process_collector.cpython-311.pyc ADDED
Binary file (6.88 kB). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/samples.cpython-311.pyc ADDED
Binary file (3.89 kB). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/__pycache__/utils.cpython-311.pyc ADDED
Binary file (1.13 kB). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/bridge/__init__.py ADDED
File without changes
.venv/lib/python3.11/site-packages/prometheus_client/bridge/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (197 Bytes). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/bridge/__pycache__/graphite.cpython-311.pyc ADDED
Binary file (5.35 kB). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/bridge/graphite.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+
3
+ import logging
4
+ import re
5
+ import socket
6
+ import threading
7
+ import time
8
+ from timeit import default_timer
9
+ from typing import Callable, Tuple
10
+
11
+ from ..registry import CollectorRegistry, REGISTRY
12
+
13
+ # Roughly, have to keep to what works as a file name.
14
+ # We also remove periods, so labels can be distinguished.
15
+
16
+ _INVALID_GRAPHITE_CHARS = re.compile(r"[^a-zA-Z0-9_-]")
17
+
18
+
19
+ def _sanitize(s):
20
+ return _INVALID_GRAPHITE_CHARS.sub('_', s)
21
+
22
+
23
+ class _RegularPush(threading.Thread):
24
+ def __init__(self, pusher, interval, prefix):
25
+ super().__init__()
26
+ self._pusher = pusher
27
+ self._interval = interval
28
+ self._prefix = prefix
29
+
30
+ def run(self):
31
+ wait_until = default_timer()
32
+ while True:
33
+ while True:
34
+ now = default_timer()
35
+ if now >= wait_until:
36
+ # May need to skip some pushes.
37
+ while wait_until < now:
38
+ wait_until += self._interval
39
+ break
40
+ # time.sleep can return early.
41
+ time.sleep(wait_until - now)
42
+ try:
43
+ self._pusher.push(prefix=self._prefix)
44
+ except OSError:
45
+ logging.exception("Push failed")
46
+
47
+
48
+ class GraphiteBridge:
49
+ def __init__(self,
50
+ address: Tuple[str, int],
51
+ registry: CollectorRegistry = REGISTRY,
52
+ timeout_seconds: float = 30,
53
+ _timer: Callable[[], float] = time.time,
54
+ tags: bool = False,
55
+ ):
56
+ self._address = address
57
+ self._registry = registry
58
+ self._tags = tags
59
+ self._timeout = timeout_seconds
60
+ self._timer = _timer
61
+
62
+ def push(self, prefix: str = '') -> None:
63
+ now = int(self._timer())
64
+ output = []
65
+
66
+ prefixstr = ''
67
+ if prefix:
68
+ prefixstr = prefix + '.'
69
+
70
+ for metric in self._registry.collect():
71
+ for s in metric.samples:
72
+ if s.labels:
73
+ if self._tags:
74
+ sep = ';'
75
+ fmt = '{0}={1}'
76
+ else:
77
+ sep = '.'
78
+ fmt = '{0}.{1}'
79
+ labelstr = sep + sep.join(
80
+ [fmt.format(
81
+ _sanitize(k), _sanitize(v))
82
+ for k, v in sorted(s.labels.items())])
83
+ else:
84
+ labelstr = ''
85
+ output.append(f'{prefixstr}{_sanitize(s.name)}{labelstr} {float(s.value)} {now}\n')
86
+
87
+ conn = socket.create_connection(self._address, self._timeout)
88
+ conn.sendall(''.join(output).encode('ascii'))
89
+ conn.close()
90
+
91
+ def start(self, interval: float = 60.0, prefix: str = '') -> None:
92
+ t = _RegularPush(self, interval, prefix)
93
+ t.daemon = True
94
+ t.start()
.venv/lib/python3.11/site-packages/prometheus_client/twisted/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from ._exposition import MetricsResource
2
+
3
+ __all__ = ['MetricsResource']
.venv/lib/python3.11/site-packages/prometheus_client/twisted/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (289 Bytes). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/twisted/__pycache__/_exposition.cpython-311.pyc ADDED
Binary file (745 Bytes). View file
 
.venv/lib/python3.11/site-packages/prometheus_client/twisted/_exposition.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from twisted.internet import reactor
2
+ from twisted.web.wsgi import WSGIResource
3
+
4
+ from .. import exposition, REGISTRY
5
+
6
+ MetricsResource = lambda registry=REGISTRY: WSGIResource(
7
+ reactor, reactor.getThreadPool(), exposition.make_wsgi_app(registry)
8
+ )
.venv/lib/python3.11/site-packages/referencing/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ Cross-specification, implementation-agnostic JSON referencing.
3
+ """
4
+
5
+ from referencing._core import Anchor, Registry, Resource, Specification
6
+
7
+ __all__ = ["Anchor", "Registry", "Resource", "Specification"]
.venv/lib/python3.11/site-packages/referencing/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (444 Bytes). View file
 
.venv/lib/python3.11/site-packages/referencing/__pycache__/_attrs.cpython-311.pyc ADDED
Binary file (1.69 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/__pycache__/_core.cpython-311.pyc ADDED
Binary file (33.1 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/__pycache__/exceptions.cpython-311.pyc ADDED
Binary file (7.76 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/__pycache__/jsonschema.cpython-311.pyc ADDED
Binary file (19.8 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/__pycache__/retrieval.cpython-311.pyc ADDED
Binary file (3.73 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/__pycache__/typing.cpython-311.pyc ADDED
Binary file (2.62 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/_attrs.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import NoReturn, TypeVar
4
+
5
+ from attrs import define as _define, frozen as _frozen
6
+
7
+ _T = TypeVar("_T")
8
+
9
+
10
+ def define(cls: type[_T]) -> type[_T]: # pragma: no cover
11
+ cls.__init_subclass__ = _do_not_subclass
12
+ return _define(cls)
13
+
14
+
15
+ def frozen(cls: type[_T]) -> type[_T]:
16
+ cls.__init_subclass__ = _do_not_subclass
17
+ return _frozen(cls)
18
+
19
+
20
+ class UnsupportedSubclassing(Exception):
21
+ def __str__(self):
22
+ return (
23
+ "Subclassing is not part of referencing's public API. "
24
+ "If no other suitable API exists for what you're trying to do, "
25
+ "feel free to file an issue asking for one."
26
+ )
27
+
28
+
29
+ @staticmethod
30
+ def _do_not_subclass() -> NoReturn: # pragma: no cover
31
+ raise UnsupportedSubclassing()
.venv/lib/python3.11/site-packages/referencing/_attrs.pyi ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Callable, TypeVar, Union
2
+
3
+ from attr import attrib, field
4
+
5
+ class UnsupportedSubclassing(Exception): ...
6
+
7
+ _T = TypeVar("_T")
8
+
9
+ def __dataclass_transform__(
10
+ *,
11
+ frozen_default: bool = False,
12
+ field_descriptors: tuple[Union[type, Callable[..., Any]], ...] = ...,
13
+ ) -> Callable[[_T], _T]: ...
14
+ @__dataclass_transform__(field_descriptors=(attrib, field))
15
+ def define(cls: type[_T]) -> type[_T]: ...
16
+ @__dataclass_transform__(
17
+ frozen_default=True,
18
+ field_descriptors=(attrib, field),
19
+ )
20
+ def frozen(cls: type[_T]) -> type[_T]: ...
.venv/lib/python3.11/site-packages/referencing/_core.py ADDED
@@ -0,0 +1,739 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable, Iterator, Sequence
4
+ from enum import Enum
5
+ from typing import Any, Callable, ClassVar, Generic, Protocol
6
+ from urllib.parse import unquote, urldefrag, urljoin
7
+
8
+ from attrs import evolve, field
9
+ from rpds import HashTrieMap, HashTrieSet, List
10
+
11
+ try:
12
+ from typing_extensions import TypeVar
13
+ except ImportError: # pragma: no cover
14
+ from typing import TypeVar
15
+
16
+ from referencing import exceptions
17
+ from referencing._attrs import frozen
18
+ from referencing.typing import URI, Anchor as AnchorType, D, Mapping, Retrieve
19
+
20
+ EMPTY_UNCRAWLED: HashTrieSet[URI] = HashTrieSet()
21
+ EMPTY_PREVIOUS_RESOLVERS: List[URI] = List()
22
+
23
+
24
+ class _Unset(Enum):
25
+ """
26
+ What sillyness...
27
+ """
28
+
29
+ SENTINEL = 1
30
+
31
+
32
+ _UNSET = _Unset.SENTINEL
33
+
34
+
35
+ class _MaybeInSubresource(Protocol[D]):
36
+ def __call__(
37
+ self,
38
+ segments: Sequence[int | str],
39
+ resolver: Resolver[D],
40
+ subresource: Resource[D],
41
+ ) -> Resolver[D]: ...
42
+
43
+
44
+ def _detect_or_error(contents: D) -> Specification[D]:
45
+ if not isinstance(contents, Mapping):
46
+ raise exceptions.CannotDetermineSpecification(contents)
47
+
48
+ jsonschema_dialect_id = contents.get("$schema") # type: ignore[reportUnknownMemberType]
49
+ if not isinstance(jsonschema_dialect_id, str):
50
+ raise exceptions.CannotDetermineSpecification(contents)
51
+
52
+ from referencing.jsonschema import specification_with
53
+
54
+ return specification_with(jsonschema_dialect_id)
55
+
56
+
57
+ def _detect_or_default(
58
+ default: Specification[D],
59
+ ) -> Callable[[D], Specification[D]]:
60
+ def _detect(contents: D) -> Specification[D]:
61
+ if not isinstance(contents, Mapping):
62
+ return default
63
+
64
+ jsonschema_dialect_id = contents.get("$schema") # type: ignore[reportUnknownMemberType]
65
+ if jsonschema_dialect_id is None:
66
+ return default
67
+
68
+ from referencing.jsonschema import specification_with
69
+
70
+ return specification_with(
71
+ jsonschema_dialect_id, # type: ignore[reportUnknownArgumentType]
72
+ default=default,
73
+ )
74
+
75
+ return _detect
76
+
77
+
78
+ class _SpecificationDetector:
79
+ def __get__(
80
+ self,
81
+ instance: Specification[D] | None,
82
+ cls: type[Specification[D]],
83
+ ) -> Callable[[D], Specification[D]]:
84
+ if instance is None:
85
+ return _detect_or_error
86
+ else:
87
+ return _detect_or_default(instance)
88
+
89
+
90
+ @frozen
91
+ class Specification(Generic[D]):
92
+ """
93
+ A specification which defines referencing behavior.
94
+
95
+ The various methods of a `Specification` allow for varying referencing
96
+ behavior across JSON Schema specification versions, etc.
97
+ """
98
+
99
+ #: A short human-readable name for the specification, used for debugging.
100
+ name: str
101
+
102
+ #: Find the ID of a given document.
103
+ id_of: Callable[[D], URI | None]
104
+
105
+ #: Retrieve the subresources of the given document (without traversing into
106
+ #: the subresources themselves).
107
+ subresources_of: Callable[[D], Iterable[D]]
108
+
109
+ #: While resolving a JSON pointer, conditionally enter a subresource
110
+ #: (if e.g. we have just entered a keyword whose value is a subresource)
111
+ maybe_in_subresource: _MaybeInSubresource[D]
112
+
113
+ #: Retrieve the anchors contained in the given document.
114
+ _anchors_in: Callable[
115
+ [Specification[D], D],
116
+ Iterable[AnchorType[D]],
117
+ ] = field(alias="anchors_in")
118
+
119
+ #: An opaque specification where resources have no subresources
120
+ #: nor internal identifiers.
121
+ OPAQUE: ClassVar[Specification[Any]]
122
+
123
+ #: Attempt to discern which specification applies to the given contents.
124
+ #:
125
+ #: May be called either as an instance method or as a class method, with
126
+ #: slightly different behavior in the following case:
127
+ #:
128
+ #: Recall that not all contents contains enough internal information about
129
+ #: which specification it is written for -- the JSON Schema ``{}``,
130
+ #: for instance, is valid under many different dialects and may be
131
+ #: interpreted as any one of them.
132
+ #:
133
+ #: When this method is used as an instance method (i.e. called on a
134
+ #: specific specification), that specification is used as the default
135
+ #: if the given contents are unidentifiable.
136
+ #:
137
+ #: On the other hand when called as a class method, an error is raised.
138
+ #:
139
+ #: To reiterate, ``DRAFT202012.detect({})`` will return ``DRAFT202012``
140
+ #: whereas the class method ``Specification.detect({})`` will raise an
141
+ #: error.
142
+ #:
143
+ #: (Note that of course ``DRAFT202012.detect(...)`` may return some other
144
+ #: specification when given a schema which *does* identify as being for
145
+ #: another version).
146
+ #:
147
+ #: Raises:
148
+ #:
149
+ #: `CannotDetermineSpecification`
150
+ #:
151
+ #: if the given contents don't have any discernible
152
+ #: information which could be used to guess which
153
+ #: specification they identify as
154
+ detect = _SpecificationDetector()
155
+
156
+ def __repr__(self) -> str:
157
+ return f"<Specification name={self.name!r}>"
158
+
159
+ def anchors_in(self, contents: D):
160
+ """
161
+ Retrieve the anchors contained in the given document.
162
+ """
163
+ return self._anchors_in(self, contents)
164
+
165
+ def create_resource(self, contents: D) -> Resource[D]:
166
+ """
167
+ Create a resource which is interpreted using this specification.
168
+ """
169
+ return Resource(contents=contents, specification=self)
170
+
171
+
172
+ Specification.OPAQUE = Specification(
173
+ name="opaque",
174
+ id_of=lambda contents: None,
175
+ subresources_of=lambda contents: [],
176
+ anchors_in=lambda specification, contents: [],
177
+ maybe_in_subresource=lambda segments, resolver, subresource: resolver,
178
+ )
179
+
180
+
181
+ @frozen
182
+ class Resource(Generic[D]):
183
+ r"""
184
+ A document (deserialized JSON) with a concrete interpretation under a spec.
185
+
186
+ In other words, a Python object, along with an instance of `Specification`
187
+ which describes how the document interacts with referencing -- both
188
+ internally (how it refers to other `Resource`\ s) and externally (how it
189
+ should be identified such that it is referenceable by other documents).
190
+ """
191
+
192
+ contents: D
193
+ _specification: Specification[D] = field(alias="specification")
194
+
195
+ @classmethod
196
+ def from_contents(
197
+ cls,
198
+ contents: D,
199
+ default_specification: (
200
+ type[Specification[D]] | Specification[D]
201
+ ) = Specification,
202
+ ) -> Resource[D]:
203
+ """
204
+ Create a resource guessing which specification applies to the contents.
205
+
206
+ Raises:
207
+
208
+ `CannotDetermineSpecification`
209
+
210
+ if the given contents don't have any discernible
211
+ information which could be used to guess which
212
+ specification they identify as
213
+
214
+ """
215
+ specification = default_specification.detect(contents)
216
+ return specification.create_resource(contents=contents)
217
+
218
+ @classmethod
219
+ def opaque(cls, contents: D) -> Resource[D]:
220
+ """
221
+ Create an opaque `Resource` -- i.e. one with opaque specification.
222
+
223
+ See `Specification.OPAQUE` for details.
224
+ """
225
+ return Specification.OPAQUE.create_resource(contents=contents)
226
+
227
+ def id(self) -> URI | None:
228
+ """
229
+ Retrieve this resource's (specification-specific) identifier.
230
+ """
231
+ id = self._specification.id_of(self.contents)
232
+ if id is None:
233
+ return
234
+ return id.rstrip("#")
235
+
236
+ def subresources(self) -> Iterable[Resource[D]]:
237
+ """
238
+ Retrieve this resource's subresources.
239
+ """
240
+ return (
241
+ Resource.from_contents(
242
+ each,
243
+ default_specification=self._specification,
244
+ )
245
+ for each in self._specification.subresources_of(self.contents)
246
+ )
247
+
248
+ def anchors(self) -> Iterable[AnchorType[D]]:
249
+ """
250
+ Retrieve this resource's (specification-specific) identifier.
251
+ """
252
+ return self._specification.anchors_in(self.contents)
253
+
254
+ def pointer(self, pointer: str, resolver: Resolver[D]) -> Resolved[D]:
255
+ """
256
+ Resolve the given JSON pointer.
257
+
258
+ Raises:
259
+
260
+ `exceptions.PointerToNowhere`
261
+
262
+ if the pointer points to a location not present in the document
263
+
264
+ """
265
+ if not pointer:
266
+ return Resolved(contents=self.contents, resolver=resolver)
267
+
268
+ contents = self.contents
269
+ segments: list[int | str] = []
270
+ for segment in unquote(pointer[1:]).split("/"):
271
+ if isinstance(contents, Sequence):
272
+ segment = int(segment)
273
+ else:
274
+ segment = segment.replace("~1", "/").replace("~0", "~")
275
+ try:
276
+ contents = contents[segment] # type: ignore[reportUnknownArgumentType]
277
+ except LookupError as lookup_error:
278
+ error = exceptions.PointerToNowhere(ref=pointer, resource=self)
279
+ raise error from lookup_error
280
+
281
+ segments.append(segment)
282
+ last = resolver
283
+ resolver = self._specification.maybe_in_subresource(
284
+ segments=segments,
285
+ resolver=resolver,
286
+ subresource=self._specification.create_resource(contents),
287
+ )
288
+ if resolver is not last:
289
+ segments = []
290
+ return Resolved(contents=contents, resolver=resolver) # type: ignore[reportUnknownArgumentType]
291
+
292
+
293
+ def _fail_to_retrieve(uri: URI):
294
+ raise exceptions.NoSuchResource(ref=uri)
295
+
296
+
297
+ @frozen
298
+ class Registry(Mapping[URI, Resource[D]]):
299
+ r"""
300
+ A registry of `Resource`\ s, each identified by their canonical URIs.
301
+
302
+ Registries store a collection of in-memory resources, and optionally
303
+ enable additional resources which may be stored elsewhere (e.g. in a
304
+ database, a separate set of files, over the network, etc.).
305
+
306
+ They also lazily walk their known resources, looking for subresources
307
+ within them. In other words, subresources contained within any added
308
+ resources will be retrievable via their own IDs (though this discovery of
309
+ subresources will be delayed until necessary).
310
+
311
+ Registries are immutable, and their methods return new instances of the
312
+ registry with the additional resources added to them.
313
+
314
+ The ``retrieve`` argument can be used to configure retrieval of resources
315
+ dynamically, either over the network, from a database, or the like.
316
+ Pass it a callable which will be called if any URI not present in the
317
+ registry is accessed. It must either return a `Resource` or else raise a
318
+ `NoSuchResource` exception indicating that the resource does not exist
319
+ even according to the retrieval logic.
320
+ """
321
+
322
+ _resources: HashTrieMap[URI, Resource[D]] = field(
323
+ default=HashTrieMap(),
324
+ converter=HashTrieMap.convert, # type: ignore[reportGeneralTypeIssues]
325
+ alias="resources",
326
+ )
327
+ _anchors: HashTrieMap[tuple[URI, str], AnchorType[D]] = HashTrieMap()
328
+ _uncrawled: HashTrieSet[URI] = EMPTY_UNCRAWLED
329
+ _retrieve: Retrieve[D] = field(default=_fail_to_retrieve, alias="retrieve")
330
+
331
+ def __getitem__(self, uri: URI) -> Resource[D]:
332
+ """
333
+ Return the (already crawled) `Resource` identified by the given URI.
334
+ """
335
+ try:
336
+ return self._resources[uri.rstrip("#")]
337
+ except KeyError:
338
+ raise exceptions.NoSuchResource(ref=uri) from None
339
+
340
+ def __iter__(self) -> Iterator[URI]:
341
+ """
342
+ Iterate over all crawled URIs in the registry.
343
+ """
344
+ return iter(self._resources)
345
+
346
+ def __len__(self) -> int:
347
+ """
348
+ Count the total number of fully crawled resources in this registry.
349
+ """
350
+ return len(self._resources)
351
+
352
+ def __rmatmul__(
353
+ self,
354
+ new: Resource[D] | Iterable[Resource[D]],
355
+ ) -> Registry[D]:
356
+ """
357
+ Create a new registry with resource(s) added using their internal IDs.
358
+
359
+ Resources must have a internal IDs (e.g. the :kw:`$id` keyword in
360
+ modern JSON Schema versions), otherwise an error will be raised.
361
+
362
+ Both a single resource as well as an iterable of resources works, i.e.:
363
+
364
+ * ``resource @ registry`` or
365
+
366
+ * ``[iterable, of, multiple, resources] @ registry``
367
+
368
+ which -- again, assuming the resources have internal IDs -- is
369
+ equivalent to calling `Registry.with_resources` as such:
370
+
371
+ .. code:: python
372
+
373
+ registry.with_resources(
374
+ (resource.id(), resource) for resource in new_resources
375
+ )
376
+
377
+ Raises:
378
+
379
+ `NoInternalID`
380
+
381
+ if the resource(s) in fact do not have IDs
382
+
383
+ """
384
+ if isinstance(new, Resource):
385
+ new = (new,)
386
+
387
+ resources = self._resources
388
+ uncrawled = self._uncrawled
389
+ for resource in new:
390
+ id = resource.id()
391
+ if id is None:
392
+ raise exceptions.NoInternalID(resource=resource)
393
+ uncrawled = uncrawled.insert(id)
394
+ resources = resources.insert(id, resource)
395
+ return evolve(self, resources=resources, uncrawled=uncrawled)
396
+
397
+ def __repr__(self) -> str:
398
+ size = len(self)
399
+ pluralized = "resource" if size == 1 else "resources"
400
+ if self._uncrawled:
401
+ uncrawled = len(self._uncrawled)
402
+ if uncrawled == size:
403
+ summary = f"uncrawled {pluralized}"
404
+ else:
405
+ summary = f"{pluralized}, {uncrawled} uncrawled"
406
+ else:
407
+ summary = f"{pluralized}"
408
+ return f"<Registry ({size} {summary})>"
409
+
410
+ def get_or_retrieve(self, uri: URI) -> Retrieved[D, Resource[D]]:
411
+ """
412
+ Get a resource from the registry, crawling or retrieving if necessary.
413
+
414
+ May involve crawling to find the given URI if it is not already known,
415
+ so the returned object is a `Retrieved` object which contains both the
416
+ resource value as well as the registry which ultimately contained it.
417
+ """
418
+ resource = self._resources.get(uri)
419
+ if resource is not None:
420
+ return Retrieved(registry=self, value=resource)
421
+
422
+ registry = self.crawl()
423
+ resource = registry._resources.get(uri)
424
+ if resource is not None:
425
+ return Retrieved(registry=registry, value=resource)
426
+
427
+ try:
428
+ resource = registry._retrieve(uri)
429
+ except (
430
+ exceptions.CannotDetermineSpecification,
431
+ exceptions.NoSuchResource,
432
+ ):
433
+ raise
434
+ except Exception as error:
435
+ raise exceptions.Unretrievable(ref=uri) from error
436
+ else:
437
+ registry = registry.with_resource(uri, resource)
438
+ return Retrieved(registry=registry, value=resource)
439
+
440
+ def remove(self, uri: URI):
441
+ """
442
+ Return a registry with the resource identified by a given URI removed.
443
+ """
444
+ if uri not in self._resources:
445
+ raise exceptions.NoSuchResource(ref=uri)
446
+
447
+ return evolve(
448
+ self,
449
+ resources=self._resources.remove(uri),
450
+ uncrawled=self._uncrawled.discard(uri),
451
+ anchors=HashTrieMap(
452
+ (k, v) for k, v in self._anchors.items() if k[0] != uri
453
+ ),
454
+ )
455
+
456
+ def anchor(self, uri: URI, name: str):
457
+ """
458
+ Retrieve a given anchor from a resource which must already be crawled.
459
+ """
460
+ value = self._anchors.get((uri, name))
461
+ if value is not None:
462
+ return Retrieved(value=value, registry=self)
463
+
464
+ registry = self.crawl()
465
+ value = registry._anchors.get((uri, name))
466
+ if value is not None:
467
+ return Retrieved(value=value, registry=registry)
468
+
469
+ resource = self[uri]
470
+ canonical_uri = resource.id()
471
+ if canonical_uri is not None:
472
+ value = registry._anchors.get((canonical_uri, name))
473
+ if value is not None:
474
+ return Retrieved(value=value, registry=registry)
475
+
476
+ if "/" in name:
477
+ raise exceptions.InvalidAnchor(
478
+ ref=uri,
479
+ resource=resource,
480
+ anchor=name,
481
+ )
482
+ raise exceptions.NoSuchAnchor(ref=uri, resource=resource, anchor=name)
483
+
484
+ def contents(self, uri: URI) -> D:
485
+ """
486
+ Retrieve the (already crawled) contents identified by the given URI.
487
+ """
488
+ return self[uri].contents
489
+
490
+ def crawl(self) -> Registry[D]:
491
+ """
492
+ Crawl all added resources, discovering subresources.
493
+ """
494
+ resources = self._resources
495
+ anchors = self._anchors
496
+ uncrawled = [(uri, resources[uri]) for uri in self._uncrawled]
497
+ while uncrawled:
498
+ uri, resource = uncrawled.pop()
499
+
500
+ id = resource.id()
501
+ if id is not None:
502
+ uri = urljoin(uri, id)
503
+ resources = resources.insert(uri, resource)
504
+ for each in resource.anchors():
505
+ anchors = anchors.insert((uri, each.name), each)
506
+ uncrawled.extend((uri, each) for each in resource.subresources())
507
+ return evolve(
508
+ self,
509
+ resources=resources,
510
+ anchors=anchors,
511
+ uncrawled=EMPTY_UNCRAWLED,
512
+ )
513
+
514
+ def with_resource(self, uri: URI, resource: Resource[D]):
515
+ """
516
+ Add the given `Resource` to the registry, without crawling it.
517
+ """
518
+ return self.with_resources([(uri, resource)])
519
+
520
+ def with_resources(
521
+ self,
522
+ pairs: Iterable[tuple[URI, Resource[D]]],
523
+ ) -> Registry[D]:
524
+ r"""
525
+ Add the given `Resource`\ s to the registry, without crawling them.
526
+ """
527
+ resources = self._resources
528
+ uncrawled = self._uncrawled
529
+ for uri, resource in pairs:
530
+ # Empty fragment URIs are equivalent to URIs without the fragment.
531
+ # TODO: Is this true for non JSON Schema resources? Probably not.
532
+ uri = uri.rstrip("#")
533
+ uncrawled = uncrawled.insert(uri)
534
+ resources = resources.insert(uri, resource)
535
+ return evolve(self, resources=resources, uncrawled=uncrawled)
536
+
537
+ def with_contents(
538
+ self,
539
+ pairs: Iterable[tuple[URI, D]],
540
+ **kwargs: Any,
541
+ ) -> Registry[D]:
542
+ r"""
543
+ Add the given contents to the registry, autodetecting when necessary.
544
+ """
545
+ return self.with_resources(
546
+ (uri, Resource.from_contents(each, **kwargs))
547
+ for uri, each in pairs
548
+ )
549
+
550
+ def combine(self, *registries: Registry[D]) -> Registry[D]:
551
+ """
552
+ Combine together one or more other registries, producing a unified one.
553
+ """
554
+ if registries == (self,):
555
+ return self
556
+ resources = self._resources
557
+ anchors = self._anchors
558
+ uncrawled = self._uncrawled
559
+ retrieve = self._retrieve
560
+ for registry in registries:
561
+ resources = resources.update(registry._resources)
562
+ anchors = anchors.update(registry._anchors)
563
+ uncrawled = uncrawled.update(registry._uncrawled)
564
+
565
+ if registry._retrieve is not _fail_to_retrieve: # type: ignore[reportUnnecessaryComparison] ???
566
+ if registry._retrieve is not retrieve is not _fail_to_retrieve: # type: ignore[reportUnnecessaryComparison] ???
567
+ raise ValueError( # noqa: TRY003
568
+ "Cannot combine registries with conflicting retrieval "
569
+ "functions.",
570
+ )
571
+ retrieve = registry._retrieve
572
+ return evolve(
573
+ self,
574
+ anchors=anchors,
575
+ resources=resources,
576
+ uncrawled=uncrawled,
577
+ retrieve=retrieve,
578
+ )
579
+
580
+ def resolver(self, base_uri: URI = "") -> Resolver[D]:
581
+ """
582
+ Return a `Resolver` which resolves references against this registry.
583
+ """
584
+ return Resolver(base_uri=base_uri, registry=self)
585
+
586
+ def resolver_with_root(self, resource: Resource[D]) -> Resolver[D]:
587
+ """
588
+ Return a `Resolver` with a specific root resource.
589
+ """
590
+ uri = resource.id() or ""
591
+ return Resolver(
592
+ base_uri=uri,
593
+ registry=self.with_resource(uri, resource),
594
+ )
595
+
596
+
597
+ #: An anchor or resource.
598
+ AnchorOrResource = TypeVar(
599
+ "AnchorOrResource",
600
+ AnchorType[Any],
601
+ Resource[Any],
602
+ default=Resource[Any],
603
+ )
604
+
605
+
606
+ @frozen
607
+ class Retrieved(Generic[D, AnchorOrResource]):
608
+ """
609
+ A value retrieved from a `Registry`.
610
+ """
611
+
612
+ value: AnchorOrResource
613
+ registry: Registry[D]
614
+
615
+
616
+ @frozen
617
+ class Resolved(Generic[D]):
618
+ """
619
+ A reference resolved to its contents by a `Resolver`.
620
+ """
621
+
622
+ contents: D
623
+ resolver: Resolver[D]
624
+
625
+
626
+ @frozen
627
+ class Resolver(Generic[D]):
628
+ """
629
+ A reference resolver.
630
+
631
+ Resolvers help resolve references (including relative ones) by
632
+ pairing a fixed base URI with a `Registry`.
633
+
634
+ This object, under normal circumstances, is expected to be used by
635
+ *implementers of libraries* built on top of `referencing` (e.g. JSON Schema
636
+ implementations or other libraries resolving JSON references),
637
+ not directly by end-users populating registries or while writing
638
+ schemas or other resources.
639
+
640
+ References are resolved against the base URI, and the combined URI
641
+ is then looked up within the registry.
642
+
643
+ The process of resolving a reference may itself involve calculating
644
+ a *new* base URI for future reference resolution (e.g. if an
645
+ intermediate resource sets a new base URI), or may involve encountering
646
+ additional subresources and adding them to a new registry.
647
+ """
648
+
649
+ _base_uri: URI = field(alias="base_uri")
650
+ _registry: Registry[D] = field(alias="registry")
651
+ _previous: List[URI] = field(default=List(), repr=False, alias="previous")
652
+
653
+ def lookup(self, ref: URI) -> Resolved[D]:
654
+ """
655
+ Resolve the given reference to the resource it points to.
656
+
657
+ Raises:
658
+
659
+ `exceptions.Unresolvable`
660
+
661
+ or a subclass thereof (see below) if the reference isn't
662
+ resolvable
663
+
664
+ `exceptions.NoSuchAnchor`
665
+
666
+ if the reference is to a URI where a resource exists but
667
+ contains a plain name fragment which does not exist within
668
+ the resource
669
+
670
+ `exceptions.PointerToNowhere`
671
+
672
+ if the reference is to a URI where a resource exists but
673
+ contains a JSON pointer to a location within the resource
674
+ that does not exist
675
+
676
+ """
677
+ if ref.startswith("#"):
678
+ uri, fragment = self._base_uri, ref[1:]
679
+ else:
680
+ uri, fragment = urldefrag(urljoin(self._base_uri, ref))
681
+ try:
682
+ retrieved = self._registry.get_or_retrieve(uri)
683
+ except exceptions.NoSuchResource:
684
+ raise exceptions.Unresolvable(ref=ref) from None
685
+ except exceptions.Unretrievable as error:
686
+ raise exceptions.Unresolvable(ref=ref) from error
687
+
688
+ if fragment.startswith("/"):
689
+ resolver = self._evolve(registry=retrieved.registry, base_uri=uri)
690
+ return retrieved.value.pointer(pointer=fragment, resolver=resolver)
691
+
692
+ if fragment:
693
+ retrieved = retrieved.registry.anchor(uri, fragment)
694
+ resolver = self._evolve(registry=retrieved.registry, base_uri=uri)
695
+ return retrieved.value.resolve(resolver=resolver)
696
+
697
+ resolver = self._evolve(registry=retrieved.registry, base_uri=uri)
698
+ return Resolved(contents=retrieved.value.contents, resolver=resolver)
699
+
700
+ def in_subresource(self, subresource: Resource[D]) -> Resolver[D]:
701
+ """
702
+ Create a resolver for a subresource (which may have a new base URI).
703
+ """
704
+ id = subresource.id()
705
+ if id is None:
706
+ return self
707
+ return evolve(self, base_uri=urljoin(self._base_uri, id))
708
+
709
+ def dynamic_scope(self) -> Iterable[tuple[URI, Registry[D]]]:
710
+ """
711
+ In specs with such a notion, return the URIs in the dynamic scope.
712
+ """
713
+ for uri in self._previous:
714
+ yield uri, self._registry
715
+
716
+ def _evolve(self, base_uri: URI, **kwargs: Any):
717
+ """
718
+ Evolve, appending to the dynamic scope.
719
+ """
720
+ previous = self._previous
721
+ if self._base_uri and (not previous or base_uri != self._base_uri):
722
+ previous = previous.push_front(self._base_uri)
723
+ return evolve(self, base_uri=base_uri, previous=previous, **kwargs)
724
+
725
+
726
+ @frozen
727
+ class Anchor(Generic[D]):
728
+ """
729
+ A simple anchor in a `Resource`.
730
+ """
731
+
732
+ name: str
733
+ resource: Resource[D]
734
+
735
+ def resolve(self, resolver: Resolver[D]):
736
+ """
737
+ Return the resource for this anchor.
738
+ """
739
+ return Resolved(contents=self.resource.contents, resolver=resolver)
.venv/lib/python3.11/site-packages/referencing/exceptions.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Errors, oh no!
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ import attrs
10
+
11
+ from referencing._attrs import frozen
12
+
13
+ if TYPE_CHECKING:
14
+ from referencing import Resource
15
+ from referencing.typing import URI
16
+
17
+
18
+ @frozen
19
+ class NoSuchResource(KeyError):
20
+ """
21
+ The given URI is not present in a registry.
22
+
23
+ Unlike most exceptions, this class *is* intended to be publicly
24
+ instantiable and *is* part of the public API of the package.
25
+ """
26
+
27
+ ref: URI
28
+
29
+ def __eq__(self, other: object) -> bool:
30
+ if self.__class__ is not other.__class__:
31
+ return NotImplemented
32
+ return attrs.astuple(self) == attrs.astuple(other)
33
+
34
+ def __hash__(self) -> int:
35
+ return hash(attrs.astuple(self))
36
+
37
+
38
+ @frozen
39
+ class NoInternalID(Exception):
40
+ """
41
+ A resource has no internal ID, but one is needed.
42
+
43
+ E.g. in modern JSON Schema drafts, this is the :kw:`$id` keyword.
44
+
45
+ One might be needed if a resource was to-be added to a registry but no
46
+ other URI is available, and the resource doesn't declare its canonical URI.
47
+ """
48
+
49
+ resource: Resource[Any]
50
+
51
+ def __eq__(self, other: object) -> bool:
52
+ if self.__class__ is not other.__class__:
53
+ return NotImplemented
54
+ return attrs.astuple(self) == attrs.astuple(other)
55
+
56
+ def __hash__(self) -> int:
57
+ return hash(attrs.astuple(self))
58
+
59
+
60
+ @frozen
61
+ class Unretrievable(KeyError):
62
+ """
63
+ The given URI is not present in a registry, and retrieving it failed.
64
+ """
65
+
66
+ ref: URI
67
+
68
+ def __eq__(self, other: object) -> bool:
69
+ if self.__class__ is not other.__class__:
70
+ return NotImplemented
71
+ return attrs.astuple(self) == attrs.astuple(other)
72
+
73
+ def __hash__(self) -> int:
74
+ return hash(attrs.astuple(self))
75
+
76
+
77
+ @frozen
78
+ class CannotDetermineSpecification(Exception):
79
+ """
80
+ Attempting to detect the appropriate `Specification` failed.
81
+
82
+ This happens if no discernible information is found in the contents of the
83
+ new resource which would help identify it.
84
+ """
85
+
86
+ contents: Any
87
+
88
+ def __eq__(self, other: object) -> bool:
89
+ if self.__class__ is not other.__class__:
90
+ return NotImplemented
91
+ return attrs.astuple(self) == attrs.astuple(other)
92
+
93
+ def __hash__(self) -> int:
94
+ return hash(attrs.astuple(self))
95
+
96
+
97
+ @attrs.frozen # Because here we allow subclassing below.
98
+ class Unresolvable(Exception):
99
+ """
100
+ A reference was unresolvable.
101
+ """
102
+
103
+ ref: URI
104
+
105
+ def __eq__(self, other: object) -> bool:
106
+ if self.__class__ is not other.__class__:
107
+ return NotImplemented
108
+ return attrs.astuple(self) == attrs.astuple(other)
109
+
110
+ def __hash__(self) -> int:
111
+ return hash(attrs.astuple(self))
112
+
113
+
114
+ @frozen
115
+ class PointerToNowhere(Unresolvable):
116
+ """
117
+ A JSON Pointer leads to a part of a document that does not exist.
118
+ """
119
+
120
+ resource: Resource[Any]
121
+
122
+ def __str__(self) -> str:
123
+ msg = f"{self.ref!r} does not exist within {self.resource.contents!r}"
124
+ if self.ref == "/":
125
+ msg += (
126
+ ". The pointer '/' is a valid JSON Pointer but it points to "
127
+ "an empty string property ''. If you intended to point "
128
+ "to the entire resource, you should use '#'."
129
+ )
130
+ return msg
131
+
132
+
133
+ @frozen
134
+ class NoSuchAnchor(Unresolvable):
135
+ """
136
+ An anchor does not exist within a particular resource.
137
+ """
138
+
139
+ resource: Resource[Any]
140
+ anchor: str
141
+
142
+ def __str__(self) -> str:
143
+ return (
144
+ f"{self.anchor!r} does not exist within {self.resource.contents!r}"
145
+ )
146
+
147
+
148
+ @frozen
149
+ class InvalidAnchor(Unresolvable):
150
+ """
151
+ An anchor which could never exist in a resource was dereferenced.
152
+
153
+ It is somehow syntactically invalid.
154
+ """
155
+
156
+ resource: Resource[Any]
157
+ anchor: str
158
+
159
+ def __str__(self) -> str:
160
+ return (
161
+ f"'#{self.anchor}' is not a valid anchor, neither as a "
162
+ "plain name anchor nor as a JSON Pointer. You may have intended "
163
+ f"to use '#/{self.anchor}', as the slash is required *before each "
164
+ "segment* of a JSON pointer."
165
+ )
.venv/lib/python3.11/site-packages/referencing/jsonschema.py ADDED
@@ -0,0 +1,642 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Referencing implementations for JSON Schema specs (historic & current).
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from collections.abc import Iterable, Sequence, Set
8
+ from typing import Any, Union
9
+
10
+ from referencing import Anchor, Registry, Resource, Specification, exceptions
11
+ from referencing._attrs import frozen
12
+ from referencing._core import (
13
+ _UNSET, # type: ignore[reportPrivateUsage]
14
+ Resolved as _Resolved,
15
+ Resolver as _Resolver,
16
+ _Unset, # type: ignore[reportPrivateUsage]
17
+ )
18
+ from referencing.typing import URI, Anchor as AnchorType, Mapping
19
+
20
+ #: A JSON Schema which is a JSON object
21
+ ObjectSchema = Mapping[str, Any]
22
+
23
+ #: A JSON Schema of any kind
24
+ Schema = Union[bool, ObjectSchema]
25
+
26
+ #: A Resource whose contents are JSON Schemas
27
+ SchemaResource = Resource[Schema]
28
+
29
+ #: A JSON Schema Registry
30
+ SchemaRegistry = Registry[Schema]
31
+
32
+ #: The empty JSON Schema Registry
33
+ EMPTY_REGISTRY: SchemaRegistry = Registry()
34
+
35
+
36
+ @frozen
37
+ class UnknownDialect(Exception):
38
+ """
39
+ A dialect identifier was found for a dialect unknown by this library.
40
+
41
+ If it's a custom ("unofficial") dialect, be sure you've registered it.
42
+ """
43
+
44
+ uri: URI
45
+
46
+
47
+ def _dollar_id(contents: Schema) -> URI | None:
48
+ if isinstance(contents, bool):
49
+ return
50
+ return contents.get("$id")
51
+
52
+
53
+ def _legacy_dollar_id(contents: Schema) -> URI | None:
54
+ if isinstance(contents, bool) or "$ref" in contents:
55
+ return
56
+ id = contents.get("$id")
57
+ if id is not None and not id.startswith("#"):
58
+ return id
59
+
60
+
61
+ def _legacy_id(contents: ObjectSchema) -> URI | None:
62
+ if "$ref" in contents:
63
+ return
64
+ id = contents.get("id")
65
+ if id is not None and not id.startswith("#"):
66
+ return id
67
+
68
+
69
+ def _anchor(
70
+ specification: Specification[Schema],
71
+ contents: Schema,
72
+ ) -> Iterable[AnchorType[Schema]]:
73
+ if isinstance(contents, bool):
74
+ return
75
+ anchor = contents.get("$anchor")
76
+ if anchor is not None:
77
+ yield Anchor(
78
+ name=anchor,
79
+ resource=specification.create_resource(contents),
80
+ )
81
+
82
+ dynamic_anchor = contents.get("$dynamicAnchor")
83
+ if dynamic_anchor is not None:
84
+ yield DynamicAnchor(
85
+ name=dynamic_anchor,
86
+ resource=specification.create_resource(contents),
87
+ )
88
+
89
+
90
+ def _anchor_2019(
91
+ specification: Specification[Schema],
92
+ contents: Schema,
93
+ ) -> Iterable[Anchor[Schema]]:
94
+ if isinstance(contents, bool):
95
+ return []
96
+ anchor = contents.get("$anchor")
97
+ if anchor is None:
98
+ return []
99
+ return [
100
+ Anchor(
101
+ name=anchor,
102
+ resource=specification.create_resource(contents),
103
+ ),
104
+ ]
105
+
106
+
107
+ def _legacy_anchor_in_dollar_id(
108
+ specification: Specification[Schema],
109
+ contents: Schema,
110
+ ) -> Iterable[Anchor[Schema]]:
111
+ if isinstance(contents, bool):
112
+ return []
113
+ id = contents.get("$id", "")
114
+ if not id.startswith("#"):
115
+ return []
116
+ return [
117
+ Anchor(
118
+ name=id[1:],
119
+ resource=specification.create_resource(contents),
120
+ ),
121
+ ]
122
+
123
+
124
+ def _legacy_anchor_in_id(
125
+ specification: Specification[ObjectSchema],
126
+ contents: ObjectSchema,
127
+ ) -> Iterable[Anchor[ObjectSchema]]:
128
+ id = contents.get("id", "")
129
+ if not id.startswith("#"):
130
+ return []
131
+ return [
132
+ Anchor(
133
+ name=id[1:],
134
+ resource=specification.create_resource(contents),
135
+ ),
136
+ ]
137
+
138
+
139
+ def _subresources_of(
140
+ in_value: Set[str] = frozenset(),
141
+ in_subvalues: Set[str] = frozenset(),
142
+ in_subarray: Set[str] = frozenset(),
143
+ ):
144
+ """
145
+ Create a callable returning JSON Schema specification-style subschemas.
146
+
147
+ Relies on specifying the set of keywords containing subschemas in their
148
+ values, in a subobject's values, or in a subarray.
149
+ """
150
+
151
+ def subresources_of(contents: Schema) -> Iterable[ObjectSchema]:
152
+ if isinstance(contents, bool):
153
+ return
154
+ for each in in_value:
155
+ if each in contents:
156
+ yield contents[each]
157
+ for each in in_subarray:
158
+ if each in contents:
159
+ yield from contents[each]
160
+ for each in in_subvalues:
161
+ if each in contents:
162
+ yield from contents[each].values()
163
+
164
+ return subresources_of
165
+
166
+
167
+ def _subresources_of_with_crazy_items(
168
+ in_value: Set[str] = frozenset(),
169
+ in_subvalues: Set[str] = frozenset(),
170
+ in_subarray: Set[str] = frozenset(),
171
+ ):
172
+ """
173
+ Specifically handle older drafts where there are some funky keywords.
174
+ """
175
+
176
+ def subresources_of(contents: Schema) -> Iterable[ObjectSchema]:
177
+ if isinstance(contents, bool):
178
+ return
179
+ for each in in_value:
180
+ if each in contents:
181
+ yield contents[each]
182
+ for each in in_subarray:
183
+ if each in contents:
184
+ yield from contents[each]
185
+ for each in in_subvalues:
186
+ if each in contents:
187
+ yield from contents[each].values()
188
+
189
+ items = contents.get("items")
190
+ if items is not None:
191
+ if isinstance(items, Sequence):
192
+ yield from items
193
+ else:
194
+ yield items
195
+
196
+ return subresources_of
197
+
198
+
199
+ def _subresources_of_with_crazy_items_dependencies(
200
+ in_value: Set[str] = frozenset(),
201
+ in_subvalues: Set[str] = frozenset(),
202
+ in_subarray: Set[str] = frozenset(),
203
+ ):
204
+ """
205
+ Specifically handle older drafts where there are some funky keywords.
206
+ """
207
+
208
+ def subresources_of(contents: Schema) -> Iterable[ObjectSchema]:
209
+ if isinstance(contents, bool):
210
+ return
211
+ for each in in_value:
212
+ if each in contents:
213
+ yield contents[each]
214
+ for each in in_subarray:
215
+ if each in contents:
216
+ yield from contents[each]
217
+ for each in in_subvalues:
218
+ if each in contents:
219
+ yield from contents[each].values()
220
+
221
+ items = contents.get("items")
222
+ if items is not None:
223
+ if isinstance(items, Sequence):
224
+ yield from items
225
+ else:
226
+ yield items
227
+ dependencies = contents.get("dependencies")
228
+ if dependencies is not None:
229
+ values = iter(dependencies.values())
230
+ value = next(values, None)
231
+ if isinstance(value, Mapping):
232
+ yield value
233
+ yield from values
234
+
235
+ return subresources_of
236
+
237
+
238
+ def _subresources_of_with_crazy_aP_items_dependencies(
239
+ in_value: Set[str] = frozenset(),
240
+ in_subvalues: Set[str] = frozenset(),
241
+ in_subarray: Set[str] = frozenset(),
242
+ ):
243
+ """
244
+ Specifically handle even older drafts where there are some funky keywords.
245
+ """
246
+
247
+ def subresources_of(contents: ObjectSchema) -> Iterable[ObjectSchema]:
248
+ for each in in_value:
249
+ if each in contents:
250
+ yield contents[each]
251
+ for each in in_subarray:
252
+ if each in contents:
253
+ yield from contents[each]
254
+ for each in in_subvalues:
255
+ if each in contents:
256
+ yield from contents[each].values()
257
+
258
+ items = contents.get("items")
259
+ if items is not None:
260
+ if isinstance(items, Sequence):
261
+ yield from items
262
+ else:
263
+ yield items
264
+ dependencies = contents.get("dependencies")
265
+ if dependencies is not None:
266
+ values = iter(dependencies.values())
267
+ value = next(values, None)
268
+ if isinstance(value, Mapping):
269
+ yield value
270
+ yield from values
271
+
272
+ for each in "additionalItems", "additionalProperties":
273
+ value = contents.get(each)
274
+ if isinstance(value, Mapping):
275
+ yield value
276
+
277
+ return subresources_of
278
+
279
+
280
+ def _maybe_in_subresource(
281
+ in_value: Set[str] = frozenset(),
282
+ in_subvalues: Set[str] = frozenset(),
283
+ in_subarray: Set[str] = frozenset(),
284
+ ):
285
+ in_child = in_subvalues | in_subarray
286
+
287
+ def maybe_in_subresource(
288
+ segments: Sequence[int | str],
289
+ resolver: _Resolver[Any],
290
+ subresource: Resource[Any],
291
+ ) -> _Resolver[Any]:
292
+ _segments = iter(segments)
293
+ for segment in _segments:
294
+ if segment not in in_value and (
295
+ segment not in in_child or next(_segments, None) is None
296
+ ):
297
+ return resolver
298
+ return resolver.in_subresource(subresource)
299
+
300
+ return maybe_in_subresource
301
+
302
+
303
+ def _maybe_in_subresource_crazy_items(
304
+ in_value: Set[str] = frozenset(),
305
+ in_subvalues: Set[str] = frozenset(),
306
+ in_subarray: Set[str] = frozenset(),
307
+ ):
308
+ in_child = in_subvalues | in_subarray
309
+
310
+ def maybe_in_subresource(
311
+ segments: Sequence[int | str],
312
+ resolver: _Resolver[Any],
313
+ subresource: Resource[Any],
314
+ ) -> _Resolver[Any]:
315
+ _segments = iter(segments)
316
+ for segment in _segments:
317
+ if segment == "items" and isinstance(
318
+ subresource.contents,
319
+ Mapping,
320
+ ):
321
+ return resolver.in_subresource(subresource)
322
+ if segment not in in_value and (
323
+ segment not in in_child or next(_segments, None) is None
324
+ ):
325
+ return resolver
326
+ return resolver.in_subresource(subresource)
327
+
328
+ return maybe_in_subresource
329
+
330
+
331
+ def _maybe_in_subresource_crazy_items_dependencies(
332
+ in_value: Set[str] = frozenset(),
333
+ in_subvalues: Set[str] = frozenset(),
334
+ in_subarray: Set[str] = frozenset(),
335
+ ):
336
+ in_child = in_subvalues | in_subarray
337
+
338
+ def maybe_in_subresource(
339
+ segments: Sequence[int | str],
340
+ resolver: _Resolver[Any],
341
+ subresource: Resource[Any],
342
+ ) -> _Resolver[Any]:
343
+ _segments = iter(segments)
344
+ for segment in _segments:
345
+ if segment in {"items", "dependencies"} and isinstance(
346
+ subresource.contents,
347
+ Mapping,
348
+ ):
349
+ return resolver.in_subresource(subresource)
350
+ if segment not in in_value and (
351
+ segment not in in_child or next(_segments, None) is None
352
+ ):
353
+ return resolver
354
+ return resolver.in_subresource(subresource)
355
+
356
+ return maybe_in_subresource
357
+
358
+
359
+ #: JSON Schema draft 2020-12
360
+ DRAFT202012 = Specification(
361
+ name="draft2020-12",
362
+ id_of=_dollar_id,
363
+ subresources_of=_subresources_of(
364
+ in_value={
365
+ "additionalProperties",
366
+ "contains",
367
+ "contentSchema",
368
+ "else",
369
+ "if",
370
+ "items",
371
+ "not",
372
+ "propertyNames",
373
+ "then",
374
+ "unevaluatedItems",
375
+ "unevaluatedProperties",
376
+ },
377
+ in_subarray={"allOf", "anyOf", "oneOf", "prefixItems"},
378
+ in_subvalues={
379
+ "$defs",
380
+ "definitions",
381
+ "dependentSchemas",
382
+ "patternProperties",
383
+ "properties",
384
+ },
385
+ ),
386
+ anchors_in=_anchor,
387
+ maybe_in_subresource=_maybe_in_subresource(
388
+ in_value={
389
+ "additionalProperties",
390
+ "contains",
391
+ "contentSchema",
392
+ "else",
393
+ "if",
394
+ "items",
395
+ "not",
396
+ "propertyNames",
397
+ "then",
398
+ "unevaluatedItems",
399
+ "unevaluatedProperties",
400
+ },
401
+ in_subarray={"allOf", "anyOf", "oneOf", "prefixItems"},
402
+ in_subvalues={
403
+ "$defs",
404
+ "definitions",
405
+ "dependentSchemas",
406
+ "patternProperties",
407
+ "properties",
408
+ },
409
+ ),
410
+ )
411
+ #: JSON Schema draft 2019-09
412
+ DRAFT201909 = Specification(
413
+ name="draft2019-09",
414
+ id_of=_dollar_id,
415
+ subresources_of=_subresources_of_with_crazy_items(
416
+ in_value={
417
+ "additionalItems",
418
+ "additionalProperties",
419
+ "contains",
420
+ "contentSchema",
421
+ "else",
422
+ "if",
423
+ "not",
424
+ "propertyNames",
425
+ "then",
426
+ "unevaluatedItems",
427
+ "unevaluatedProperties",
428
+ },
429
+ in_subarray={"allOf", "anyOf", "oneOf"},
430
+ in_subvalues={
431
+ "$defs",
432
+ "definitions",
433
+ "dependentSchemas",
434
+ "patternProperties",
435
+ "properties",
436
+ },
437
+ ),
438
+ anchors_in=_anchor_2019,
439
+ maybe_in_subresource=_maybe_in_subresource_crazy_items(
440
+ in_value={
441
+ "additionalItems",
442
+ "additionalProperties",
443
+ "contains",
444
+ "contentSchema",
445
+ "else",
446
+ "if",
447
+ "not",
448
+ "propertyNames",
449
+ "then",
450
+ "unevaluatedItems",
451
+ "unevaluatedProperties",
452
+ },
453
+ in_subarray={"allOf", "anyOf", "oneOf"},
454
+ in_subvalues={
455
+ "$defs",
456
+ "definitions",
457
+ "dependentSchemas",
458
+ "patternProperties",
459
+ "properties",
460
+ },
461
+ ),
462
+ )
463
+ #: JSON Schema draft 7
464
+ DRAFT7 = Specification(
465
+ name="draft-07",
466
+ id_of=_legacy_dollar_id,
467
+ subresources_of=_subresources_of_with_crazy_items_dependencies(
468
+ in_value={
469
+ "additionalItems",
470
+ "additionalProperties",
471
+ "contains",
472
+ "else",
473
+ "if",
474
+ "not",
475
+ "propertyNames",
476
+ "then",
477
+ },
478
+ in_subarray={"allOf", "anyOf", "oneOf"},
479
+ in_subvalues={"definitions", "patternProperties", "properties"},
480
+ ),
481
+ anchors_in=_legacy_anchor_in_dollar_id,
482
+ maybe_in_subresource=_maybe_in_subresource_crazy_items_dependencies(
483
+ in_value={
484
+ "additionalItems",
485
+ "additionalProperties",
486
+ "contains",
487
+ "else",
488
+ "if",
489
+ "not",
490
+ "propertyNames",
491
+ "then",
492
+ },
493
+ in_subarray={"allOf", "anyOf", "oneOf"},
494
+ in_subvalues={"definitions", "patternProperties", "properties"},
495
+ ),
496
+ )
497
+ #: JSON Schema draft 6
498
+ DRAFT6 = Specification(
499
+ name="draft-06",
500
+ id_of=_legacy_dollar_id,
501
+ subresources_of=_subresources_of_with_crazy_items_dependencies(
502
+ in_value={
503
+ "additionalItems",
504
+ "additionalProperties",
505
+ "contains",
506
+ "not",
507
+ "propertyNames",
508
+ },
509
+ in_subarray={"allOf", "anyOf", "oneOf"},
510
+ in_subvalues={"definitions", "patternProperties", "properties"},
511
+ ),
512
+ anchors_in=_legacy_anchor_in_dollar_id,
513
+ maybe_in_subresource=_maybe_in_subresource_crazy_items_dependencies(
514
+ in_value={
515
+ "additionalItems",
516
+ "additionalProperties",
517
+ "contains",
518
+ "not",
519
+ "propertyNames",
520
+ },
521
+ in_subarray={"allOf", "anyOf", "oneOf"},
522
+ in_subvalues={"definitions", "patternProperties", "properties"},
523
+ ),
524
+ )
525
+ #: JSON Schema draft 4
526
+ DRAFT4 = Specification(
527
+ name="draft-04",
528
+ id_of=_legacy_id,
529
+ subresources_of=_subresources_of_with_crazy_aP_items_dependencies(
530
+ in_value={"not"},
531
+ in_subarray={"allOf", "anyOf", "oneOf"},
532
+ in_subvalues={"definitions", "patternProperties", "properties"},
533
+ ),
534
+ anchors_in=_legacy_anchor_in_id,
535
+ maybe_in_subresource=_maybe_in_subresource_crazy_items_dependencies(
536
+ in_value={"additionalItems", "additionalProperties", "not"},
537
+ in_subarray={"allOf", "anyOf", "oneOf"},
538
+ in_subvalues={"definitions", "patternProperties", "properties"},
539
+ ),
540
+ )
541
+ #: JSON Schema draft 3
542
+ DRAFT3 = Specification(
543
+ name="draft-03",
544
+ id_of=_legacy_id,
545
+ subresources_of=_subresources_of_with_crazy_aP_items_dependencies(
546
+ in_subarray={"extends"},
547
+ in_subvalues={"definitions", "patternProperties", "properties"},
548
+ ),
549
+ anchors_in=_legacy_anchor_in_id,
550
+ maybe_in_subresource=_maybe_in_subresource_crazy_items_dependencies(
551
+ in_value={"additionalItems", "additionalProperties"},
552
+ in_subarray={"extends"},
553
+ in_subvalues={"definitions", "patternProperties", "properties"},
554
+ ),
555
+ )
556
+
557
+
558
+ _SPECIFICATIONS: Registry[Specification[Schema]] = Registry(
559
+ {
560
+ dialect_id: Resource.opaque(specification)
561
+ for dialect_id, specification in [
562
+ ("https://json-schema.org/draft/2020-12/schema", DRAFT202012),
563
+ ("https://json-schema.org/draft/2019-09/schema", DRAFT201909),
564
+ ("http://json-schema.org/draft-07/schema", DRAFT7),
565
+ ("http://json-schema.org/draft-06/schema", DRAFT6),
566
+ ("http://json-schema.org/draft-04/schema", DRAFT4),
567
+ ("http://json-schema.org/draft-03/schema", DRAFT3),
568
+ ]
569
+ },
570
+ )
571
+
572
+
573
+ def specification_with(
574
+ dialect_id: URI,
575
+ default: Specification[Any] | _Unset = _UNSET,
576
+ ) -> Specification[Any]:
577
+ """
578
+ Retrieve the `Specification` with the given dialect identifier.
579
+
580
+ Raises:
581
+
582
+ `UnknownDialect`
583
+
584
+ if the given ``dialect_id`` isn't known
585
+
586
+ """
587
+ resource = _SPECIFICATIONS.get(dialect_id.rstrip("#"))
588
+ if resource is not None:
589
+ return resource.contents
590
+ if default is _UNSET:
591
+ raise UnknownDialect(dialect_id)
592
+ return default
593
+
594
+
595
+ @frozen
596
+ class DynamicAnchor:
597
+ """
598
+ Dynamic anchors, introduced in draft 2020.
599
+ """
600
+
601
+ name: str
602
+ resource: SchemaResource
603
+
604
+ def resolve(self, resolver: _Resolver[Schema]) -> _Resolved[Schema]:
605
+ """
606
+ Resolve this anchor dynamically.
607
+ """
608
+ last = self.resource
609
+ for uri, registry in resolver.dynamic_scope():
610
+ try:
611
+ anchor = registry.anchor(uri, self.name).value
612
+ except exceptions.NoSuchAnchor:
613
+ continue
614
+ if isinstance(anchor, DynamicAnchor):
615
+ last = anchor.resource
616
+ return _Resolved(
617
+ contents=last.contents,
618
+ resolver=resolver.in_subresource(last),
619
+ )
620
+
621
+
622
+ def lookup_recursive_ref(resolver: _Resolver[Schema]) -> _Resolved[Schema]:
623
+ """
624
+ Recursive references (via recursive anchors), present only in draft 2019.
625
+
626
+ As per the 2019 specification (§ 8.2.4.2.1), only the ``#`` recursive
627
+ reference is supported (and is therefore assumed to be the relevant
628
+ reference).
629
+ """
630
+ resolved = resolver.lookup("#")
631
+ if isinstance(resolved.contents, Mapping) and resolved.contents.get(
632
+ "$recursiveAnchor",
633
+ ):
634
+ for uri, _ in resolver.dynamic_scope():
635
+ next_resolved = resolver.lookup(uri)
636
+ if not isinstance(
637
+ next_resolved.contents,
638
+ Mapping,
639
+ ) or not next_resolved.contents.get("$recursiveAnchor"):
640
+ break
641
+ resolved = next_resolved
642
+ return resolved
.venv/lib/python3.11/site-packages/referencing/py.typed ADDED
File without changes
.venv/lib/python3.11/site-packages/referencing/retrieval.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Helpers related to (dynamic) resource retrieval.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from functools import lru_cache
8
+ from typing import TYPE_CHECKING, Callable
9
+ import json
10
+
11
+ try:
12
+ from typing_extensions import TypeVar
13
+ except ImportError: # pragma: no cover
14
+ from typing import TypeVar
15
+
16
+ from referencing import Resource
17
+
18
+ if TYPE_CHECKING:
19
+ from referencing.typing import URI, D, Retrieve
20
+
21
+ #: A serialized document (e.g. a JSON string)
22
+ _T = TypeVar("_T", default=str)
23
+
24
+
25
+ def to_cached_resource(
26
+ cache: Callable[[Retrieve[D]], Retrieve[D]] | None = None,
27
+ loads: Callable[[_T], D] = json.loads,
28
+ from_contents: Callable[[D], Resource[D]] = Resource.from_contents,
29
+ ) -> Callable[[Callable[[URI], _T]], Retrieve[D]]:
30
+ """
31
+ Create a retriever which caches its return values from a simpler callable.
32
+
33
+ Takes a function which returns things like serialized JSON (strings) and
34
+ returns something suitable for passing to `Registry` as a retrieve
35
+ function.
36
+
37
+ This decorator both reduces a small bit of boilerplate for a common case
38
+ (deserializing JSON from strings and creating `Resource` objects from the
39
+ result) as well as makes the probable need for caching a bit easier.
40
+ Retrievers which otherwise do expensive operations (like hitting the
41
+ network) might otherwise be called repeatedly.
42
+
43
+ Examples
44
+ --------
45
+
46
+ .. testcode::
47
+
48
+ from referencing import Registry
49
+ from referencing.typing import URI
50
+ import referencing.retrieval
51
+
52
+
53
+ @referencing.retrieval.to_cached_resource()
54
+ def retrieve(uri: URI):
55
+ print(f"Retrieved {uri}")
56
+
57
+ # Normally, go get some expensive JSON from the network, a file ...
58
+ return '''
59
+ {
60
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
61
+ "foo": "bar"
62
+ }
63
+ '''
64
+
65
+ one = Registry(retrieve=retrieve).get_or_retrieve("urn:example:foo")
66
+ print(one.value.contents["foo"])
67
+
68
+ # Retrieving the same URI again reuses the same value (and thus doesn't
69
+ # print another retrieval message here)
70
+ two = Registry(retrieve=retrieve).get_or_retrieve("urn:example:foo")
71
+ print(two.value.contents["foo"])
72
+
73
+ .. testoutput::
74
+
75
+ Retrieved urn:example:foo
76
+ bar
77
+ bar
78
+
79
+ """
80
+ if cache is None:
81
+ cache = lru_cache(maxsize=None)
82
+
83
+ def decorator(retrieve: Callable[[URI], _T]):
84
+ @cache
85
+ def cached_retrieve(uri: URI):
86
+ response = retrieve(uri)
87
+ contents = loads(response)
88
+ return from_contents(contents)
89
+
90
+ return cached_retrieve
91
+
92
+ return decorator
.venv/lib/python3.11/site-packages/referencing/tests/__init__.py ADDED
File without changes
.venv/lib/python3.11/site-packages/referencing/tests/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (190 Bytes). View file
 
.venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_core.cpython-311.pyc ADDED
Binary file (69.5 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_exceptions.cpython-311.pyc ADDED
Binary file (2.95 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_jsonschema.cpython-311.pyc ADDED
Binary file (13.9 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_referencing_suite.cpython-311.pyc ADDED
Binary file (5.17 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/tests/__pycache__/test_retrieval.cpython-311.pyc ADDED
Binary file (6.8 kB). View file
 
.venv/lib/python3.11/site-packages/referencing/tests/test_core.py ADDED
@@ -0,0 +1,1057 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from rpds import HashTrieMap
2
+ import pytest
3
+
4
+ from referencing import Anchor, Registry, Resource, Specification, exceptions
5
+ from referencing.jsonschema import DRAFT202012
6
+
7
+ ID_AND_CHILDREN = Specification(
8
+ name="id-and-children",
9
+ id_of=lambda contents: contents.get("ID"),
10
+ subresources_of=lambda contents: contents.get("children", []),
11
+ anchors_in=lambda specification, contents: [
12
+ Anchor(
13
+ name=name,
14
+ resource=specification.create_resource(contents=each),
15
+ )
16
+ for name, each in contents.get("anchors", {}).items()
17
+ ],
18
+ maybe_in_subresource=lambda segments, resolver, subresource: (
19
+ resolver.in_subresource(subresource)
20
+ if not len(segments) % 2
21
+ and all(each == "children" for each in segments[::2])
22
+ else resolver
23
+ ),
24
+ )
25
+
26
+
27
+ def blow_up(uri): # pragma: no cover
28
+ """
29
+ A retriever suitable for use in tests which expect it never to be used.
30
+ """
31
+ raise RuntimeError("This retrieve function expects to never be called!")
32
+
33
+
34
+ class TestRegistry:
35
+ def test_with_resource(self):
36
+ """
37
+ Adding a resource to the registry then allows re-retrieving it.
38
+ """
39
+
40
+ resource = Resource.opaque(contents={"foo": "bar"})
41
+ uri = "urn:example"
42
+ registry = Registry().with_resource(uri=uri, resource=resource)
43
+ assert registry[uri] is resource
44
+
45
+ def test_with_resources(self):
46
+ """
47
+ Adding multiple resources to the registry is like adding each one.
48
+ """
49
+
50
+ one = Resource.opaque(contents={})
51
+ two = Resource(contents={"foo": "bar"}, specification=ID_AND_CHILDREN)
52
+ registry = Registry().with_resources(
53
+ [
54
+ ("http://example.com/1", one),
55
+ ("http://example.com/foo/bar", two),
56
+ ],
57
+ )
58
+ assert registry == Registry().with_resource(
59
+ uri="http://example.com/1",
60
+ resource=one,
61
+ ).with_resource(
62
+ uri="http://example.com/foo/bar",
63
+ resource=two,
64
+ )
65
+
66
+ def test_matmul_resource(self):
67
+ uri = "urn:example:resource"
68
+ resource = ID_AND_CHILDREN.create_resource({"ID": uri, "foo": 12})
69
+ registry = resource @ Registry()
70
+ assert registry == Registry().with_resource(uri, resource)
71
+
72
+ def test_matmul_many_resources(self):
73
+ one_uri = "urn:example:one"
74
+ one = ID_AND_CHILDREN.create_resource({"ID": one_uri, "foo": 12})
75
+
76
+ two_uri = "urn:example:two"
77
+ two = ID_AND_CHILDREN.create_resource({"ID": two_uri, "foo": 12})
78
+
79
+ registry = [one, two] @ Registry()
80
+ assert registry == Registry().with_resources(
81
+ [(one_uri, one), (two_uri, two)],
82
+ )
83
+
84
+ def test_matmul_resource_without_id(self):
85
+ resource = Resource.opaque(contents={"foo": "bar"})
86
+ with pytest.raises(exceptions.NoInternalID) as e:
87
+ resource @ Registry()
88
+ assert e.value == exceptions.NoInternalID(resource=resource)
89
+
90
+ def test_with_contents_from_json_schema(self):
91
+ uri = "urn:example"
92
+ schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
93
+ registry = Registry().with_contents([(uri, schema)])
94
+
95
+ expected = Resource(contents=schema, specification=DRAFT202012)
96
+ assert registry[uri] == expected
97
+
98
+ def test_with_contents_and_default_specification(self):
99
+ uri = "urn:example"
100
+ registry = Registry().with_contents(
101
+ [(uri, {"foo": "bar"})],
102
+ default_specification=Specification.OPAQUE,
103
+ )
104
+ assert registry[uri] == Resource.opaque({"foo": "bar"})
105
+
106
+ def test_len(self):
107
+ total = 5
108
+ registry = Registry().with_contents(
109
+ [(str(i), {"foo": "bar"}) for i in range(total)],
110
+ default_specification=Specification.OPAQUE,
111
+ )
112
+ assert len(registry) == total
113
+
114
+ def test_bool_empty(self):
115
+ assert not Registry()
116
+
117
+ def test_bool_not_empty(self):
118
+ registry = Registry().with_contents(
119
+ [(str(i), {"foo": "bar"}) for i in range(3)],
120
+ default_specification=Specification.OPAQUE,
121
+ )
122
+ assert registry
123
+
124
+ def test_iter(self):
125
+ registry = Registry().with_contents(
126
+ [(str(i), {"foo": "bar"}) for i in range(8)],
127
+ default_specification=Specification.OPAQUE,
128
+ )
129
+ assert set(registry) == {str(i) for i in range(8)}
130
+
131
+ def test_crawl_still_has_top_level_resource(self):
132
+ resource = Resource.opaque({"foo": "bar"})
133
+ uri = "urn:example"
134
+ registry = Registry({uri: resource}).crawl()
135
+ assert registry[uri] is resource
136
+
137
+ def test_crawl_finds_a_subresource(self):
138
+ child_id = "urn:child"
139
+ root = ID_AND_CHILDREN.create_resource(
140
+ {"ID": "urn:root", "children": [{"ID": child_id, "foo": 12}]},
141
+ )
142
+ registry = root @ Registry()
143
+ with pytest.raises(LookupError):
144
+ registry[child_id]
145
+
146
+ expected = ID_AND_CHILDREN.create_resource({"ID": child_id, "foo": 12})
147
+ assert registry.crawl()[child_id] == expected
148
+
149
+ def test_crawl_finds_anchors_with_id(self):
150
+ resource = ID_AND_CHILDREN.create_resource(
151
+ {"ID": "urn:bar", "anchors": {"foo": 12}},
152
+ )
153
+ registry = resource @ Registry()
154
+
155
+ assert registry.crawl().anchor(resource.id(), "foo").value == Anchor(
156
+ name="foo",
157
+ resource=ID_AND_CHILDREN.create_resource(12),
158
+ )
159
+
160
+ def test_crawl_finds_anchors_no_id(self):
161
+ resource = ID_AND_CHILDREN.create_resource({"anchors": {"foo": 12}})
162
+ registry = Registry().with_resource("urn:root", resource)
163
+
164
+ assert registry.crawl().anchor("urn:root", "foo").value == Anchor(
165
+ name="foo",
166
+ resource=ID_AND_CHILDREN.create_resource(12),
167
+ )
168
+
169
+ def test_contents(self):
170
+ resource = Resource.opaque({"foo": "bar"})
171
+ uri = "urn:example"
172
+ registry = Registry().with_resource(uri, resource)
173
+ assert registry.contents(uri) == {"foo": "bar"}
174
+
175
+ def test_getitem_strips_empty_fragments(self):
176
+ uri = "http://example.com/"
177
+ resource = ID_AND_CHILDREN.create_resource({"ID": uri + "#"})
178
+ registry = resource @ Registry()
179
+ assert registry[uri] == registry[uri + "#"] == resource
180
+
181
+ def test_contents_strips_empty_fragments(self):
182
+ uri = "http://example.com/"
183
+ resource = ID_AND_CHILDREN.create_resource({"ID": uri + "#"})
184
+ registry = resource @ Registry()
185
+ assert (
186
+ registry.contents(uri)
187
+ == registry.contents(uri + "#")
188
+ == {"ID": uri + "#"}
189
+ )
190
+
191
+ def test_contents_nonexistent_resource(self):
192
+ registry = Registry()
193
+ with pytest.raises(exceptions.NoSuchResource) as e:
194
+ registry.contents("urn:example")
195
+ assert e.value == exceptions.NoSuchResource(ref="urn:example")
196
+
197
+ def test_crawled_anchor(self):
198
+ resource = ID_AND_CHILDREN.create_resource({"anchors": {"foo": "bar"}})
199
+ registry = Registry().with_resource("urn:example", resource)
200
+ retrieved = registry.anchor("urn:example", "foo")
201
+ assert retrieved.value == Anchor(
202
+ name="foo",
203
+ resource=ID_AND_CHILDREN.create_resource("bar"),
204
+ )
205
+ assert retrieved.registry == registry.crawl()
206
+
207
+ def test_anchor_in_nonexistent_resource(self):
208
+ registry = Registry()
209
+ with pytest.raises(exceptions.NoSuchResource) as e:
210
+ registry.anchor("urn:example", "foo")
211
+ assert e.value == exceptions.NoSuchResource(ref="urn:example")
212
+
213
+ def test_init(self):
214
+ one = Resource.opaque(contents={})
215
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
216
+ registry = Registry(
217
+ {
218
+ "http://example.com/1": one,
219
+ "http://example.com/foo/bar": two,
220
+ },
221
+ )
222
+ assert (
223
+ registry
224
+ == Registry()
225
+ .with_resources(
226
+ [
227
+ ("http://example.com/1", one),
228
+ ("http://example.com/foo/bar", two),
229
+ ],
230
+ )
231
+ .crawl()
232
+ )
233
+
234
+ def test_dict_conversion(self):
235
+ """
236
+ Passing a `dict` to `Registry` gets converted to a `HashTrieMap`.
237
+
238
+ So continuing to use the registry works.
239
+ """
240
+
241
+ one = Resource.opaque(contents={})
242
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
243
+ registry = Registry(
244
+ {"http://example.com/1": one},
245
+ ).with_resource("http://example.com/foo/bar", two)
246
+ assert (
247
+ registry.crawl()
248
+ == Registry()
249
+ .with_resources(
250
+ [
251
+ ("http://example.com/1", one),
252
+ ("http://example.com/foo/bar", two),
253
+ ],
254
+ )
255
+ .crawl()
256
+ )
257
+
258
+ def test_no_such_resource(self):
259
+ registry = Registry()
260
+ with pytest.raises(exceptions.NoSuchResource) as e:
261
+ registry["urn:bigboom"]
262
+ assert e.value == exceptions.NoSuchResource(ref="urn:bigboom")
263
+
264
+ def test_combine(self):
265
+ one = Resource.opaque(contents={})
266
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
267
+ three = ID_AND_CHILDREN.create_resource({"baz": "quux"})
268
+ four = ID_AND_CHILDREN.create_resource({"anchors": {"foo": 12}})
269
+
270
+ first = Registry({"http://example.com/1": one})
271
+ second = Registry().with_resource("http://example.com/foo/bar", two)
272
+ third = Registry(
273
+ {
274
+ "http://example.com/1": one,
275
+ "http://example.com/baz": three,
276
+ },
277
+ )
278
+ fourth = (
279
+ Registry()
280
+ .with_resource(
281
+ "http://example.com/foo/quux",
282
+ four,
283
+ )
284
+ .crawl()
285
+ )
286
+ assert first.combine(second, third, fourth) == Registry(
287
+ [
288
+ ("http://example.com/1", one),
289
+ ("http://example.com/baz", three),
290
+ ("http://example.com/foo/quux", four),
291
+ ],
292
+ anchors=HashTrieMap(
293
+ {
294
+ ("http://example.com/foo/quux", "foo"): Anchor(
295
+ name="foo",
296
+ resource=ID_AND_CHILDREN.create_resource(12),
297
+ ),
298
+ },
299
+ ),
300
+ ).with_resource("http://example.com/foo/bar", two)
301
+
302
+ def test_combine_self(self):
303
+ """
304
+ Combining a registry with itself short-circuits.
305
+
306
+ This is a performance optimization -- otherwise we do lots more work
307
+ (in jsonschema this seems to correspond to making the test suite take
308
+ *3x* longer).
309
+ """
310
+
311
+ registry = Registry({"urn:foo": "bar"})
312
+ assert registry.combine(registry) is registry
313
+
314
+ def test_combine_with_uncrawled_resources(self):
315
+ one = Resource.opaque(contents={})
316
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
317
+ three = ID_AND_CHILDREN.create_resource({"baz": "quux"})
318
+
319
+ first = Registry().with_resource("http://example.com/1", one)
320
+ second = Registry().with_resource("http://example.com/foo/bar", two)
321
+ third = Registry(
322
+ {
323
+ "http://example.com/1": one,
324
+ "http://example.com/baz": three,
325
+ },
326
+ )
327
+ expected = Registry(
328
+ [
329
+ ("http://example.com/1", one),
330
+ ("http://example.com/foo/bar", two),
331
+ ("http://example.com/baz", three),
332
+ ],
333
+ )
334
+ combined = first.combine(second, third)
335
+ assert combined != expected
336
+ assert combined.crawl() == expected
337
+
338
+ def test_combine_with_single_retrieve(self):
339
+ one = Resource.opaque(contents={})
340
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
341
+ three = ID_AND_CHILDREN.create_resource({"baz": "quux"})
342
+
343
+ def retrieve(uri): # pragma: no cover
344
+ pass
345
+
346
+ first = Registry().with_resource("http://example.com/1", one)
347
+ second = Registry(
348
+ retrieve=retrieve,
349
+ ).with_resource("http://example.com/2", two)
350
+ third = Registry().with_resource("http://example.com/3", three)
351
+
352
+ assert first.combine(second, third) == Registry(
353
+ retrieve=retrieve,
354
+ ).with_resources(
355
+ [
356
+ ("http://example.com/1", one),
357
+ ("http://example.com/2", two),
358
+ ("http://example.com/3", three),
359
+ ],
360
+ )
361
+ assert second.combine(first, third) == Registry(
362
+ retrieve=retrieve,
363
+ ).with_resources(
364
+ [
365
+ ("http://example.com/1", one),
366
+ ("http://example.com/2", two),
367
+ ("http://example.com/3", three),
368
+ ],
369
+ )
370
+
371
+ def test_combine_with_common_retrieve(self):
372
+ one = Resource.opaque(contents={})
373
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
374
+ three = ID_AND_CHILDREN.create_resource({"baz": "quux"})
375
+
376
+ def retrieve(uri): # pragma: no cover
377
+ pass
378
+
379
+ first = Registry(retrieve=retrieve).with_resource(
380
+ "http://example.com/1",
381
+ one,
382
+ )
383
+ second = Registry(
384
+ retrieve=retrieve,
385
+ ).with_resource("http://example.com/2", two)
386
+ third = Registry(retrieve=retrieve).with_resource(
387
+ "http://example.com/3",
388
+ three,
389
+ )
390
+
391
+ assert first.combine(second, third) == Registry(
392
+ retrieve=retrieve,
393
+ ).with_resources(
394
+ [
395
+ ("http://example.com/1", one),
396
+ ("http://example.com/2", two),
397
+ ("http://example.com/3", three),
398
+ ],
399
+ )
400
+ assert second.combine(first, third) == Registry(
401
+ retrieve=retrieve,
402
+ ).with_resources(
403
+ [
404
+ ("http://example.com/1", one),
405
+ ("http://example.com/2", two),
406
+ ("http://example.com/3", three),
407
+ ],
408
+ )
409
+
410
+ def test_combine_conflicting_retrieve(self):
411
+ one = Resource.opaque(contents={})
412
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
413
+ three = ID_AND_CHILDREN.create_resource({"baz": "quux"})
414
+
415
+ def foo_retrieve(uri): # pragma: no cover
416
+ pass
417
+
418
+ def bar_retrieve(uri): # pragma: no cover
419
+ pass
420
+
421
+ first = Registry(retrieve=foo_retrieve).with_resource(
422
+ "http://example.com/1",
423
+ one,
424
+ )
425
+ second = Registry().with_resource("http://example.com/2", two)
426
+ third = Registry(retrieve=bar_retrieve).with_resource(
427
+ "http://example.com/3",
428
+ three,
429
+ )
430
+
431
+ with pytest.raises(Exception, match="conflict.*retriev"):
432
+ first.combine(second, third)
433
+
434
+ def test_remove(self):
435
+ one = Resource.opaque(contents={})
436
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
437
+ registry = Registry({"urn:foo": one, "urn:bar": two})
438
+ assert registry.remove("urn:foo") == Registry({"urn:bar": two})
439
+
440
+ def test_remove_uncrawled(self):
441
+ one = Resource.opaque(contents={})
442
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
443
+ registry = Registry().with_resources(
444
+ [("urn:foo", one), ("urn:bar", two)],
445
+ )
446
+ assert registry.remove("urn:foo") == Registry().with_resource(
447
+ "urn:bar",
448
+ two,
449
+ )
450
+
451
+ def test_remove_with_anchors(self):
452
+ one = Resource.opaque(contents={})
453
+ two = ID_AND_CHILDREN.create_resource({"anchors": {"foo": "bar"}})
454
+ registry = (
455
+ Registry()
456
+ .with_resources(
457
+ [("urn:foo", one), ("urn:bar", two)],
458
+ )
459
+ .crawl()
460
+ )
461
+ assert (
462
+ registry.remove("urn:bar")
463
+ == Registry()
464
+ .with_resource(
465
+ "urn:foo",
466
+ one,
467
+ )
468
+ .crawl()
469
+ )
470
+
471
+ def test_remove_nonexistent_uri(self):
472
+ with pytest.raises(exceptions.NoSuchResource) as e:
473
+ Registry().remove("urn:doesNotExist")
474
+ assert e.value == exceptions.NoSuchResource(ref="urn:doesNotExist")
475
+
476
+ def test_retrieve(self):
477
+ foo = Resource.opaque({"foo": "bar"})
478
+ registry = Registry(retrieve=lambda uri: foo)
479
+ assert registry.get_or_retrieve("urn:example").value == foo
480
+
481
+ def test_retrieve_arbitrary_exception(self):
482
+ foo = Resource.opaque({"foo": "bar"})
483
+
484
+ def retrieve(uri):
485
+ if uri == "urn:succeed":
486
+ return foo
487
+ raise Exception("Oh no!")
488
+
489
+ registry = Registry(retrieve=retrieve)
490
+ assert registry.get_or_retrieve("urn:succeed").value == foo
491
+ with pytest.raises(exceptions.Unretrievable):
492
+ registry.get_or_retrieve("urn:uhoh")
493
+
494
+ def test_retrieve_no_such_resource(self):
495
+ foo = Resource.opaque({"foo": "bar"})
496
+
497
+ def retrieve(uri):
498
+ if uri == "urn:succeed":
499
+ return foo
500
+ raise exceptions.NoSuchResource(ref=uri)
501
+
502
+ registry = Registry(retrieve=retrieve)
503
+ assert registry.get_or_retrieve("urn:succeed").value == foo
504
+ with pytest.raises(exceptions.NoSuchResource):
505
+ registry.get_or_retrieve("urn:uhoh")
506
+
507
+ def test_retrieve_cannot_determine_specification(self):
508
+ def retrieve(uri):
509
+ return Resource.from_contents({})
510
+
511
+ registry = Registry(retrieve=retrieve)
512
+ with pytest.raises(exceptions.CannotDetermineSpecification):
513
+ registry.get_or_retrieve("urn:uhoh")
514
+
515
+ def test_retrieve_already_available_resource(self):
516
+ foo = Resource.opaque({"foo": "bar"})
517
+ registry = Registry({"urn:example": foo}, retrieve=blow_up)
518
+ assert registry["urn:example"] == foo
519
+ assert registry.get_or_retrieve("urn:example").value == foo
520
+
521
+ def test_retrieve_first_checks_crawlable_resource(self):
522
+ child = ID_AND_CHILDREN.create_resource({"ID": "urn:child", "foo": 12})
523
+ root = ID_AND_CHILDREN.create_resource({"children": [child.contents]})
524
+ registry = Registry(retrieve=blow_up).with_resource("urn:root", root)
525
+ assert registry.crawl()["urn:child"] == child
526
+
527
+ def test_resolver(self):
528
+ one = Resource.opaque(contents={})
529
+ registry = Registry({"http://example.com": one})
530
+ resolver = registry.resolver(base_uri="http://example.com")
531
+ assert resolver.lookup("#").contents == {}
532
+
533
+ def test_resolver_with_root_identified(self):
534
+ root = ID_AND_CHILDREN.create_resource({"ID": "http://example.com"})
535
+ resolver = Registry().resolver_with_root(root)
536
+ assert resolver.lookup("http://example.com").contents == root.contents
537
+ assert resolver.lookup("#").contents == root.contents
538
+
539
+ def test_resolver_with_root_unidentified(self):
540
+ root = Resource.opaque(contents={})
541
+ resolver = Registry().resolver_with_root(root)
542
+ assert resolver.lookup("#").contents == root.contents
543
+
544
+ def test_repr(self):
545
+ one = Resource.opaque(contents={})
546
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
547
+ registry = Registry().with_resources(
548
+ [
549
+ ("http://example.com/1", one),
550
+ ("http://example.com/foo/bar", two),
551
+ ],
552
+ )
553
+ assert repr(registry) == "<Registry (2 uncrawled resources)>"
554
+ assert repr(registry.crawl()) == "<Registry (2 resources)>"
555
+
556
+ def test_repr_mixed_crawled(self):
557
+ one = Resource.opaque(contents={})
558
+ two = ID_AND_CHILDREN.create_resource({"foo": "bar"})
559
+ registry = (
560
+ Registry(
561
+ {"http://example.com/1": one},
562
+ )
563
+ .crawl()
564
+ .with_resource(uri="http://example.com/foo/bar", resource=two)
565
+ )
566
+ assert repr(registry) == "<Registry (2 resources, 1 uncrawled)>"
567
+
568
+ def test_repr_one_resource(self):
569
+ registry = Registry().with_resource(
570
+ uri="http://example.com/1",
571
+ resource=Resource.opaque(contents={}),
572
+ )
573
+ assert repr(registry) == "<Registry (1 uncrawled resource)>"
574
+
575
+ def test_repr_empty(self):
576
+ assert repr(Registry()) == "<Registry (0 resources)>"
577
+
578
+
579
+ class TestResource:
580
+ def test_from_contents_from_json_schema(self):
581
+ schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
582
+ resource = Resource.from_contents(schema)
583
+ assert resource == Resource(contents=schema, specification=DRAFT202012)
584
+
585
+ def test_from_contents_with_no_discernible_information(self):
586
+ """
587
+ Creating a resource with no discernible way to see what
588
+ specification it belongs to (e.g. no ``$schema`` keyword for JSON
589
+ Schema) raises an error.
590
+ """
591
+
592
+ with pytest.raises(exceptions.CannotDetermineSpecification):
593
+ Resource.from_contents({"foo": "bar"})
594
+
595
+ def test_from_contents_with_no_discernible_information_and_default(self):
596
+ resource = Resource.from_contents(
597
+ {"foo": "bar"},
598
+ default_specification=Specification.OPAQUE,
599
+ )
600
+ assert resource == Resource.opaque(contents={"foo": "bar"})
601
+
602
+ def test_from_contents_unneeded_default(self):
603
+ schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
604
+ resource = Resource.from_contents(
605
+ schema,
606
+ default_specification=Specification.OPAQUE,
607
+ )
608
+ assert resource == Resource(
609
+ contents=schema,
610
+ specification=DRAFT202012,
611
+ )
612
+
613
+ def test_non_mapping_from_contents(self):
614
+ resource = Resource.from_contents(
615
+ True,
616
+ default_specification=ID_AND_CHILDREN,
617
+ )
618
+ assert resource == Resource(
619
+ contents=True,
620
+ specification=ID_AND_CHILDREN,
621
+ )
622
+
623
+ def test_from_contents_with_fallback(self):
624
+ resource = Resource.from_contents(
625
+ {"foo": "bar"},
626
+ default_specification=Specification.OPAQUE,
627
+ )
628
+ assert resource == Resource.opaque(contents={"foo": "bar"})
629
+
630
+ def test_id_delegates_to_specification(self):
631
+ specification = Specification(
632
+ name="",
633
+ id_of=lambda contents: "urn:fixedID",
634
+ subresources_of=lambda contents: [],
635
+ anchors_in=lambda specification, contents: [],
636
+ maybe_in_subresource=(
637
+ lambda segments, resolver, subresource: resolver
638
+ ),
639
+ )
640
+ resource = Resource(
641
+ contents={"foo": "baz"},
642
+ specification=specification,
643
+ )
644
+ assert resource.id() == "urn:fixedID"
645
+
646
+ def test_id_strips_empty_fragment(self):
647
+ uri = "http://example.com/"
648
+ root = ID_AND_CHILDREN.create_resource({"ID": uri + "#"})
649
+ assert root.id() == uri
650
+
651
+ def test_subresources_delegates_to_specification(self):
652
+ resource = ID_AND_CHILDREN.create_resource({"children": [{}, 12]})
653
+ assert list(resource.subresources()) == [
654
+ ID_AND_CHILDREN.create_resource(each) for each in [{}, 12]
655
+ ]
656
+
657
+ def test_subresource_with_different_specification(self):
658
+ schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
659
+ resource = ID_AND_CHILDREN.create_resource({"children": [schema]})
660
+ assert list(resource.subresources()) == [
661
+ DRAFT202012.create_resource(schema),
662
+ ]
663
+
664
+ def test_anchors_delegates_to_specification(self):
665
+ resource = ID_AND_CHILDREN.create_resource(
666
+ {"anchors": {"foo": {}, "bar": 1, "baz": ""}},
667
+ )
668
+ assert list(resource.anchors()) == [
669
+ Anchor(name="foo", resource=ID_AND_CHILDREN.create_resource({})),
670
+ Anchor(name="bar", resource=ID_AND_CHILDREN.create_resource(1)),
671
+ Anchor(name="baz", resource=ID_AND_CHILDREN.create_resource("")),
672
+ ]
673
+
674
+ def test_pointer_to_mapping(self):
675
+ resource = Resource.opaque(contents={"foo": "baz"})
676
+ resolver = Registry().resolver()
677
+ assert resource.pointer("/foo", resolver=resolver).contents == "baz"
678
+
679
+ def test_pointer_to_array(self):
680
+ resource = Resource.opaque(contents={"foo": {"bar": [3]}})
681
+ resolver = Registry().resolver()
682
+ assert resource.pointer("/foo/bar/0", resolver=resolver).contents == 3
683
+
684
+ def test_root_pointer(self):
685
+ contents = {"foo": "baz"}
686
+ resource = Resource.opaque(contents=contents)
687
+ resolver = Registry().resolver()
688
+ assert resource.pointer("", resolver=resolver).contents == contents
689
+
690
+ def test_opaque(self):
691
+ contents = {"foo": "bar"}
692
+ assert Resource.opaque(contents) == Resource(
693
+ contents=contents,
694
+ specification=Specification.OPAQUE,
695
+ )
696
+
697
+
698
+ class TestResolver:
699
+ def test_lookup_exact_uri(self):
700
+ resource = Resource.opaque(contents={"foo": "baz"})
701
+ resolver = Registry({"http://example.com/1": resource}).resolver()
702
+ resolved = resolver.lookup("http://example.com/1")
703
+ assert resolved.contents == resource.contents
704
+
705
+ def test_lookup_subresource(self):
706
+ root = ID_AND_CHILDREN.create_resource(
707
+ {
708
+ "ID": "http://example.com/",
709
+ "children": [
710
+ {"ID": "http://example.com/a", "foo": 12},
711
+ ],
712
+ },
713
+ )
714
+ registry = root @ Registry()
715
+ resolved = registry.resolver().lookup("http://example.com/a")
716
+ assert resolved.contents == {"ID": "http://example.com/a", "foo": 12}
717
+
718
+ def test_lookup_anchor_with_id(self):
719
+ root = ID_AND_CHILDREN.create_resource(
720
+ {
721
+ "ID": "http://example.com/",
722
+ "anchors": {"foo": 12},
723
+ },
724
+ )
725
+ registry = root @ Registry()
726
+ resolved = registry.resolver().lookup("http://example.com/#foo")
727
+ assert resolved.contents == 12
728
+
729
+ def test_lookup_anchor_without_id(self):
730
+ root = ID_AND_CHILDREN.create_resource({"anchors": {"foo": 12}})
731
+ resolver = Registry().with_resource("urn:example", root).resolver()
732
+ resolved = resolver.lookup("urn:example#foo")
733
+ assert resolved.contents == 12
734
+
735
+ def test_lookup_unknown_reference(self):
736
+ resolver = Registry().resolver()
737
+ ref = "http://example.com/does/not/exist"
738
+ with pytest.raises(exceptions.Unresolvable) as e:
739
+ resolver.lookup(ref)
740
+ assert e.value == exceptions.Unresolvable(ref=ref)
741
+
742
+ def test_lookup_non_existent_pointer(self):
743
+ resource = Resource.opaque({"foo": {}})
744
+ resolver = Registry({"http://example.com/1": resource}).resolver()
745
+ ref = "http://example.com/1#/foo/bar"
746
+ with pytest.raises(exceptions.Unresolvable) as e:
747
+ resolver.lookup(ref)
748
+ assert e.value == exceptions.PointerToNowhere(
749
+ ref="/foo/bar",
750
+ resource=resource,
751
+ )
752
+ assert str(e.value) == "'/foo/bar' does not exist within {'foo': {}}"
753
+
754
+ def test_lookup_non_existent_pointer_to_array_index(self):
755
+ resource = Resource.opaque([1, 2, 4, 8])
756
+ resolver = Registry({"http://example.com/1": resource}).resolver()
757
+ ref = "http://example.com/1#/10"
758
+ with pytest.raises(exceptions.Unresolvable) as e:
759
+ resolver.lookup(ref)
760
+ assert e.value == exceptions.PointerToNowhere(
761
+ ref="/10",
762
+ resource=resource,
763
+ )
764
+
765
+ def test_lookup_pointer_to_empty_string(self):
766
+ resolver = Registry().resolver_with_root(Resource.opaque({"": {}}))
767
+ assert resolver.lookup("#/").contents == {}
768
+
769
+ def test_lookup_non_existent_pointer_to_empty_string(self):
770
+ resource = Resource.opaque({"foo": {}})
771
+ resolver = Registry().resolver_with_root(resource)
772
+ with pytest.raises(
773
+ exceptions.Unresolvable,
774
+ match="^'/' does not exist within {'foo': {}}.*'#'",
775
+ ) as e:
776
+ resolver.lookup("#/")
777
+ assert e.value == exceptions.PointerToNowhere(
778
+ ref="/",
779
+ resource=resource,
780
+ )
781
+
782
+ def test_lookup_non_existent_anchor(self):
783
+ root = ID_AND_CHILDREN.create_resource({"anchors": {}})
784
+ resolver = Registry().with_resource("urn:example", root).resolver()
785
+ resolved = resolver.lookup("urn:example")
786
+ assert resolved.contents == root.contents
787
+
788
+ ref = "urn:example#noSuchAnchor"
789
+ with pytest.raises(exceptions.Unresolvable) as e:
790
+ resolver.lookup(ref)
791
+ assert "'noSuchAnchor' does not exist" in str(e.value)
792
+ assert e.value == exceptions.NoSuchAnchor(
793
+ ref="urn:example",
794
+ resource=root,
795
+ anchor="noSuchAnchor",
796
+ )
797
+
798
+ def test_lookup_invalid_JSON_pointerish_anchor(self):
799
+ resolver = Registry().resolver_with_root(
800
+ ID_AND_CHILDREN.create_resource(
801
+ {
802
+ "ID": "http://example.com/",
803
+ "foo": {"bar": 12},
804
+ },
805
+ ),
806
+ )
807
+
808
+ valid = resolver.lookup("#/foo/bar")
809
+ assert valid.contents == 12
810
+
811
+ with pytest.raises(exceptions.InvalidAnchor) as e:
812
+ resolver.lookup("#foo/bar")
813
+ assert " '#/foo/bar'" in str(e.value)
814
+
815
+ def test_lookup_retrieved_resource(self):
816
+ resource = Resource.opaque(contents={"foo": "baz"})
817
+ resolver = Registry(retrieve=lambda uri: resource).resolver()
818
+ resolved = resolver.lookup("http://example.com/")
819
+ assert resolved.contents == resource.contents
820
+
821
+ def test_lookup_failed_retrieved_resource(self):
822
+ """
823
+ Unretrievable exceptions are also wrapped in Unresolvable.
824
+ """
825
+
826
+ uri = "http://example.com/"
827
+
828
+ registry = Registry(retrieve=blow_up)
829
+ with pytest.raises(exceptions.Unretrievable):
830
+ registry.get_or_retrieve(uri)
831
+
832
+ resolver = registry.resolver()
833
+ with pytest.raises(exceptions.Unresolvable):
834
+ resolver.lookup(uri)
835
+
836
+ def test_repeated_lookup_from_retrieved_resource(self):
837
+ """
838
+ A (custom-)retrieved resource is added to the registry returned by
839
+ looking it up.
840
+ """
841
+ resource = Resource.opaque(contents={"foo": "baz"})
842
+ once = [resource]
843
+
844
+ def retrieve(uri):
845
+ return once.pop()
846
+
847
+ resolver = Registry(retrieve=retrieve).resolver()
848
+ resolved = resolver.lookup("http://example.com/")
849
+ assert resolved.contents == resource.contents
850
+
851
+ resolved = resolved.resolver.lookup("http://example.com/")
852
+ assert resolved.contents == resource.contents
853
+
854
+ def test_repeated_anchor_lookup_from_retrieved_resource(self):
855
+ resource = Resource.opaque(contents={"foo": "baz"})
856
+ once = [resource]
857
+
858
+ def retrieve(uri):
859
+ return once.pop()
860
+
861
+ resolver = Registry(retrieve=retrieve).resolver()
862
+ resolved = resolver.lookup("http://example.com/")
863
+ assert resolved.contents == resource.contents
864
+
865
+ resolved = resolved.resolver.lookup("#")
866
+ assert resolved.contents == resource.contents
867
+
868
+ # FIXME: The tests below aren't really representable in the current
869
+ # suite, though we should probably think of ways to do so.
870
+
871
+ def test_in_subresource(self):
872
+ root = ID_AND_CHILDREN.create_resource(
873
+ {
874
+ "ID": "http://example.com/",
875
+ "children": [
876
+ {
877
+ "ID": "child/",
878
+ "children": [{"ID": "grandchild"}],
879
+ },
880
+ ],
881
+ },
882
+ )
883
+ registry = root @ Registry()
884
+
885
+ resolver = registry.resolver()
886
+ first = resolver.lookup("http://example.com/")
887
+ assert first.contents == root.contents
888
+
889
+ with pytest.raises(exceptions.Unresolvable):
890
+ first.resolver.lookup("grandchild")
891
+
892
+ sub = first.resolver.in_subresource(
893
+ ID_AND_CHILDREN.create_resource(first.contents["children"][0]),
894
+ )
895
+ second = sub.lookup("grandchild")
896
+ assert second.contents == {"ID": "grandchild"}
897
+
898
+ def test_in_pointer_subresource(self):
899
+ root = ID_AND_CHILDREN.create_resource(
900
+ {
901
+ "ID": "http://example.com/",
902
+ "children": [
903
+ {
904
+ "ID": "child/",
905
+ "children": [{"ID": "grandchild"}],
906
+ },
907
+ ],
908
+ },
909
+ )
910
+ registry = root @ Registry()
911
+
912
+ resolver = registry.resolver()
913
+ first = resolver.lookup("http://example.com/")
914
+ assert first.contents == root.contents
915
+
916
+ with pytest.raises(exceptions.Unresolvable):
917
+ first.resolver.lookup("grandchild")
918
+
919
+ second = first.resolver.lookup("#/children/0")
920
+ third = second.resolver.lookup("grandchild")
921
+ assert third.contents == {"ID": "grandchild"}
922
+
923
+ def test_dynamic_scope(self):
924
+ one = ID_AND_CHILDREN.create_resource(
925
+ {
926
+ "ID": "http://example.com/",
927
+ "children": [
928
+ {
929
+ "ID": "child/",
930
+ "children": [{"ID": "grandchild"}],
931
+ },
932
+ ],
933
+ },
934
+ )
935
+ two = ID_AND_CHILDREN.create_resource(
936
+ {
937
+ "ID": "http://example.com/two",
938
+ "children": [{"ID": "two-child/"}],
939
+ },
940
+ )
941
+ registry = [one, two] @ Registry()
942
+
943
+ resolver = registry.resolver()
944
+ first = resolver.lookup("http://example.com/")
945
+ second = first.resolver.lookup("#/children/0")
946
+ third = second.resolver.lookup("grandchild")
947
+ fourth = third.resolver.lookup("http://example.com/two")
948
+ assert list(fourth.resolver.dynamic_scope()) == [
949
+ ("http://example.com/child/grandchild", fourth.resolver._registry),
950
+ ("http://example.com/child/", fourth.resolver._registry),
951
+ ("http://example.com/", fourth.resolver._registry),
952
+ ]
953
+ assert list(third.resolver.dynamic_scope()) == [
954
+ ("http://example.com/child/", third.resolver._registry),
955
+ ("http://example.com/", third.resolver._registry),
956
+ ]
957
+ assert list(second.resolver.dynamic_scope()) == [
958
+ ("http://example.com/", second.resolver._registry),
959
+ ]
960
+ assert list(first.resolver.dynamic_scope()) == []
961
+
962
+
963
+ class TestSpecification:
964
+ def test_create_resource(self):
965
+ specification = Specification(
966
+ name="",
967
+ id_of=lambda contents: "urn:fixedID",
968
+ subresources_of=lambda contents: [],
969
+ anchors_in=lambda specification, contents: [],
970
+ maybe_in_subresource=(
971
+ lambda segments, resolver, subresource: resolver
972
+ ),
973
+ )
974
+ resource = specification.create_resource(contents={"foo": "baz"})
975
+ assert resource == Resource(
976
+ contents={"foo": "baz"},
977
+ specification=specification,
978
+ )
979
+ assert resource.id() == "urn:fixedID"
980
+
981
+ def test_detect_from_json_schema(self):
982
+ schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
983
+ specification = Specification.detect(schema)
984
+ assert specification == DRAFT202012
985
+
986
+ def test_detect_with_no_discernible_information(self):
987
+ with pytest.raises(exceptions.CannotDetermineSpecification):
988
+ Specification.detect({"foo": "bar"})
989
+
990
+ def test_detect_with_non_URI_schema(self):
991
+ with pytest.raises(exceptions.CannotDetermineSpecification):
992
+ Specification.detect({"$schema": 37})
993
+
994
+ def test_detect_with_no_discernible_information_and_default(self):
995
+ specification = Specification.OPAQUE.detect({"foo": "bar"})
996
+ assert specification is Specification.OPAQUE
997
+
998
+ def test_detect_unneeded_default(self):
999
+ schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
1000
+ specification = Specification.OPAQUE.detect(schema)
1001
+ assert specification == DRAFT202012
1002
+
1003
+ def test_non_mapping_detect(self):
1004
+ with pytest.raises(exceptions.CannotDetermineSpecification):
1005
+ Specification.detect(True)
1006
+
1007
+ def test_non_mapping_detect_with_default(self):
1008
+ specification = ID_AND_CHILDREN.detect(True)
1009
+ assert specification is ID_AND_CHILDREN
1010
+
1011
+ def test_detect_with_fallback(self):
1012
+ specification = Specification.OPAQUE.detect({"foo": "bar"})
1013
+ assert specification is Specification.OPAQUE
1014
+
1015
+ def test_repr(self):
1016
+ assert (
1017
+ repr(ID_AND_CHILDREN) == "<Specification name='id-and-children'>"
1018
+ )
1019
+
1020
+
1021
+ class TestOpaqueSpecification:
1022
+ THINGS = [{"foo": "bar"}, True, 37, "foo", object()]
1023
+
1024
+ @pytest.mark.parametrize("thing", THINGS)
1025
+ def test_no_id(self, thing):
1026
+ """
1027
+ An arbitrary thing has no ID.
1028
+ """
1029
+
1030
+ assert Specification.OPAQUE.id_of(thing) is None
1031
+
1032
+ @pytest.mark.parametrize("thing", THINGS)
1033
+ def test_no_subresources(self, thing):
1034
+ """
1035
+ An arbitrary thing has no subresources.
1036
+ """
1037
+
1038
+ assert list(Specification.OPAQUE.subresources_of(thing)) == []
1039
+
1040
+ @pytest.mark.parametrize("thing", THINGS)
1041
+ def test_no_anchors(self, thing):
1042
+ """
1043
+ An arbitrary thing has no anchors.
1044
+ """
1045
+
1046
+ assert list(Specification.OPAQUE.anchors_in(thing)) == []
1047
+
1048
+
1049
+ @pytest.mark.parametrize(
1050
+ "cls",
1051
+ [Anchor, Registry, Resource, Specification, exceptions.PointerToNowhere],
1052
+ )
1053
+ def test_nonsubclassable(cls):
1054
+ with pytest.raises(Exception, match="(?i)subclassing"):
1055
+
1056
+ class Boom(cls): # pragma: no cover
1057
+ pass
.venv/lib/python3.11/site-packages/referencing/tests/test_exceptions.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import itertools
2
+
3
+ import pytest
4
+
5
+ from referencing import Resource, exceptions
6
+
7
+
8
+ def pairs(choices):
9
+ return itertools.combinations(choices, 2)
10
+
11
+
12
+ TRUE = Resource.opaque(True)
13
+
14
+
15
+ thunks = (
16
+ lambda: exceptions.CannotDetermineSpecification(TRUE),
17
+ lambda: exceptions.NoSuchResource("urn:example:foo"),
18
+ lambda: exceptions.NoInternalID(TRUE),
19
+ lambda: exceptions.InvalidAnchor(resource=TRUE, anchor="foo", ref="a#b"),
20
+ lambda: exceptions.NoSuchAnchor(resource=TRUE, anchor="foo", ref="a#b"),
21
+ lambda: exceptions.PointerToNowhere(resource=TRUE, ref="urn:example:foo"),
22
+ lambda: exceptions.Unresolvable("urn:example:foo"),
23
+ lambda: exceptions.Unretrievable("urn:example:foo"),
24
+ )
25
+
26
+
27
+ @pytest.mark.parametrize("one, two", pairs(each() for each in thunks))
28
+ def test_eq_incompatible_types(one, two):
29
+ assert one != two
30
+
31
+
32
+ @pytest.mark.parametrize("thunk", thunks)
33
+ def test_hash(thunk):
34
+ assert thunk() in {thunk()}
.venv/lib/python3.11/site-packages/referencing/tests/test_jsonschema.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ from referencing import Registry, Resource, Specification
4
+ import referencing.jsonschema
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ "uri, expected",
9
+ [
10
+ (
11
+ "https://json-schema.org/draft/2020-12/schema",
12
+ referencing.jsonschema.DRAFT202012,
13
+ ),
14
+ (
15
+ "https://json-schema.org/draft/2019-09/schema",
16
+ referencing.jsonschema.DRAFT201909,
17
+ ),
18
+ (
19
+ "http://json-schema.org/draft-07/schema#",
20
+ referencing.jsonschema.DRAFT7,
21
+ ),
22
+ (
23
+ "http://json-schema.org/draft-06/schema#",
24
+ referencing.jsonschema.DRAFT6,
25
+ ),
26
+ (
27
+ "http://json-schema.org/draft-04/schema#",
28
+ referencing.jsonschema.DRAFT4,
29
+ ),
30
+ (
31
+ "http://json-schema.org/draft-03/schema#",
32
+ referencing.jsonschema.DRAFT3,
33
+ ),
34
+ ],
35
+ )
36
+ def test_schemas_with_explicit_schema_keywords_are_detected(uri, expected):
37
+ """
38
+ The $schema keyword in JSON Schema is a dialect identifier.
39
+ """
40
+ contents = {"$schema": uri}
41
+ resource = Resource.from_contents(contents)
42
+ assert resource == Resource(contents=contents, specification=expected)
43
+
44
+
45
+ def test_unknown_dialect():
46
+ dialect_id = "http://example.com/unknown-json-schema-dialect-id"
47
+ with pytest.raises(referencing.jsonschema.UnknownDialect) as excinfo:
48
+ Resource.from_contents({"$schema": dialect_id})
49
+ assert excinfo.value.uri == dialect_id
50
+
51
+
52
+ @pytest.mark.parametrize(
53
+ "id, specification",
54
+ [
55
+ ("$id", referencing.jsonschema.DRAFT202012),
56
+ ("$id", referencing.jsonschema.DRAFT201909),
57
+ ("$id", referencing.jsonschema.DRAFT7),
58
+ ("$id", referencing.jsonschema.DRAFT6),
59
+ ("id", referencing.jsonschema.DRAFT4),
60
+ ("id", referencing.jsonschema.DRAFT3),
61
+ ],
62
+ )
63
+ def test_id_of_mapping(id, specification):
64
+ uri = "http://example.com/some-schema"
65
+ assert specification.id_of({id: uri}) == uri
66
+
67
+
68
+ @pytest.mark.parametrize(
69
+ "specification",
70
+ [
71
+ referencing.jsonschema.DRAFT202012,
72
+ referencing.jsonschema.DRAFT201909,
73
+ referencing.jsonschema.DRAFT7,
74
+ referencing.jsonschema.DRAFT6,
75
+ ],
76
+ )
77
+ @pytest.mark.parametrize("value", [True, False])
78
+ def test_id_of_bool(specification, value):
79
+ assert specification.id_of(value) is None
80
+
81
+
82
+ @pytest.mark.parametrize(
83
+ "specification",
84
+ [
85
+ referencing.jsonschema.DRAFT202012,
86
+ referencing.jsonschema.DRAFT201909,
87
+ referencing.jsonschema.DRAFT7,
88
+ referencing.jsonschema.DRAFT6,
89
+ ],
90
+ )
91
+ @pytest.mark.parametrize("value", [True, False])
92
+ def test_anchors_in_bool(specification, value):
93
+ assert list(specification.anchors_in(value)) == []
94
+
95
+
96
+ @pytest.mark.parametrize(
97
+ "specification",
98
+ [
99
+ referencing.jsonschema.DRAFT202012,
100
+ referencing.jsonschema.DRAFT201909,
101
+ referencing.jsonschema.DRAFT7,
102
+ referencing.jsonschema.DRAFT6,
103
+ ],
104
+ )
105
+ @pytest.mark.parametrize("value", [True, False])
106
+ def test_subresources_of_bool(specification, value):
107
+ assert list(specification.subresources_of(value)) == []
108
+
109
+
110
+ @pytest.mark.parametrize(
111
+ "uri, expected",
112
+ [
113
+ (
114
+ "https://json-schema.org/draft/2020-12/schema",
115
+ referencing.jsonschema.DRAFT202012,
116
+ ),
117
+ (
118
+ "https://json-schema.org/draft/2019-09/schema",
119
+ referencing.jsonschema.DRAFT201909,
120
+ ),
121
+ (
122
+ "http://json-schema.org/draft-07/schema#",
123
+ referencing.jsonschema.DRAFT7,
124
+ ),
125
+ (
126
+ "http://json-schema.org/draft-06/schema#",
127
+ referencing.jsonschema.DRAFT6,
128
+ ),
129
+ (
130
+ "http://json-schema.org/draft-04/schema#",
131
+ referencing.jsonschema.DRAFT4,
132
+ ),
133
+ (
134
+ "http://json-schema.org/draft-03/schema#",
135
+ referencing.jsonschema.DRAFT3,
136
+ ),
137
+ ],
138
+ )
139
+ def test_specification_with(uri, expected):
140
+ assert referencing.jsonschema.specification_with(uri) == expected
141
+
142
+
143
+ @pytest.mark.parametrize(
144
+ "uri, expected",
145
+ [
146
+ (
147
+ "http://json-schema.org/draft-07/schema",
148
+ referencing.jsonschema.DRAFT7,
149
+ ),
150
+ (
151
+ "http://json-schema.org/draft-06/schema",
152
+ referencing.jsonschema.DRAFT6,
153
+ ),
154
+ (
155
+ "http://json-schema.org/draft-04/schema",
156
+ referencing.jsonschema.DRAFT4,
157
+ ),
158
+ (
159
+ "http://json-schema.org/draft-03/schema",
160
+ referencing.jsonschema.DRAFT3,
161
+ ),
162
+ ],
163
+ )
164
+ def test_specification_with_no_empty_fragment(uri, expected):
165
+ assert referencing.jsonschema.specification_with(uri) == expected
166
+
167
+
168
+ def test_specification_with_unknown_dialect():
169
+ dialect_id = "http://example.com/unknown-json-schema-dialect-id"
170
+ with pytest.raises(referencing.jsonschema.UnknownDialect) as excinfo:
171
+ referencing.jsonschema.specification_with(dialect_id)
172
+ assert excinfo.value.uri == dialect_id
173
+
174
+
175
+ def test_specification_with_default():
176
+ dialect_id = "http://example.com/unknown-json-schema-dialect-id"
177
+ specification = referencing.jsonschema.specification_with(
178
+ dialect_id,
179
+ default=Specification.OPAQUE,
180
+ )
181
+ assert specification is Specification.OPAQUE
182
+
183
+
184
+ # FIXME: The tests below should move to the referencing suite but I haven't yet
185
+ # figured out how to represent dynamic (& recursive) ref lookups in it.
186
+ def test_lookup_trivial_dynamic_ref():
187
+ one = referencing.jsonschema.DRAFT202012.create_resource(
188
+ {"$dynamicAnchor": "foo"},
189
+ )
190
+ resolver = Registry().with_resource("http://example.com", one).resolver()
191
+ resolved = resolver.lookup("http://example.com#foo")
192
+ assert resolved.contents == one.contents
193
+
194
+
195
+ def test_multiple_lookup_trivial_dynamic_ref():
196
+ TRUE = referencing.jsonschema.DRAFT202012.create_resource(True)
197
+ root = referencing.jsonschema.DRAFT202012.create_resource(
198
+ {
199
+ "$id": "http://example.com",
200
+ "$dynamicAnchor": "fooAnchor",
201
+ "$defs": {
202
+ "foo": {
203
+ "$id": "foo",
204
+ "$dynamicAnchor": "fooAnchor",
205
+ "$defs": {
206
+ "bar": True,
207
+ "baz": {
208
+ "$dynamicAnchor": "fooAnchor",
209
+ },
210
+ },
211
+ },
212
+ },
213
+ },
214
+ )
215
+ resolver = (
216
+ Registry()
217
+ .with_resources(
218
+ [
219
+ ("http://example.com", root),
220
+ ("http://example.com/foo/", TRUE),
221
+ ("http://example.com/foo/bar", root),
222
+ ],
223
+ )
224
+ .resolver()
225
+ )
226
+
227
+ first = resolver.lookup("http://example.com")
228
+ second = first.resolver.lookup("foo/")
229
+ resolver = second.resolver.lookup("bar").resolver
230
+ fourth = resolver.lookup("#fooAnchor")
231
+ assert fourth.contents == root.contents
232
+
233
+
234
+ def test_multiple_lookup_dynamic_ref_to_nondynamic_ref():
235
+ one = referencing.jsonschema.DRAFT202012.create_resource(
236
+ {"$anchor": "fooAnchor"},
237
+ )
238
+ two = referencing.jsonschema.DRAFT202012.create_resource(
239
+ {
240
+ "$id": "http://example.com",
241
+ "$dynamicAnchor": "fooAnchor",
242
+ "$defs": {
243
+ "foo": {
244
+ "$id": "foo",
245
+ "$dynamicAnchor": "fooAnchor",
246
+ "$defs": {
247
+ "bar": True,
248
+ "baz": {
249
+ "$dynamicAnchor": "fooAnchor",
250
+ },
251
+ },
252
+ },
253
+ },
254
+ },
255
+ )
256
+ resolver = (
257
+ Registry()
258
+ .with_resources(
259
+ [
260
+ ("http://example.com", two),
261
+ ("http://example.com/foo/", one),
262
+ ("http://example.com/foo/bar", two),
263
+ ],
264
+ )
265
+ .resolver()
266
+ )
267
+
268
+ first = resolver.lookup("http://example.com")
269
+ second = first.resolver.lookup("foo/")
270
+ resolver = second.resolver.lookup("bar").resolver
271
+ fourth = resolver.lookup("#fooAnchor")
272
+ assert fourth.contents == two.contents
273
+
274
+
275
+ def test_lookup_trivial_recursive_ref():
276
+ one = referencing.jsonschema.DRAFT201909.create_resource(
277
+ {"$recursiveAnchor": True},
278
+ )
279
+ resolver = Registry().with_resource("http://example.com", one).resolver()
280
+ first = resolver.lookup("http://example.com")
281
+ resolved = referencing.jsonschema.lookup_recursive_ref(
282
+ resolver=first.resolver,
283
+ )
284
+ assert resolved.contents == one.contents
285
+
286
+
287
+ def test_lookup_recursive_ref_to_bool():
288
+ TRUE = referencing.jsonschema.DRAFT201909.create_resource(True)
289
+ registry = Registry({"http://example.com": TRUE})
290
+ resolved = referencing.jsonschema.lookup_recursive_ref(
291
+ resolver=registry.resolver(base_uri="http://example.com"),
292
+ )
293
+ assert resolved.contents == TRUE.contents
294
+
295
+
296
+ def test_multiple_lookup_recursive_ref_to_bool():
297
+ TRUE = referencing.jsonschema.DRAFT201909.create_resource(True)
298
+ root = referencing.jsonschema.DRAFT201909.create_resource(
299
+ {
300
+ "$id": "http://example.com",
301
+ "$recursiveAnchor": True,
302
+ "$defs": {
303
+ "foo": {
304
+ "$id": "foo",
305
+ "$recursiveAnchor": True,
306
+ "$defs": {
307
+ "bar": True,
308
+ "baz": {
309
+ "$recursiveAnchor": True,
310
+ "$anchor": "fooAnchor",
311
+ },
312
+ },
313
+ },
314
+ },
315
+ },
316
+ )
317
+ resolver = (
318
+ Registry()
319
+ .with_resources(
320
+ [
321
+ ("http://example.com", root),
322
+ ("http://example.com/foo/", TRUE),
323
+ ("http://example.com/foo/bar", root),
324
+ ],
325
+ )
326
+ .resolver()
327
+ )
328
+
329
+ first = resolver.lookup("http://example.com")
330
+ second = first.resolver.lookup("foo/")
331
+ resolver = second.resolver.lookup("bar").resolver
332
+ fourth = referencing.jsonschema.lookup_recursive_ref(resolver=resolver)
333
+ assert fourth.contents == root.contents
334
+
335
+
336
+ def test_multiple_lookup_recursive_ref_with_nonrecursive_ref():
337
+ one = referencing.jsonschema.DRAFT201909.create_resource(
338
+ {"$recursiveAnchor": True},
339
+ )
340
+ two = referencing.jsonschema.DRAFT201909.create_resource(
341
+ {
342
+ "$id": "http://example.com",
343
+ "$recursiveAnchor": True,
344
+ "$defs": {
345
+ "foo": {
346
+ "$id": "foo",
347
+ "$recursiveAnchor": True,
348
+ "$defs": {
349
+ "bar": True,
350
+ "baz": {
351
+ "$recursiveAnchor": True,
352
+ "$anchor": "fooAnchor",
353
+ },
354
+ },
355
+ },
356
+ },
357
+ },
358
+ )
359
+ three = referencing.jsonschema.DRAFT201909.create_resource(
360
+ {"$recursiveAnchor": False},
361
+ )
362
+ resolver = (
363
+ Registry()
364
+ .with_resources(
365
+ [
366
+ ("http://example.com", three),
367
+ ("http://example.com/foo/", two),
368
+ ("http://example.com/foo/bar", one),
369
+ ],
370
+ )
371
+ .resolver()
372
+ )
373
+
374
+ first = resolver.lookup("http://example.com")
375
+ second = first.resolver.lookup("foo/")
376
+ resolver = second.resolver.lookup("bar").resolver
377
+ fourth = referencing.jsonschema.lookup_recursive_ref(resolver=resolver)
378
+ assert fourth.contents == two.contents
379
+
380
+
381
+ def test_empty_registry():
382
+ assert referencing.jsonschema.EMPTY_REGISTRY == Registry()
.venv/lib/python3.11/site-packages/referencing/tests/test_referencing_suite.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import json
3
+ import os
4
+
5
+ import pytest
6
+
7
+ from referencing import Registry
8
+ from referencing.exceptions import Unresolvable
9
+ import referencing.jsonschema
10
+
11
+
12
+ class SuiteNotFound(Exception):
13
+ def __str__(self): # pragma: no cover
14
+ return (
15
+ "Cannot find the referencing suite. "
16
+ "Set the REFERENCING_SUITE environment variable to the path to "
17
+ "the suite, or run the test suite from alongside a full checkout "
18
+ "of the git repository."
19
+ )
20
+
21
+
22
+ if "REFERENCING_SUITE" in os.environ: # pragma: no cover
23
+ SUITE = Path(os.environ["REFERENCING_SUITE"]) / "tests"
24
+ else:
25
+ SUITE = Path(__file__).parent.parent.parent / "suite/tests"
26
+ if not SUITE.is_dir(): # pragma: no cover
27
+ raise SuiteNotFound()
28
+ DIALECT_IDS = json.loads(SUITE.joinpath("specifications.json").read_text())
29
+
30
+
31
+ @pytest.mark.parametrize(
32
+ "test_path",
33
+ [
34
+ pytest.param(each, id=f"{each.parent.name}-{each.stem}")
35
+ for each in SUITE.glob("*/**/*.json")
36
+ ],
37
+ )
38
+ def test_referencing_suite(test_path, subtests):
39
+ dialect_id = DIALECT_IDS[test_path.relative_to(SUITE).parts[0]]
40
+ specification = referencing.jsonschema.specification_with(dialect_id)
41
+ loaded = json.loads(test_path.read_text())
42
+ registry = loaded["registry"]
43
+ registry = Registry().with_resources(
44
+ (uri, specification.create_resource(contents))
45
+ for uri, contents in loaded["registry"].items()
46
+ )
47
+ for test in loaded["tests"]:
48
+ with subtests.test(test=test):
49
+ if "normalization" in test_path.stem:
50
+ pytest.xfail("APIs need to change for proper URL support.")
51
+
52
+ resolver = registry.resolver(base_uri=test.get("base_uri", ""))
53
+
54
+ if test.get("error"):
55
+ with pytest.raises(Unresolvable):
56
+ resolver.lookup(test["ref"])
57
+ else:
58
+ resolved = resolver.lookup(test["ref"])
59
+ assert resolved.contents == test["target"]
60
+
61
+ then = test.get("then")
62
+ while then: # pragma: no cover
63
+ with subtests.test(test=test, then=then):
64
+ resolved = resolved.resolver.lookup(then["ref"])
65
+ assert resolved.contents == then["target"]
66
+ then = then.get("then")
.venv/lib/python3.11/site-packages/referencing/tests/test_retrieval.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import lru_cache
2
+ import json
3
+
4
+ import pytest
5
+
6
+ from referencing import Registry, Resource, exceptions
7
+ from referencing.jsonschema import DRAFT202012
8
+ from referencing.retrieval import to_cached_resource
9
+
10
+
11
+ class TestToCachedResource:
12
+ def test_it_caches_retrieved_resources(self):
13
+ contents = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
14
+ stack = [json.dumps(contents)]
15
+
16
+ @to_cached_resource()
17
+ def retrieve(uri):
18
+ return stack.pop()
19
+
20
+ registry = Registry(retrieve=retrieve)
21
+
22
+ expected = Resource.from_contents(contents)
23
+
24
+ got = registry.get_or_retrieve("urn:example:schema")
25
+ assert got.value == expected
26
+
27
+ # And a second time we get the same value.
28
+ again = registry.get_or_retrieve("urn:example:schema")
29
+ assert again.value is got.value
30
+
31
+ def test_custom_loader(self):
32
+ contents = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
33
+ stack = [json.dumps(contents)[::-1]]
34
+
35
+ @to_cached_resource(loads=lambda s: json.loads(s[::-1]))
36
+ def retrieve(uri):
37
+ return stack.pop()
38
+
39
+ registry = Registry(retrieve=retrieve)
40
+
41
+ expected = Resource.from_contents(contents)
42
+
43
+ got = registry.get_or_retrieve("urn:example:schema")
44
+ assert got.value == expected
45
+
46
+ # And a second time we get the same value.
47
+ again = registry.get_or_retrieve("urn:example:schema")
48
+ assert again.value is got.value
49
+
50
+ def test_custom_from_contents(self):
51
+ contents = {}
52
+ stack = [json.dumps(contents)]
53
+
54
+ @to_cached_resource(from_contents=DRAFT202012.create_resource)
55
+ def retrieve(uri):
56
+ return stack.pop()
57
+
58
+ registry = Registry(retrieve=retrieve)
59
+
60
+ expected = DRAFT202012.create_resource(contents)
61
+
62
+ got = registry.get_or_retrieve("urn:example:schema")
63
+ assert got.value == expected
64
+
65
+ # And a second time we get the same value.
66
+ again = registry.get_or_retrieve("urn:example:schema")
67
+ assert again.value is got.value
68
+
69
+ def test_custom_cache(self):
70
+ schema = {"$schema": "https://json-schema.org/draft/2020-12/schema"}
71
+ mapping = {
72
+ "urn:example:1": dict(schema, foo=1),
73
+ "urn:example:2": dict(schema, foo=2),
74
+ "urn:example:3": dict(schema, foo=3),
75
+ }
76
+
77
+ resources = {
78
+ uri: Resource.from_contents(contents)
79
+ for uri, contents in mapping.items()
80
+ }
81
+
82
+ @to_cached_resource(cache=lru_cache(maxsize=2))
83
+ def retrieve(uri):
84
+ return json.dumps(mapping.pop(uri))
85
+
86
+ registry = Registry(retrieve=retrieve)
87
+
88
+ got = registry.get_or_retrieve("urn:example:1")
89
+ assert got.value == resources["urn:example:1"]
90
+ assert registry.get_or_retrieve("urn:example:1").value is got.value
91
+ assert registry.get_or_retrieve("urn:example:1").value is got.value
92
+
93
+ got = registry.get_or_retrieve("urn:example:2")
94
+ assert got.value == resources["urn:example:2"]
95
+ assert registry.get_or_retrieve("urn:example:2").value is got.value
96
+ assert registry.get_or_retrieve("urn:example:2").value is got.value
97
+
98
+ # This still succeeds, but evicts the first URI
99
+ got = registry.get_or_retrieve("urn:example:3")
100
+ assert got.value == resources["urn:example:3"]
101
+ assert registry.get_or_retrieve("urn:example:3").value is got.value
102
+ assert registry.get_or_retrieve("urn:example:3").value is got.value
103
+
104
+ # And now this fails (as we popped the value out of `mapping`)
105
+ with pytest.raises(exceptions.Unretrievable):
106
+ registry.get_or_retrieve("urn:example:1")
.venv/lib/python3.11/site-packages/referencing/typing.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Type-annotation related support for the referencing library.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from collections.abc import Mapping as Mapping
8
+ from typing import TYPE_CHECKING, Any, Protocol
9
+
10
+ try:
11
+ from typing_extensions import TypeVar
12
+ except ImportError: # pragma: no cover
13
+ from typing import TypeVar
14
+
15
+ if TYPE_CHECKING:
16
+ from referencing._core import Resolved, Resolver, Resource
17
+
18
+ #: A URI which identifies a `Resource`.
19
+ URI = str
20
+
21
+ #: The type of documents within a registry.
22
+ D = TypeVar("D", default=Any)
23
+
24
+
25
+ class Retrieve(Protocol[D]):
26
+ """
27
+ A retrieval callable, usable within a `Registry` for resource retrieval.
28
+
29
+ Does not make assumptions about where the resource might be coming from.
30
+ """
31
+
32
+ def __call__(self, uri: URI) -> Resource[D]:
33
+ """
34
+ Retrieve the resource with the given URI.
35
+
36
+ Raise `referencing.exceptions.NoSuchResource` if you wish to indicate
37
+ the retriever cannot lookup the given URI.
38
+ """
39
+ ...
40
+
41
+
42
+ class Anchor(Protocol[D]):
43
+ """
44
+ An anchor within a `Resource`.
45
+
46
+ Beyond "simple" anchors, some specifications like JSON Schema's 2020
47
+ version have dynamic anchors.
48
+ """
49
+
50
+ @property
51
+ def name(self) -> str:
52
+ """
53
+ Return the name of this anchor.
54
+ """
55
+ ...
56
+
57
+ def resolve(self, resolver: Resolver[D]) -> Resolved[D]:
58
+ """
59
+ Return the resource for this anchor.
60
+ """
61
+ ...
.venv/lib/python3.11/site-packages/starlette/_exception_handler.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
5
+ from starlette._utils import is_async_callable
6
+ from starlette.concurrency import run_in_threadpool
7
+ from starlette.exceptions import HTTPException
8
+ from starlette.requests import Request
9
+ from starlette.types import ASGIApp, ExceptionHandler, Message, Receive, Scope, Send
10
+ from starlette.websockets import WebSocket
11
+
12
+ ExceptionHandlers = dict[typing.Any, ExceptionHandler]
13
+ StatusHandlers = dict[int, ExceptionHandler]
14
+
15
+
16
+ def _lookup_exception_handler(exc_handlers: ExceptionHandlers, exc: Exception) -> ExceptionHandler | None:
17
+ for cls in type(exc).__mro__:
18
+ if cls in exc_handlers:
19
+ return exc_handlers[cls]
20
+ return None
21
+
22
+
23
+ def wrap_app_handling_exceptions(app: ASGIApp, conn: Request | WebSocket) -> ASGIApp:
24
+ exception_handlers: ExceptionHandlers
25
+ status_handlers: StatusHandlers
26
+ try:
27
+ exception_handlers, status_handlers = conn.scope["starlette.exception_handlers"]
28
+ except KeyError:
29
+ exception_handlers, status_handlers = {}, {}
30
+
31
+ async def wrapped_app(scope: Scope, receive: Receive, send: Send) -> None:
32
+ response_started = False
33
+
34
+ async def sender(message: Message) -> None:
35
+ nonlocal response_started
36
+
37
+ if message["type"] == "http.response.start":
38
+ response_started = True
39
+ await send(message)
40
+
41
+ try:
42
+ await app(scope, receive, sender)
43
+ except Exception as exc:
44
+ handler = None
45
+
46
+ if isinstance(exc, HTTPException):
47
+ handler = status_handlers.get(exc.status_code)
48
+
49
+ if handler is None:
50
+ handler = _lookup_exception_handler(exception_handlers, exc)
51
+
52
+ if handler is None:
53
+ raise exc
54
+
55
+ if response_started:
56
+ raise RuntimeError("Caught handled exception, but response already started.") from exc
57
+
58
+ if is_async_callable(handler):
59
+ response = await handler(conn, exc)
60
+ else:
61
+ response = await run_in_threadpool(handler, conn, exc) # type: ignore
62
+ if response is not None:
63
+ await response(scope, receive, sender)
64
+
65
+ return wrapped_app
.venv/lib/python3.11/site-packages/starlette/applications.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import typing
5
+ import warnings
6
+
7
+ if sys.version_info >= (3, 10): # pragma: no cover
8
+ from typing import ParamSpec
9
+ else: # pragma: no cover
10
+ from typing_extensions import ParamSpec
11
+
12
+ from starlette.datastructures import State, URLPath
13
+ from starlette.middleware import Middleware, _MiddlewareFactory
14
+ from starlette.middleware.base import BaseHTTPMiddleware
15
+ from starlette.middleware.errors import ServerErrorMiddleware
16
+ from starlette.middleware.exceptions import ExceptionMiddleware
17
+ from starlette.requests import Request
18
+ from starlette.responses import Response
19
+ from starlette.routing import BaseRoute, Router
20
+ from starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send
21
+ from starlette.websockets import WebSocket
22
+
23
+ AppType = typing.TypeVar("AppType", bound="Starlette")
24
+ P = ParamSpec("P")
25
+
26
+
27
+ class Starlette:
28
+ """Creates an Starlette application."""
29
+
30
+ def __init__(
31
+ self: AppType,
32
+ debug: bool = False,
33
+ routes: typing.Sequence[BaseRoute] | None = None,
34
+ middleware: typing.Sequence[Middleware] | None = None,
35
+ exception_handlers: typing.Mapping[typing.Any, ExceptionHandler] | None = None,
36
+ on_startup: typing.Sequence[typing.Callable[[], typing.Any]] | None = None,
37
+ on_shutdown: typing.Sequence[typing.Callable[[], typing.Any]] | None = None,
38
+ lifespan: Lifespan[AppType] | None = None,
39
+ ) -> None:
40
+ """Initializes the application.
41
+
42
+ Parameters:
43
+ debug: Boolean indicating if debug tracebacks should be returned on errors.
44
+ routes: A list of routes to serve incoming HTTP and WebSocket requests.
45
+ middleware: A list of middleware to run for every request. A starlette
46
+ application will always automatically include two middleware classes.
47
+ `ServerErrorMiddleware` is added as the very outermost middleware, to handle
48
+ any uncaught errors occurring anywhere in the entire stack.
49
+ `ExceptionMiddleware` is added as the very innermost middleware, to deal
50
+ with handled exception cases occurring in the routing or endpoints.
51
+ exception_handlers: A mapping of either integer status codes,
52
+ or exception class types onto callables which handle the exceptions.
53
+ Exception handler callables should be of the form
54
+ `handler(request, exc) -> response` and may be either standard functions, or
55
+ async functions.
56
+ on_startup: A list of callables to run on application startup.
57
+ Startup handler callables do not take any arguments, and may be either
58
+ standard functions, or async functions.
59
+ on_shutdown: A list of callables to run on application shutdown.
60
+ Shutdown handler callables do not take any arguments, and may be either
61
+ standard functions, or async functions.
62
+ lifespan: A lifespan context function, which can be used to perform
63
+ startup and shutdown tasks. This is a newer style that replaces the
64
+ `on_startup` and `on_shutdown` handlers. Use one or the other, not both.
65
+ """
66
+ # The lifespan context function is a newer style that replaces
67
+ # on_startup / on_shutdown handlers. Use one or the other, not both.
68
+ assert lifespan is None or (
69
+ on_startup is None and on_shutdown is None
70
+ ), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both."
71
+
72
+ self.debug = debug
73
+ self.state = State()
74
+ self.router = Router(routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan)
75
+ self.exception_handlers = {} if exception_handlers is None else dict(exception_handlers)
76
+ self.user_middleware = [] if middleware is None else list(middleware)
77
+ self.middleware_stack: ASGIApp | None = None
78
+
79
+ def build_middleware_stack(self) -> ASGIApp:
80
+ debug = self.debug
81
+ error_handler = None
82
+ exception_handlers: dict[typing.Any, typing.Callable[[Request, Exception], Response]] = {}
83
+
84
+ for key, value in self.exception_handlers.items():
85
+ if key in (500, Exception):
86
+ error_handler = value
87
+ else:
88
+ exception_handlers[key] = value
89
+
90
+ middleware = (
91
+ [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
92
+ + self.user_middleware
93
+ + [Middleware(ExceptionMiddleware, handlers=exception_handlers, debug=debug)]
94
+ )
95
+
96
+ app = self.router
97
+ for cls, args, kwargs in reversed(middleware):
98
+ app = cls(app, *args, **kwargs)
99
+ return app
100
+
101
+ @property
102
+ def routes(self) -> list[BaseRoute]:
103
+ return self.router.routes
104
+
105
+ def url_path_for(self, name: str, /, **path_params: typing.Any) -> URLPath:
106
+ return self.router.url_path_for(name, **path_params)
107
+
108
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
109
+ scope["app"] = self
110
+ if self.middleware_stack is None:
111
+ self.middleware_stack = self.build_middleware_stack()
112
+ await self.middleware_stack(scope, receive, send)
113
+
114
+ def on_event(self, event_type: str) -> typing.Callable: # type: ignore[type-arg]
115
+ return self.router.on_event(event_type) # pragma: no cover
116
+
117
+ def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
118
+ self.router.mount(path, app=app, name=name) # pragma: no cover
119
+
120
+ def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:
121
+ self.router.host(host, app=app, name=name) # pragma: no cover
122
+
123
+ def add_middleware(
124
+ self,
125
+ middleware_class: _MiddlewareFactory[P],
126
+ *args: P.args,
127
+ **kwargs: P.kwargs,
128
+ ) -> None:
129
+ if self.middleware_stack is not None: # pragma: no cover
130
+ raise RuntimeError("Cannot add middleware after an application has started")
131
+ self.user_middleware.insert(0, Middleware(middleware_class, *args, **kwargs))
132
+
133
+ def add_exception_handler(
134
+ self,
135
+ exc_class_or_status_code: int | type[Exception],
136
+ handler: ExceptionHandler,
137
+ ) -> None: # pragma: no cover
138
+ self.exception_handlers[exc_class_or_status_code] = handler
139
+
140
+ def add_event_handler(
141
+ self,
142
+ event_type: str,
143
+ func: typing.Callable, # type: ignore[type-arg]
144
+ ) -> None: # pragma: no cover
145
+ self.router.add_event_handler(event_type, func)
146
+
147
+ def add_route(
148
+ self,
149
+ path: str,
150
+ route: typing.Callable[[Request], typing.Awaitable[Response] | Response],
151
+ methods: list[str] | None = None,
152
+ name: str | None = None,
153
+ include_in_schema: bool = True,
154
+ ) -> None: # pragma: no cover
155
+ self.router.add_route(path, route, methods=methods, name=name, include_in_schema=include_in_schema)
156
+
157
+ def add_websocket_route(
158
+ self,
159
+ path: str,
160
+ route: typing.Callable[[WebSocket], typing.Awaitable[None]],
161
+ name: str | None = None,
162
+ ) -> None: # pragma: no cover
163
+ self.router.add_websocket_route(path, route, name=name)
164
+
165
+ def exception_handler(self, exc_class_or_status_code: int | type[Exception]) -> typing.Callable: # type: ignore[type-arg]
166
+ warnings.warn(
167
+ "The `exception_handler` decorator is deprecated, and will be removed in version 1.0.0. "
168
+ "Refer to https://www.starlette.io/exceptions/ for the recommended approach.",
169
+ DeprecationWarning,
170
+ )
171
+
172
+ def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg]
173
+ self.add_exception_handler(exc_class_or_status_code, func)
174
+ return func
175
+
176
+ return decorator
177
+
178
+ def route(
179
+ self,
180
+ path: str,
181
+ methods: list[str] | None = None,
182
+ name: str | None = None,
183
+ include_in_schema: bool = True,
184
+ ) -> typing.Callable: # type: ignore[type-arg]
185
+ """
186
+ We no longer document this decorator style API, and its usage is discouraged.
187
+ Instead you should use the following approach:
188
+
189
+ >>> routes = [Route(path, endpoint=...), ...]
190
+ >>> app = Starlette(routes=routes)
191
+ """
192
+ warnings.warn(
193
+ "The `route` decorator is deprecated, and will be removed in version 1.0.0. "
194
+ "Refer to https://www.starlette.io/routing/ for the recommended approach.",
195
+ DeprecationWarning,
196
+ )
197
+
198
+ def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg]
199
+ self.router.add_route(
200
+ path,
201
+ func,
202
+ methods=methods,
203
+ name=name,
204
+ include_in_schema=include_in_schema,
205
+ )
206
+ return func
207
+
208
+ return decorator
209
+
210
+ def websocket_route(self, path: str, name: str | None = None) -> typing.Callable: # type: ignore[type-arg]
211
+ """
212
+ We no longer document this decorator style API, and its usage is discouraged.
213
+ Instead you should use the following approach:
214
+
215
+ >>> routes = [WebSocketRoute(path, endpoint=...), ...]
216
+ >>> app = Starlette(routes=routes)
217
+ """
218
+ warnings.warn(
219
+ "The `websocket_route` decorator is deprecated, and will be removed in version 1.0.0. "
220
+ "Refer to https://www.starlette.io/routing/#websocket-routing for the recommended approach.",
221
+ DeprecationWarning,
222
+ )
223
+
224
+ def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg]
225
+ self.router.add_websocket_route(path, func, name=name)
226
+ return func
227
+
228
+ return decorator
229
+
230
+ def middleware(self, middleware_type: str) -> typing.Callable: # type: ignore[type-arg]
231
+ """
232
+ We no longer document this decorator style API, and its usage is discouraged.
233
+ Instead you should use the following approach:
234
+
235
+ >>> middleware = [Middleware(...), ...]
236
+ >>> app = Starlette(middleware=middleware)
237
+ """
238
+ warnings.warn(
239
+ "The `middleware` decorator is deprecated, and will be removed in version 1.0.0. "
240
+ "Refer to https://www.starlette.io/middleware/#using-middleware for recommended approach.",
241
+ DeprecationWarning,
242
+ )
243
+ assert middleware_type == "http", 'Currently only middleware("http") is supported.'
244
+
245
+ def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg]
246
+ self.add_middleware(BaseHTTPMiddleware, dispatch=func)
247
+ return func
248
+
249
+ return decorator
.venv/lib/python3.11/site-packages/starlette/concurrency.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import sys
5
+ import typing
6
+ import warnings
7
+
8
+ import anyio.to_thread
9
+
10
+ if sys.version_info >= (3, 10): # pragma: no cover
11
+ from typing import ParamSpec
12
+ else: # pragma: no cover
13
+ from typing_extensions import ParamSpec
14
+
15
+ P = ParamSpec("P")
16
+ T = typing.TypeVar("T")
17
+
18
+
19
+ async def run_until_first_complete(*args: tuple[typing.Callable, dict]) -> None: # type: ignore[type-arg]
20
+ warnings.warn(
21
+ "run_until_first_complete is deprecated and will be removed in a future version.",
22
+ DeprecationWarning,
23
+ )
24
+
25
+ async with anyio.create_task_group() as task_group:
26
+
27
+ async def run(func: typing.Callable[[], typing.Coroutine]) -> None: # type: ignore[type-arg]
28
+ await func()
29
+ task_group.cancel_scope.cancel()
30
+
31
+ for func, kwargs in args:
32
+ task_group.start_soon(run, functools.partial(func, **kwargs))
33
+
34
+
35
+ async def run_in_threadpool(func: typing.Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
36
+ func = functools.partial(func, *args, **kwargs)
37
+ return await anyio.to_thread.run_sync(func)
38
+
39
+
40
+ class _StopIteration(Exception):
41
+ pass
42
+
43
+
44
+ def _next(iterator: typing.Iterator[T]) -> T:
45
+ # We can't raise `StopIteration` from within the threadpool iterator
46
+ # and catch it outside that context, so we coerce them into a different
47
+ # exception type.
48
+ try:
49
+ return next(iterator)
50
+ except StopIteration:
51
+ raise _StopIteration
52
+
53
+
54
+ async def iterate_in_threadpool(
55
+ iterator: typing.Iterable[T],
56
+ ) -> typing.AsyncIterator[T]:
57
+ as_iterator = iter(iterator)
58
+ while True:
59
+ try:
60
+ yield await anyio.to_thread.run_sync(_next, as_iterator)
61
+ except _StopIteration:
62
+ break