pytest-jsonschema-snapshot 0.2.3__py3-none-any.whl → 0.2.5__py3-none-any.whl
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.
- pytest_jsonschema_snapshot/__init__.py +1 -1
- pytest_jsonschema_snapshot/core.py +27 -12
- pytest_jsonschema_snapshot/stats.py +103 -63
- pytest_jsonschema_snapshot/tools/__init__.py +1 -2
- {pytest_jsonschema_snapshot-0.2.3.dist-info → pytest_jsonschema_snapshot-0.2.5.dist-info}/METADATA +2 -2
- pytest_jsonschema_snapshot-0.2.5.dist-info/RECORD +12 -0
- {pytest_jsonschema_snapshot-0.2.3.dist-info → pytest_jsonschema_snapshot-0.2.5.dist-info}/WHEEL +1 -1
- pytest_jsonschema_snapshot/tools/genson_addon/__init__.py +0 -3
- pytest_jsonschema_snapshot/tools/genson_addon/format_detector.py +0 -52
- pytest_jsonschema_snapshot/tools/genson_addon/to_schema_converter.py +0 -100
- pytest_jsonschema_snapshot-0.2.3.dist-info/RECORD +0 -15
- {pytest_jsonschema_snapshot-0.2.3.dist-info → pytest_jsonschema_snapshot-0.2.5.dist-info}/entry_points.txt +0 -0
- {pytest_jsonschema_snapshot-0.2.3.dist-info → pytest_jsonschema_snapshot-0.2.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,10 +13,17 @@ if TYPE_CHECKING:
|
|
|
13
13
|
from jsonschema_diff import JsonSchemaDiff
|
|
14
14
|
|
|
15
15
|
import pytest
|
|
16
|
+
from genschema import Converter, PseudoArrayHandler
|
|
17
|
+
from genschema.comparators import (
|
|
18
|
+
DeleteElement,
|
|
19
|
+
FormatComparator,
|
|
20
|
+
RequiredComparator,
|
|
21
|
+
SchemaVersionComparator,
|
|
22
|
+
)
|
|
16
23
|
from jsonschema import FormatChecker, ValidationError, validate
|
|
17
24
|
|
|
18
25
|
from .stats import GLOBAL_STATS
|
|
19
|
-
from .tools import
|
|
26
|
+
from .tools import NameMaker
|
|
20
27
|
|
|
21
28
|
|
|
22
29
|
class SchemaShot:
|
|
@@ -54,6 +61,17 @@ class SchemaShot:
|
|
|
54
61
|
self.snapshot_dir: Path = root_dir / snapshot_dir_name
|
|
55
62
|
self.used_schemas: Set[str] = set()
|
|
56
63
|
|
|
64
|
+
self.conv = Converter(
|
|
65
|
+
pseudo_handler=PseudoArrayHandler(),
|
|
66
|
+
base_of="anyOf",
|
|
67
|
+
)
|
|
68
|
+
self.conv.register(FormatComparator())
|
|
69
|
+
self.conv.register(RequiredComparator())
|
|
70
|
+
# self.conv.register(EmptyComparator())
|
|
71
|
+
self.conv.register(SchemaVersionComparator())
|
|
72
|
+
self.conv.register(DeleteElement())
|
|
73
|
+
self.conv.register(DeleteElement("isPseudoArray"))
|
|
74
|
+
|
|
57
75
|
self.logger = logging.getLogger(__name__)
|
|
58
76
|
# добавляем вывод в stderr
|
|
59
77
|
handler = logging.StreamHandler()
|
|
@@ -144,11 +162,9 @@ class SchemaShot:
|
|
|
144
162
|
|
|
145
163
|
real_name = self._process_name(name)
|
|
146
164
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
builder.add_object(data)
|
|
151
|
-
current_schema = builder.to_schema()
|
|
165
|
+
self.conv.clear_data()
|
|
166
|
+
self.conv.add_schema(data)
|
|
167
|
+
current_schema = self.conv.run()
|
|
152
168
|
|
|
153
169
|
real_name, status = self._base_match(data, current_schema, real_name)
|
|
154
170
|
|
|
@@ -234,12 +250,11 @@ class SchemaShot:
|
|
|
234
250
|
schema_updated = False
|
|
235
251
|
|
|
236
252
|
def merge_schemas(old: dict, new: dict) -> dict:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return builder.to_schema()
|
|
253
|
+
self.conv.clear_data()
|
|
254
|
+
self.conv.add_schema(old)
|
|
255
|
+
self.conv.add_schema(new)
|
|
256
|
+
result = self.conv.run()
|
|
257
|
+
return result
|
|
243
258
|
|
|
244
259
|
if existing_schema != current_schema: # есть отличия
|
|
245
260
|
if (self.update_mode or self.reset_mode) and self.update_actions.get("update"):
|
|
@@ -80,51 +80,61 @@ class SchemaStats:
|
|
|
80
80
|
|
|
81
81
|
return "\n".join(parts)
|
|
82
82
|
|
|
83
|
+
def _iter_schemas(self, names: List[str]) -> Generator[tuple[str, Optional[str]], None, None]:
|
|
84
|
+
"""
|
|
85
|
+
Iterates over schema displays: (display, schema_key)
|
|
86
|
+
- display: string to display (may have " + original")
|
|
87
|
+
- schema_key: file name of the schema (<name>.schema.json) to find diffs,
|
|
88
|
+
or None if it's not a schema.
|
|
89
|
+
Preserves the original list order: merging happens at .schema.json
|
|
90
|
+
position; skips .json if paired with schema.
|
|
91
|
+
"""
|
|
92
|
+
names = list(names) # order matters
|
|
93
|
+
schema_sfx = ".schema.json"
|
|
94
|
+
json_sfx = ".json"
|
|
95
|
+
|
|
96
|
+
# sets of bases
|
|
97
|
+
# bases_with_schema = {n[: -len(schema_sfx)] for n in names if n.endswith(schema_sfx)}
|
|
98
|
+
bases_with_original = {
|
|
99
|
+
n[: -len(json_sfx)]
|
|
100
|
+
for n in names
|
|
101
|
+
if n.endswith(json_sfx) and not n.endswith(schema_sfx)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for n in names:
|
|
105
|
+
if n.endswith(schema_sfx):
|
|
106
|
+
base = n[: -len(schema_sfx)]
|
|
107
|
+
if base in bases_with_original:
|
|
108
|
+
yield f"{n} + original", n # display, schema_key
|
|
109
|
+
else:
|
|
110
|
+
yield n, n
|
|
111
|
+
# if .json, skip if paired
|
|
112
|
+
# if other, yield n, n (but assume all are .json or .schema.json)
|
|
113
|
+
|
|
114
|
+
def _iter_only_originals(self, names: List[str]) -> Generator[str, None, None]:
|
|
115
|
+
"""
|
|
116
|
+
Iterates over only unpaired .json files, in the order they appear.
|
|
117
|
+
"""
|
|
118
|
+
names = list(names) # order matters
|
|
119
|
+
schema_sfx = ".schema.json"
|
|
120
|
+
json_sfx = ".json"
|
|
121
|
+
|
|
122
|
+
bases_with_schema = {n[: -len(schema_sfx)] for n in names if n.endswith(schema_sfx)}
|
|
123
|
+
|
|
124
|
+
for n in names:
|
|
125
|
+
if n.endswith(json_sfx) and not n.endswith(schema_sfx):
|
|
126
|
+
base = n[: -len(json_sfx)]
|
|
127
|
+
if base not in bases_with_schema:
|
|
128
|
+
yield n
|
|
129
|
+
|
|
83
130
|
def print_summary(self, terminalreporter: pytest.TerminalReporter, update_mode: bool) -> None:
|
|
84
131
|
"""
|
|
85
132
|
Prints schema summary to pytest terminal output.
|
|
86
133
|
Pairs of "<name>.schema.json" + "<name>.json" are merged into one line:
|
|
87
134
|
"<name>.schema.json + original" (if original is present).
|
|
135
|
+
Unpaired .json are shown in separate "only originals" sections.
|
|
88
136
|
"""
|
|
89
137
|
|
|
90
|
-
def _iter_merged(names: List[str]) -> Generator[tuple[str, Optional[str]], None, None]:
|
|
91
|
-
"""
|
|
92
|
-
Iterates over (display, schema_key):
|
|
93
|
-
- display: string to display (may have " + original")
|
|
94
|
-
- schema_key: file name of the schema (<name>.schema.json) to find diffs,
|
|
95
|
-
or None if it's not a schema.
|
|
96
|
-
Preserves the original list order: merging happens at .schema.json
|
|
97
|
-
position; single .json outputs are left as is.
|
|
98
|
-
"""
|
|
99
|
-
names = list(names) # порядок важен
|
|
100
|
-
schema_sfx = ".schema.json"
|
|
101
|
-
json_sfx = ".json"
|
|
102
|
-
|
|
103
|
-
# множество баз, где имеются схемы/оригиналы
|
|
104
|
-
bases_with_schema = {n[: -len(schema_sfx)] for n in names if n.endswith(schema_sfx)}
|
|
105
|
-
bases_with_original = {
|
|
106
|
-
n[: -len(json_sfx)]
|
|
107
|
-
for n in names
|
|
108
|
-
if n.endswith(json_sfx) and not n.endswith(schema_sfx)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
for n in names:
|
|
112
|
-
if n.endswith(schema_sfx):
|
|
113
|
-
base = n[: -len(schema_sfx)]
|
|
114
|
-
if base in bases_with_original:
|
|
115
|
-
yield f"{n} + original", n # display, schema_key
|
|
116
|
-
else:
|
|
117
|
-
yield n, n
|
|
118
|
-
elif n.endswith(json_sfx) and not n.endswith(schema_sfx):
|
|
119
|
-
base = n[: -len(json_sfx)]
|
|
120
|
-
# если есть парная схема — .json не выводим отдельно
|
|
121
|
-
if base in bases_with_schema:
|
|
122
|
-
continue
|
|
123
|
-
yield n, None
|
|
124
|
-
else:
|
|
125
|
-
# на всякий случай — прочие имена
|
|
126
|
-
yield n, n
|
|
127
|
-
|
|
128
138
|
if not self.has_any_info():
|
|
129
139
|
return
|
|
130
140
|
|
|
@@ -132,52 +142,82 @@ class SchemaStats:
|
|
|
132
142
|
|
|
133
143
|
# Created
|
|
134
144
|
if self.created:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
schemas = list(self._iter_schemas(self.created))
|
|
146
|
+
only_originals = list(self._iter_only_originals(self.created))
|
|
147
|
+
if schemas:
|
|
148
|
+
terminalreporter.write_line(f"Created schemas ({len(schemas)}):", green=True)
|
|
149
|
+
for display, _key in schemas:
|
|
150
|
+
terminalreporter.write_line(f" - {display}", green=True)
|
|
151
|
+
if only_originals:
|
|
152
|
+
terminalreporter.write_line(
|
|
153
|
+
f"Created only originals ({len(only_originals)}):", green=True
|
|
154
|
+
)
|
|
155
|
+
for display in only_originals:
|
|
156
|
+
terminalreporter.write_line(f" - {display}", green=True)
|
|
138
157
|
|
|
139
158
|
# Updated
|
|
140
159
|
if self.updated:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
terminalreporter.write_line("
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
schemas = list(self._iter_schemas(self.updated))
|
|
161
|
+
only_originals = list(self._iter_only_originals(self.updated))
|
|
162
|
+
if schemas:
|
|
163
|
+
terminalreporter.write_line(f"Updated schemas ({len(schemas)}):", yellow=True)
|
|
164
|
+
for display, key in schemas:
|
|
165
|
+
terminalreporter.write_line(f" - {display}", yellow=True)
|
|
166
|
+
# Show diff if available for schema
|
|
167
|
+
if key and key in self.updated_diffs:
|
|
168
|
+
diff = self.updated_diffs[key]
|
|
169
|
+
if diff.strip():
|
|
170
|
+
terminalreporter.write_line(" Changes:", yellow=True)
|
|
171
|
+
for line in diff.split("\n"):
|
|
172
|
+
if line.strip():
|
|
173
|
+
terminalreporter.write_line(f" {line}")
|
|
174
|
+
terminalreporter.write_line("") # separation
|
|
175
|
+
else:
|
|
176
|
+
terminalreporter.write_line(
|
|
177
|
+
" (Schema unchanged - no differences detected)", cyan=True
|
|
178
|
+
)
|
|
179
|
+
if only_originals:
|
|
180
|
+
terminalreporter.write_line(
|
|
181
|
+
f"Updated only originals ({len(only_originals)}):", yellow=True
|
|
182
|
+
)
|
|
183
|
+
for display in only_originals:
|
|
184
|
+
terminalreporter.write_line(f" - {display}", yellow=True)
|
|
155
185
|
|
|
156
186
|
# Uncommitted
|
|
157
187
|
if self.uncommitted:
|
|
158
188
|
terminalreporter.write_line(
|
|
159
189
|
f"Uncommitted minor updates ({len(self.uncommitted)}):", bold=True
|
|
160
190
|
)
|
|
161
|
-
for display, key in
|
|
191
|
+
for display, key in self._iter_schemas(self.uncommitted): # assuming mostly schemas
|
|
162
192
|
terminalreporter.write_line(f" - {display}", cyan=True)
|
|
193
|
+
# Show diff if available
|
|
163
194
|
if key and key in self.uncommitted_diffs:
|
|
164
195
|
terminalreporter.write_line(" Detected changes:", cyan=True)
|
|
165
196
|
for line in self.uncommitted_diffs[key].split("\n"):
|
|
166
197
|
if line.strip():
|
|
167
198
|
terminalreporter.write_line(f" {line}")
|
|
168
|
-
terminalreporter.write_line("") #
|
|
199
|
+
terminalreporter.write_line("") # separation
|
|
169
200
|
terminalreporter.write_line("Use --schema-update to commit these changes", cyan=True)
|
|
170
201
|
|
|
171
202
|
# Deleted
|
|
172
203
|
if self.deleted:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
204
|
+
schemas = list(self._iter_schemas(self.deleted))
|
|
205
|
+
only_originals = list(self._iter_only_originals(self.deleted))
|
|
206
|
+
if schemas:
|
|
207
|
+
terminalreporter.write_line(f"Deleted schemas ({len(schemas)}):", red=True)
|
|
208
|
+
for display, _key in schemas:
|
|
209
|
+
terminalreporter.write_line(f" - {display}", red=True)
|
|
210
|
+
if only_originals:
|
|
211
|
+
terminalreporter.write_line(
|
|
212
|
+
f"Deleted only originals ({len(only_originals)}):", red=True
|
|
213
|
+
)
|
|
214
|
+
for display in only_originals:
|
|
215
|
+
terminalreporter.write_line(f" - {display}", red=True)
|
|
216
|
+
|
|
217
|
+
# Unused (only if not update_mode)
|
|
178
218
|
if self.unused and not update_mode:
|
|
179
219
|
terminalreporter.write_line(f"Unused schemas ({len(self.unused)}):")
|
|
180
|
-
for display, _key in
|
|
220
|
+
for display, _key in self._iter_schemas(self.unused): # assuming schemas
|
|
181
221
|
terminalreporter.write_line(f" - {display}")
|
|
182
222
|
terminalreporter.write_line("Use --schema-update to delete unused schemas", yellow=True)
|
|
183
223
|
|
{pytest_jsonschema_snapshot-0.2.3.dist-info → pytest_jsonschema_snapshot-0.2.5.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-jsonschema-snapshot
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Pytest plugin for automatic JSON Schema generation and validation from examples
|
|
5
5
|
Project-URL: Homepage, https://miskler.github.io/pytest-jsonschema-snapshot/basic/quick_start.html
|
|
6
6
|
Project-URL: Repository, https://github.com/Miskler/pytest-jsonschema-snapshot
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Classifier: Topic :: Utilities
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
|
-
Requires-Dist:
|
|
25
|
+
Requires-Dist: genschema
|
|
26
26
|
Requires-Dist: jsonschema
|
|
27
27
|
Requires-Dist: jsonschema-diff
|
|
28
28
|
Requires-Dist: pathvalidate
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pytest_jsonschema_snapshot/__init__.py,sha256=o7yiVh_nbK13tO4NpaE25XW3J9V2YD7fkicByUdS-iM,385
|
|
2
|
+
pytest_jsonschema_snapshot/core.py,sha256=fM8r8wD83litob2sheHb1jh8x8Nam4g8AnjGzk8TZ2o,12347
|
|
3
|
+
pytest_jsonschema_snapshot/plugin.py,sha256=nvAfxtLSX_B5FzaWu7DfsiWRxFjxDvnQNNOhkRrRnbw,8677
|
|
4
|
+
pytest_jsonschema_snapshot/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
pytest_jsonschema_snapshot/stats.py,sha256=BfhfMoSkRq6Q8BwhVfrpcFl5TP9OzpgpLwnKf1Kslkw,9593
|
|
6
|
+
pytest_jsonschema_snapshot/tools/__init__.py,sha256=WeD2EVrQpKIoFW1s43QAqsJmartqZ3Irwckt814P1bs,59
|
|
7
|
+
pytest_jsonschema_snapshot/tools/name_maker.py,sha256=tqss8NCGSo2aQX_-RkCJzy3NJx_TDA-xrn8qsblecf0,5799
|
|
8
|
+
pytest_jsonschema_snapshot-0.2.5.dist-info/METADATA,sha256=QR0VvtvzFi6fDHtGUv_sNgSSUBSTw7ZJET_Ajd09IEE,7798
|
|
9
|
+
pytest_jsonschema_snapshot-0.2.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
10
|
+
pytest_jsonschema_snapshot-0.2.5.dist-info/entry_points.txt,sha256=eJ1x4TMmhcc8YtM7IoCsUJO4-rLeTrGy8tPgkrojjKs,58
|
|
11
|
+
pytest_jsonschema_snapshot-0.2.5.dist-info/licenses/LICENSE,sha256=1HRFdSzlJ4BtHv6U7tZun3iCArjbCnm5NUowE9hZpNs,1071
|
|
12
|
+
pytest_jsonschema_snapshot-0.2.5.dist-info/RECORD,,
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class FormatDetector:
|
|
6
|
-
"""Class for detecting string formats"""
|
|
7
|
-
|
|
8
|
-
# Regular expressions for various formats
|
|
9
|
-
EMAIL_PATTERN = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
|
|
10
|
-
UUID_PATTERN = re.compile(
|
|
11
|
-
r"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
|
12
|
-
re.I,
|
|
13
|
-
)
|
|
14
|
-
DATE_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
15
|
-
DATETIME_PATTERN = re.compile(
|
|
16
|
-
r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$"
|
|
17
|
-
)
|
|
18
|
-
URI_PATTERN = re.compile(r"^https?://[^\s/$.?#].[^\s]*$", re.I)
|
|
19
|
-
IPV4_PATTERN = re.compile(
|
|
20
|
-
r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}"
|
|
21
|
-
r"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
@classmethod
|
|
25
|
-
def detect_format(cls, value: str) -> Optional[str]:
|
|
26
|
-
"""
|
|
27
|
-
Detects the format of a string.
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
value: The string to analyze
|
|
31
|
-
|
|
32
|
-
Returns:
|
|
33
|
-
The name of the format or None if the format is not defined
|
|
34
|
-
"""
|
|
35
|
-
if not isinstance(value, str) or not value:
|
|
36
|
-
return None
|
|
37
|
-
|
|
38
|
-
# Check formats from more specific to less specific
|
|
39
|
-
if cls.EMAIL_PATTERN.match(value):
|
|
40
|
-
return "email"
|
|
41
|
-
elif cls.UUID_PATTERN.match(value):
|
|
42
|
-
return "uuid"
|
|
43
|
-
elif cls.DATETIME_PATTERN.match(value):
|
|
44
|
-
return "date-time"
|
|
45
|
-
elif cls.DATE_PATTERN.match(value):
|
|
46
|
-
return "date"
|
|
47
|
-
elif cls.URI_PATTERN.match(value):
|
|
48
|
-
return "uri"
|
|
49
|
-
elif cls.IPV4_PATTERN.match(value):
|
|
50
|
-
return "ipv4"
|
|
51
|
-
|
|
52
|
-
return None
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
"""Json → Schema with optional format handling.
|
|
2
|
-
|
|
3
|
-
`format_mode` options
|
|
4
|
-
---------------------
|
|
5
|
-
* ``"on"`` – detect formats and let validators assert them (default).
|
|
6
|
-
* ``"off"`` – ignore formats entirely.
|
|
7
|
-
* ``"safe"`` – keep the annotations but embed a ``$vocabulary`` block that
|
|
8
|
-
**disables** the draft‑2020‑12 *format‑assertion* vocabulary.
|
|
9
|
-
This makes every ``format`` purely informational, regardless
|
|
10
|
-
of validator settings.
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from typing import Any, Dict, Literal
|
|
14
|
-
|
|
15
|
-
from genson import SchemaBuilder # type: ignore[import-untyped]
|
|
16
|
-
|
|
17
|
-
from .format_detector import FormatDetector
|
|
18
|
-
|
|
19
|
-
_FormatMode = Literal["on", "off", "safe"]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class JsonToSchemaConverter(SchemaBuilder):
|
|
23
|
-
"""A thin wrapper around :class:`genson.SchemaBuilder`."""
|
|
24
|
-
|
|
25
|
-
# ------------------------------------------------------------------
|
|
26
|
-
# Construction
|
|
27
|
-
# ------------------------------------------------------------------
|
|
28
|
-
def __init__(
|
|
29
|
-
self,
|
|
30
|
-
schema_uri: str = "https://json-schema.org/draft/2020-12/schema",
|
|
31
|
-
*,
|
|
32
|
-
format_mode: _FormatMode = "on",
|
|
33
|
-
):
|
|
34
|
-
super().__init__(schema_uri) if schema_uri else super().__init__()
|
|
35
|
-
if format_mode not in {"on", "off", "safe"}:
|
|
36
|
-
raise ValueError("format_mode must be 'on', 'off', or 'safe'.")
|
|
37
|
-
self._format_mode: _FormatMode = format_mode
|
|
38
|
-
self._format_cache: Dict[str, set[str]] = {}
|
|
39
|
-
|
|
40
|
-
# ------------------------------------------------------------------
|
|
41
|
-
# Public API (overrides)
|
|
42
|
-
# ------------------------------------------------------------------
|
|
43
|
-
def add_object(self, obj: Any, path: str = "root") -> None:
|
|
44
|
-
super().add_object(obj)
|
|
45
|
-
if self._format_mode != "off":
|
|
46
|
-
self._collect_formats(obj, path)
|
|
47
|
-
|
|
48
|
-
def to_schema(self) -> Dict[str, Any]:
|
|
49
|
-
schema = dict(super().to_schema()) # shallow‑copy
|
|
50
|
-
|
|
51
|
-
if self._format_mode != "off":
|
|
52
|
-
self._inject_formats(schema, "root")
|
|
53
|
-
|
|
54
|
-
if self._format_mode == "safe":
|
|
55
|
-
schema.setdefault(
|
|
56
|
-
"$vocabulary",
|
|
57
|
-
{
|
|
58
|
-
"https://json-schema.org/draft/2020-12/vocab/core": True,
|
|
59
|
-
"https://json-schema.org/draft/2020-12/vocab/applicator": True,
|
|
60
|
-
"https://json-schema.org/draft/2020-12/vocab/format-annotation": True,
|
|
61
|
-
"https://json-schema.org/draft/2020-12/vocab/format-assertion": False,
|
|
62
|
-
},
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
return schema
|
|
66
|
-
|
|
67
|
-
# ------------------------------------------------------------------
|
|
68
|
-
# Internals
|
|
69
|
-
# ------------------------------------------------------------------
|
|
70
|
-
def _collect_formats(self, obj: Any, path: str) -> None:
|
|
71
|
-
if isinstance(obj, str):
|
|
72
|
-
fmt = FormatDetector.detect_format(obj)
|
|
73
|
-
if fmt:
|
|
74
|
-
self._format_cache.setdefault(path, set()).add(fmt)
|
|
75
|
-
elif isinstance(obj, dict):
|
|
76
|
-
for k, v in obj.items():
|
|
77
|
-
self._collect_formats(v, f"{path}.{k}")
|
|
78
|
-
elif isinstance(obj, (list, tuple)):
|
|
79
|
-
for i, item in enumerate(obj):
|
|
80
|
-
self._collect_formats(item, f"{path}[{i}]")
|
|
81
|
-
|
|
82
|
-
def _inject_formats(self, schema: Dict[str, Any], path: str) -> None:
|
|
83
|
-
t = schema.get("type")
|
|
84
|
-
if t == "string":
|
|
85
|
-
fmts = self._format_cache.get(path)
|
|
86
|
-
if fmts and len(fmts) == 1:
|
|
87
|
-
schema["format"] = next(iter(fmts))
|
|
88
|
-
elif t == "object" and "properties" in schema:
|
|
89
|
-
for name, subschema in schema["properties"].items():
|
|
90
|
-
self._inject_formats(subschema, f"{path}.{name}")
|
|
91
|
-
elif t == "array" and "items" in schema:
|
|
92
|
-
items_schema = schema["items"]
|
|
93
|
-
if isinstance(items_schema, dict):
|
|
94
|
-
self._inject_formats(items_schema, f"{path}[0]")
|
|
95
|
-
else:
|
|
96
|
-
for idx, subschema in enumerate(items_schema):
|
|
97
|
-
self._inject_formats(subschema, f"{path}[{idx}]")
|
|
98
|
-
elif "anyOf" in schema:
|
|
99
|
-
for subschema in schema["anyOf"]:
|
|
100
|
-
self._inject_formats(subschema, path)
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
pytest_jsonschema_snapshot/__init__.py,sha256=v-YeeYwLr0JgBV0W2iNt8SEfBDFnP0TivliTh8btZYc,385
|
|
2
|
-
pytest_jsonschema_snapshot/core.py,sha256=CoL_W-u6o3N7XDwv-MbePJiZaGX0LtGS6BLbj9MHROU,11995
|
|
3
|
-
pytest_jsonschema_snapshot/plugin.py,sha256=nvAfxtLSX_B5FzaWu7DfsiWRxFjxDvnQNNOhkRrRnbw,8677
|
|
4
|
-
pytest_jsonschema_snapshot/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
pytest_jsonschema_snapshot/stats.py,sha256=XGGzHY0ytMFOkFpnqNAK1DpV9iI0_fZPWrVvHFNFL3g,7943
|
|
6
|
-
pytest_jsonschema_snapshot/tools/__init__.py,sha256=WMS6PdgMABBfTRhPGuoUOXB-R2PcqcadwH8pG1C6MFU,132
|
|
7
|
-
pytest_jsonschema_snapshot/tools/name_maker.py,sha256=tqss8NCGSo2aQX_-RkCJzy3NJx_TDA-xrn8qsblecf0,5799
|
|
8
|
-
pytest_jsonschema_snapshot/tools/genson_addon/__init__.py,sha256=nANkqHTaWTZPwBDztsnQvObHUZLSeHenJS--oWfep8c,92
|
|
9
|
-
pytest_jsonschema_snapshot/tools/genson_addon/format_detector.py,sha256=Wc5pB_xstyr4OtjwJ2qqmV62xET63cN7Nb0gxkrYyW0,1636
|
|
10
|
-
pytest_jsonschema_snapshot/tools/genson_addon/to_schema_converter.py,sha256=UdQIkZhMrTJNHwI1B1dv3aEwx41B1B_lLyr4KWiUpNY,4168
|
|
11
|
-
pytest_jsonschema_snapshot-0.2.3.dist-info/METADATA,sha256=cMTeW3ydl2gg8dQHUqmsiLSa9l9ndSoQP5yUlzktOn4,7795
|
|
12
|
-
pytest_jsonschema_snapshot-0.2.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
-
pytest_jsonschema_snapshot-0.2.3.dist-info/entry_points.txt,sha256=eJ1x4TMmhcc8YtM7IoCsUJO4-rLeTrGy8tPgkrojjKs,58
|
|
14
|
-
pytest_jsonschema_snapshot-0.2.3.dist-info/licenses/LICENSE,sha256=1HRFdSzlJ4BtHv6U7tZun3iCArjbCnm5NUowE9hZpNs,1071
|
|
15
|
-
pytest_jsonschema_snapshot-0.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|