pytest-jsonschema-snapshot 0.2.4__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/tools/__init__.py +1 -2
- {pytest_jsonschema_snapshot-0.2.4.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.4.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.4.dist-info/RECORD +0 -15
- {pytest_jsonschema_snapshot-0.2.4.dist-info → pytest_jsonschema_snapshot-0.2.5.dist-info}/entry_points.txt +0 -0
- {pytest_jsonschema_snapshot-0.2.4.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"):
|
{pytest_jsonschema_snapshot-0.2.4.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=rUBgKVh7nCLEwfO0XZS-TewnM5eTXpBEI7NaC5p4qLg,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=BfhfMoSkRq6Q8BwhVfrpcFl5TP9OzpgpLwnKf1Kslkw,9593
|
|
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.4.dist-info/METADATA,sha256=iiGeO3N83BH1griTvads-RUawss1rw_9NIlFEdK6mkM,7795
|
|
12
|
-
pytest_jsonschema_snapshot-0.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
-
pytest_jsonschema_snapshot-0.2.4.dist-info/entry_points.txt,sha256=eJ1x4TMmhcc8YtM7IoCsUJO4-rLeTrGy8tPgkrojjKs,58
|
|
14
|
-
pytest_jsonschema_snapshot-0.2.4.dist-info/licenses/LICENSE,sha256=1HRFdSzlJ4BtHv6U7tZun3iCArjbCnm5NUowE9hZpNs,1071
|
|
15
|
-
pytest_jsonschema_snapshot-0.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|