jsonschema-path 0.4.2__tar.gz → 0.4.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/PKG-INFO +18 -2
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/README.rst +17 -1
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/__init__.py +1 -1
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/accessors.py +32 -51
- jsonschema_path-0.4.4/jsonschema_path/caches.py +108 -0
- jsonschema_path-0.4.4/jsonschema_path/nodes.py +29 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/paths.py +21 -3
- jsonschema_path-0.4.4/jsonschema_path/resolvers.py +91 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/pyproject.toml +2 -2
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/LICENSE +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/handlers/__init__.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/handlers/file.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/handlers/protocols.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/handlers/requests.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/handlers/urllib.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/handlers/utils.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/loaders.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/py.typed +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/readers.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/retrievers.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/typing.py +0 -0
- {jsonschema_path-0.4.2 → jsonschema_path-0.4.4}/jsonschema_path/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jsonschema-path
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
4
4
|
Summary: JSONSchema Spec with object-oriented paths
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -120,6 +120,21 @@ Usage
|
|
|
120
120
|
{'type': 'string', 'default': '1.0'}
|
|
121
121
|
|
|
122
122
|
|
|
123
|
+
Resolved cache
|
|
124
|
+
##############
|
|
125
|
+
|
|
126
|
+
The resolved-path cache is intended for repeated path lookups and may significantly improve
|
|
127
|
+
``read_value``/membership hot paths. Cache entries are invalidated when the
|
|
128
|
+
resolver registry evolves during reference resolution.
|
|
129
|
+
|
|
130
|
+
This cache is optional and disabled by default
|
|
131
|
+
(``resolved_cache_maxsize=0``). You can enable it when creating paths or
|
|
132
|
+
accessors, for example:
|
|
133
|
+
|
|
134
|
+
.. code-block:: python
|
|
135
|
+
|
|
136
|
+
>>> path = SchemaPath.from_dict(d, resolved_cache_maxsize=64)
|
|
137
|
+
|
|
123
138
|
Benchmarks
|
|
124
139
|
##########
|
|
125
140
|
|
|
@@ -140,12 +155,13 @@ For a quick smoke run:
|
|
|
140
155
|
poetry run python -m tests.benchmarks.bench_parse --output reports/bench-parse.quick.json --quick
|
|
141
156
|
poetry run python -m tests.benchmarks.bench_lookup --output reports/bench-lookup.quick.json --quick
|
|
142
157
|
|
|
143
|
-
You can also control repeats/warmup via env vars:
|
|
158
|
+
You can also control repeats/warmup and resolved cache maxsize via env vars:
|
|
144
159
|
|
|
145
160
|
.. code-block:: console
|
|
146
161
|
|
|
147
162
|
export JSONSCHEMA_PATH_BENCH_REPEATS=5
|
|
148
163
|
export JSONSCHEMA_PATH_BENCH_WARMUP=1
|
|
164
|
+
export JSONSCHEMA_PATH_BENCH_RESOLVED_CACHE_MAXSIZE=64
|
|
149
165
|
|
|
150
166
|
Compare two results:
|
|
151
167
|
|
|
@@ -90,6 +90,21 @@ Usage
|
|
|
90
90
|
{'type': 'string', 'default': '1.0'}
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
Resolved cache
|
|
94
|
+
##############
|
|
95
|
+
|
|
96
|
+
The resolved-path cache is intended for repeated path lookups and may significantly improve
|
|
97
|
+
``read_value``/membership hot paths. Cache entries are invalidated when the
|
|
98
|
+
resolver registry evolves during reference resolution.
|
|
99
|
+
|
|
100
|
+
This cache is optional and disabled by default
|
|
101
|
+
(``resolved_cache_maxsize=0``). You can enable it when creating paths or
|
|
102
|
+
accessors, for example:
|
|
103
|
+
|
|
104
|
+
.. code-block:: python
|
|
105
|
+
|
|
106
|
+
>>> path = SchemaPath.from_dict(d, resolved_cache_maxsize=64)
|
|
107
|
+
|
|
93
108
|
Benchmarks
|
|
94
109
|
##########
|
|
95
110
|
|
|
@@ -110,12 +125,13 @@ For a quick smoke run:
|
|
|
110
125
|
poetry run python -m tests.benchmarks.bench_parse --output reports/bench-parse.quick.json --quick
|
|
111
126
|
poetry run python -m tests.benchmarks.bench_lookup --output reports/bench-lookup.quick.json --quick
|
|
112
127
|
|
|
113
|
-
You can also control repeats/warmup via env vars:
|
|
128
|
+
You can also control repeats/warmup and resolved cache maxsize via env vars:
|
|
114
129
|
|
|
115
130
|
.. code-block:: console
|
|
116
131
|
|
|
117
132
|
export JSONSCHEMA_PATH_BENCH_REPEATS=5
|
|
118
133
|
export JSONSCHEMA_PATH_BENCH_WARMUP=1
|
|
134
|
+
export JSONSCHEMA_PATH_BENCH_RESOLVED_CACHE_MAXSIZE=64
|
|
119
135
|
|
|
120
136
|
Compare two results:
|
|
121
137
|
|
|
@@ -17,21 +17,32 @@ from referencing._core import Resolved
|
|
|
17
17
|
from referencing._core import Resolver
|
|
18
18
|
from referencing.jsonschema import DRAFT202012
|
|
19
19
|
|
|
20
|
+
from jsonschema_path.caches import FullPathResolvedCache
|
|
20
21
|
from jsonschema_path.handlers import default_handlers
|
|
22
|
+
from jsonschema_path.resolvers import CachedPathResolver
|
|
21
23
|
from jsonschema_path.retrievers import SchemaRetriever
|
|
22
24
|
from jsonschema_path.typing import ResolverHandlers
|
|
23
25
|
from jsonschema_path.typing import Schema
|
|
24
|
-
from jsonschema_path.utils import is_ref
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class SchemaAccessor(LookupAccessor):
|
|
28
|
-
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
schema: Schema,
|
|
32
|
+
resolver: Resolver[Schema],
|
|
33
|
+
resolved_cache_maxsize: int = 0,
|
|
34
|
+
):
|
|
35
|
+
if resolved_cache_maxsize < 0:
|
|
36
|
+
raise ValueError("resolved_cache_maxsize must be >= 0")
|
|
29
37
|
|
|
30
|
-
def __init__(self, schema: Schema, resolver: Resolver[Schema]):
|
|
31
38
|
super().__init__(cast(LookupNode, schema))
|
|
32
|
-
self.
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
self._path_resolver: CachedPathResolver = CachedPathResolver(
|
|
40
|
+
resolver,
|
|
41
|
+
)
|
|
42
|
+
self._resolved_cache_maxsize = resolved_cache_maxsize
|
|
43
|
+
self._resolved_cache: FullPathResolvedCache = FullPathResolvedCache(
|
|
44
|
+
maxsize=resolved_cache_maxsize
|
|
45
|
+
)
|
|
35
46
|
|
|
36
47
|
@classmethod
|
|
37
48
|
def from_schema(
|
|
@@ -40,6 +51,7 @@ class SchemaAccessor(LookupAccessor):
|
|
|
40
51
|
specification: Specification[Schema] = DRAFT202012,
|
|
41
52
|
base_uri: str = "",
|
|
42
53
|
handlers: ResolverHandlers | None = None,
|
|
54
|
+
resolved_cache_maxsize: int = 0,
|
|
43
55
|
) -> "SchemaAccessor":
|
|
44
56
|
if handlers is None:
|
|
45
57
|
handlers = default_handlers
|
|
@@ -50,7 +62,11 @@ class SchemaAccessor(LookupAccessor):
|
|
|
50
62
|
)
|
|
51
63
|
registry = registry.with_resource(base_uri, base_resource)
|
|
52
64
|
resolver = registry.resolver(base_uri=base_uri)
|
|
53
|
-
return cls(
|
|
65
|
+
return cls(
|
|
66
|
+
schema,
|
|
67
|
+
resolver,
|
|
68
|
+
resolved_cache_maxsize=resolved_cache_maxsize,
|
|
69
|
+
)
|
|
54
70
|
|
|
55
71
|
def __getitem__(self, parts: Sequence[LookupKey]) -> LookupNode:
|
|
56
72
|
resolved = self.get_resolved(parts)
|
|
@@ -143,49 +159,14 @@ class SchemaAccessor(LookupAccessor):
|
|
|
143
159
|
pass
|
|
144
160
|
|
|
145
161
|
def get_resolved(self, parts: Sequence[LookupKey]) -> Resolved[LookupNode]:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
registry=resolved.resolver._registry,
|
|
150
|
-
)
|
|
151
|
-
return resolved
|
|
162
|
+
cached_resolved = self._resolved_cache.get(parts)
|
|
163
|
+
if cached_resolved is not None:
|
|
164
|
+
return cached_resolved
|
|
152
165
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
node: LookupNode,
|
|
157
|
-
parts: Sequence[LookupKey],
|
|
158
|
-
resolver: Resolver[Schema] | None = None,
|
|
159
|
-
) -> Resolved[LookupNode]:
|
|
160
|
-
if resolver is None:
|
|
161
|
-
raise ValueError("resolver must be provided")
|
|
162
|
-
|
|
163
|
-
current_node: LookupNode = node
|
|
164
|
-
current_resolver: Resolver[Schema] = resolver
|
|
165
|
-
|
|
166
|
-
for part in parts:
|
|
167
|
-
resolved = cls._resolve_node(current_node, current_resolver)
|
|
168
|
-
current_node, current_resolver = (
|
|
169
|
-
resolved.contents,
|
|
170
|
-
resolved.resolver,
|
|
171
|
-
)
|
|
172
|
-
current_node = cls._get_subnode(current_node, part)
|
|
173
|
-
|
|
174
|
-
resolved = cls._resolve_node(current_node, current_resolver)
|
|
175
|
-
return cast(Resolved[LookupNode], resolved)
|
|
166
|
+
result = self._path_resolver.resolve(self.node, parts)
|
|
167
|
+
if result.registry_changed:
|
|
168
|
+
self._resolved_cache.invalidate()
|
|
176
169
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
node: LookupNode,
|
|
181
|
-
resolver: Resolver[Schema],
|
|
182
|
-
) -> Resolved[Schema]:
|
|
183
|
-
if is_ref(node):
|
|
184
|
-
ref_node = cls._get_subnode(node, "$ref")
|
|
185
|
-
ref = cls._read_node(ref_node)
|
|
186
|
-
resolved = resolver.lookup(ref)
|
|
187
|
-
return cls._resolve_node(
|
|
188
|
-
resolved.contents,
|
|
189
|
-
resolved.resolver,
|
|
190
|
-
)
|
|
191
|
-
return Resolved(cast(Schema, node), resolver) # type: ignore
|
|
170
|
+
self._resolved_cache.set(parts, result.resolved)
|
|
171
|
+
|
|
172
|
+
return result.resolved
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""JSONSchema path caches module."""
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
|
|
6
|
+
from pathable.types import LookupKey
|
|
7
|
+
from pathable.types import LookupNode
|
|
8
|
+
from referencing._core import Resolved
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FullPathResolvedCache:
|
|
12
|
+
def __init__(self, maxsize: int):
|
|
13
|
+
self._maxsize = maxsize
|
|
14
|
+
self._generation = 0
|
|
15
|
+
self._cache: OrderedDict[
|
|
16
|
+
tuple[tuple[LookupKey, ...], int],
|
|
17
|
+
Resolved[LookupNode],
|
|
18
|
+
] = OrderedDict()
|
|
19
|
+
|
|
20
|
+
def _make_key(
|
|
21
|
+
self,
|
|
22
|
+
parts: Sequence[LookupKey],
|
|
23
|
+
) -> tuple[tuple[LookupKey, ...], int] | None:
|
|
24
|
+
if self._maxsize <= 0:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
parts_tuple = tuple(parts)
|
|
28
|
+
try:
|
|
29
|
+
hash(parts_tuple)
|
|
30
|
+
except TypeError:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
return (parts_tuple, self._generation)
|
|
34
|
+
|
|
35
|
+
def get(
|
|
36
|
+
self,
|
|
37
|
+
parts: Sequence[LookupKey],
|
|
38
|
+
) -> Resolved[LookupNode] | None:
|
|
39
|
+
key = self._make_key(parts)
|
|
40
|
+
if key is None:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
cached = self._cache.get(key)
|
|
44
|
+
if cached is None:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
self._cache.move_to_end(key)
|
|
48
|
+
return cached
|
|
49
|
+
|
|
50
|
+
def set(
|
|
51
|
+
self,
|
|
52
|
+
parts: Sequence[LookupKey],
|
|
53
|
+
resolved: Resolved[LookupNode],
|
|
54
|
+
) -> None:
|
|
55
|
+
key = self._make_key(parts)
|
|
56
|
+
if key is None:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
self._cache[key] = resolved
|
|
60
|
+
self._cache.move_to_end(key)
|
|
61
|
+
if len(self._cache) > self._maxsize:
|
|
62
|
+
self._cache.popitem(last=False)
|
|
63
|
+
|
|
64
|
+
def invalidate(self) -> None:
|
|
65
|
+
self._generation += 1
|
|
66
|
+
self._cache.clear()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class PrefixResolvedCache:
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
self._cache: dict[tuple[LookupKey, ...], Resolved[LookupNode]] = {}
|
|
72
|
+
|
|
73
|
+
def seed_root(self, resolved: Resolved[LookupNode]) -> None:
|
|
74
|
+
self._cache[()] = resolved
|
|
75
|
+
|
|
76
|
+
def longest_prefix_hit(
|
|
77
|
+
self,
|
|
78
|
+
parts: tuple[LookupKey, ...],
|
|
79
|
+
) -> tuple[int, Resolved[LookupNode]] | None:
|
|
80
|
+
for idx in range(len(parts) - 1, -1, -1):
|
|
81
|
+
prefix = parts[:idx]
|
|
82
|
+
try:
|
|
83
|
+
cached = self._cache.get(prefix)
|
|
84
|
+
except TypeError:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
if cached is not None:
|
|
88
|
+
return idx, cached
|
|
89
|
+
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
def store_intermediate(
|
|
93
|
+
self,
|
|
94
|
+
parts: tuple[LookupKey, ...],
|
|
95
|
+
index: int,
|
|
96
|
+
resolved: Resolved[LookupNode],
|
|
97
|
+
) -> None:
|
|
98
|
+
if index >= len(parts) - 1:
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
prefix = parts[: index + 1]
|
|
102
|
+
try:
|
|
103
|
+
self._cache[prefix] = resolved
|
|
104
|
+
except TypeError:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
def invalidate(self) -> None:
|
|
108
|
+
self._cache.clear()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""JSONSchema spec nodes module."""
|
|
2
|
+
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from pathable.accessors import LookupAccessor
|
|
6
|
+
from pathable.types import LookupNode
|
|
7
|
+
from referencing._core import Resolved
|
|
8
|
+
from referencing._core import Resolver
|
|
9
|
+
|
|
10
|
+
from jsonschema_path.typing import Schema
|
|
11
|
+
from jsonschema_path.utils import is_ref
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SchemaNode(LookupAccessor):
|
|
15
|
+
@classmethod
|
|
16
|
+
def _resolve_node(
|
|
17
|
+
cls,
|
|
18
|
+
node: LookupNode,
|
|
19
|
+
resolver: Resolver[Schema],
|
|
20
|
+
) -> Resolved[Schema]:
|
|
21
|
+
if is_ref(node):
|
|
22
|
+
ref_node = cls._get_subnode(node, "$ref")
|
|
23
|
+
ref = cls._read_node(ref_node)
|
|
24
|
+
resolved = resolver.lookup(ref)
|
|
25
|
+
return cls._resolve_node(
|
|
26
|
+
resolved.contents,
|
|
27
|
+
resolved.resolver,
|
|
28
|
+
)
|
|
29
|
+
return Resolved(cast(Schema, node), resolver) # type: ignore
|
|
@@ -105,6 +105,7 @@ class SchemaPath(AccessorPath[SchemaNode, SchemaKey, SchemaValue]):
|
|
|
105
105
|
specification: Specification[Schema] = DRAFT202012,
|
|
106
106
|
base_uri: str = "",
|
|
107
107
|
handlers: ResolverHandlers = default_handlers,
|
|
108
|
+
resolved_cache_maxsize: int = 0,
|
|
108
109
|
spec_url: str | None = None,
|
|
109
110
|
ref_resolver_handlers: ResolverHandlers | None = None,
|
|
110
111
|
) -> TSchemaPath:
|
|
@@ -127,6 +128,7 @@ class SchemaPath(AccessorPath[SchemaNode, SchemaKey, SchemaValue]):
|
|
|
127
128
|
specification=specification,
|
|
128
129
|
base_uri=base_uri,
|
|
129
130
|
handlers=handlers,
|
|
131
|
+
resolved_cache_maxsize=resolved_cache_maxsize,
|
|
130
132
|
)
|
|
131
133
|
|
|
132
134
|
return cls(accessor, *args, separator=separator)
|
|
@@ -135,19 +137,29 @@ class SchemaPath(AccessorPath[SchemaNode, SchemaKey, SchemaValue]):
|
|
|
135
137
|
def from_path(
|
|
136
138
|
cls: type[TSchemaPath],
|
|
137
139
|
path: Path,
|
|
140
|
+
resolved_cache_maxsize: int = 0,
|
|
138
141
|
) -> TSchemaPath:
|
|
139
142
|
reader = PathReader(path)
|
|
140
143
|
data, base_uri = reader.read()
|
|
141
|
-
return cls.from_dict(
|
|
144
|
+
return cls.from_dict(
|
|
145
|
+
data,
|
|
146
|
+
base_uri=base_uri,
|
|
147
|
+
resolved_cache_maxsize=resolved_cache_maxsize,
|
|
148
|
+
)
|
|
142
149
|
|
|
143
150
|
@classmethod
|
|
144
151
|
def from_file_path(
|
|
145
152
|
cls: type[TSchemaPath],
|
|
146
153
|
file_path: str,
|
|
154
|
+
resolved_cache_maxsize: int = 0,
|
|
147
155
|
) -> TSchemaPath:
|
|
148
156
|
reader = FilePathReader(file_path)
|
|
149
157
|
data, base_uri = reader.read()
|
|
150
|
-
return cls.from_dict(
|
|
158
|
+
return cls.from_dict(
|
|
159
|
+
data,
|
|
160
|
+
base_uri=base_uri,
|
|
161
|
+
resolved_cache_maxsize=resolved_cache_maxsize,
|
|
162
|
+
)
|
|
151
163
|
|
|
152
164
|
@classmethod
|
|
153
165
|
def from_file(
|
|
@@ -155,10 +167,16 @@ class SchemaPath(AccessorPath[SchemaNode, SchemaKey, SchemaValue]):
|
|
|
155
167
|
fileobj: SupportsRead,
|
|
156
168
|
base_uri: str = "",
|
|
157
169
|
spec_url: str | None = None,
|
|
170
|
+
resolved_cache_maxsize: int = 0,
|
|
158
171
|
) -> TSchemaPath:
|
|
159
172
|
reader = FileReader(fileobj)
|
|
160
173
|
data, _ = reader.read()
|
|
161
|
-
return cls.from_dict(
|
|
174
|
+
return cls.from_dict(
|
|
175
|
+
data,
|
|
176
|
+
base_uri=base_uri,
|
|
177
|
+
spec_url=spec_url,
|
|
178
|
+
resolved_cache_maxsize=resolved_cache_maxsize,
|
|
179
|
+
)
|
|
162
180
|
|
|
163
181
|
def str_keys(self) -> Sequence[str]:
|
|
164
182
|
keys = list(self.keys())
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from pathable.types import LookupKey
|
|
6
|
+
from pathable.types import LookupNode
|
|
7
|
+
from referencing import Registry
|
|
8
|
+
from referencing._core import Resolved
|
|
9
|
+
from referencing._core import Resolver
|
|
10
|
+
|
|
11
|
+
from jsonschema_path.caches import PrefixResolvedCache
|
|
12
|
+
from jsonschema_path.nodes import SchemaNode
|
|
13
|
+
from jsonschema_path.typing import Schema
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class ResolveResult:
|
|
18
|
+
resolved: Resolved[LookupNode]
|
|
19
|
+
registry_changed: bool
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CachedPathResolver:
|
|
23
|
+
def __init__(self, resolver: Resolver[Schema]):
|
|
24
|
+
self.resolver = resolver
|
|
25
|
+
self.prefix_cache = PrefixResolvedCache()
|
|
26
|
+
|
|
27
|
+
def resolve(
|
|
28
|
+
self,
|
|
29
|
+
node: LookupNode,
|
|
30
|
+
parts: Sequence[LookupKey],
|
|
31
|
+
) -> ResolveResult:
|
|
32
|
+
resolved = self._resolve_with_prefix_cache(node, parts)
|
|
33
|
+
registry_changed = self._sync_registry(resolved.resolver._registry)
|
|
34
|
+
return ResolveResult(
|
|
35
|
+
resolved=resolved,
|
|
36
|
+
registry_changed=registry_changed,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def _resolve_with_prefix_cache(
|
|
40
|
+
self,
|
|
41
|
+
node: LookupNode,
|
|
42
|
+
parts: Sequence[LookupKey],
|
|
43
|
+
) -> Resolved[LookupNode]:
|
|
44
|
+
|
|
45
|
+
parts_tuple = tuple(parts)
|
|
46
|
+
cached_prefix = self.prefix_cache.longest_prefix_hit(parts_tuple)
|
|
47
|
+
if cached_prefix is None:
|
|
48
|
+
root_resolved_schema = SchemaNode._resolve_node(
|
|
49
|
+
node,
|
|
50
|
+
self.resolver,
|
|
51
|
+
)
|
|
52
|
+
resolved = cast(Resolved[LookupNode], root_resolved_schema)
|
|
53
|
+
current_node = cast(LookupNode, root_resolved_schema.contents)
|
|
54
|
+
current_resolver: Resolver[Schema] = root_resolved_schema.resolver
|
|
55
|
+
start = 0
|
|
56
|
+
self.prefix_cache.seed_root(resolved)
|
|
57
|
+
else:
|
|
58
|
+
start, resolved = cached_prefix
|
|
59
|
+
current_node = resolved.contents
|
|
60
|
+
current_resolver = cast(Resolver[Schema], resolved.resolver)
|
|
61
|
+
|
|
62
|
+
for index in range(start, len(parts_tuple)):
|
|
63
|
+
part = parts_tuple[index]
|
|
64
|
+
current_node = SchemaNode._get_subnode(current_node, part)
|
|
65
|
+
resolved_schema = SchemaNode._resolve_node(
|
|
66
|
+
current_node,
|
|
67
|
+
current_resolver,
|
|
68
|
+
)
|
|
69
|
+
resolved = cast(Resolved[LookupNode], resolved_schema)
|
|
70
|
+
current_node, current_resolver = (
|
|
71
|
+
resolved.contents,
|
|
72
|
+
resolved_schema.resolver,
|
|
73
|
+
)
|
|
74
|
+
self.prefix_cache.store_intermediate(
|
|
75
|
+
parts_tuple,
|
|
76
|
+
index,
|
|
77
|
+
resolved,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return resolved
|
|
81
|
+
|
|
82
|
+
def _sync_registry(self, registry: Registry[LookupNode]) -> bool:
|
|
83
|
+
if registry is self.resolver._registry:
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
self.resolver = self.resolver._evolve(
|
|
87
|
+
self.resolver._base_uri,
|
|
88
|
+
registry=registry,
|
|
89
|
+
)
|
|
90
|
+
self.prefix_cache.invalidate()
|
|
91
|
+
return True
|
|
@@ -19,7 +19,7 @@ ignore_missing_imports = true
|
|
|
19
19
|
|
|
20
20
|
[tool.poetry]
|
|
21
21
|
name = "jsonschema-path"
|
|
22
|
-
version = "0.4.
|
|
22
|
+
version = "0.4.4"
|
|
23
23
|
description = "JSONSchema Spec with object-oriented paths"
|
|
24
24
|
authors = ["Artur Maciag <maciag.artur@gmail.com>"]
|
|
25
25
|
license = "Apache-2.0"
|
|
@@ -93,7 +93,7 @@ message_template = "Version {new_version}"
|
|
|
93
93
|
tag_template = "{new_version}"
|
|
94
94
|
|
|
95
95
|
[tool.tbump.version]
|
|
96
|
-
current = "0.4.
|
|
96
|
+
current = "0.4.4"
|
|
97
97
|
regex = '''
|
|
98
98
|
(?P<major>\d+)
|
|
99
99
|
\.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|