json-schema-engine 0.0.1__tar.gz → 0.0.3__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.
- json_schema_engine-0.0.3/CHANGELOG.md +77 -0
- json_schema_engine-0.0.3/PKG-INFO +329 -0
- json_schema_engine-0.0.3/README.md +302 -0
- json_schema_engine-0.0.3/pyproject.toml +175 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/__init__.py +250 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/emit.py +332 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/errors.py +16 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/plan.py +780 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/py.typed +0 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/runtime.py +227 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/runtime_compile.py +37 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/serialize/__init__.py +276 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/serialize/body.py +1116 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/serialize/context.py +230 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/serialize/units.py +221 -0
- json_schema_engine-0.0.3/src/json_schema_engine/compiler/standalone.py +155 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/__init__.py +158 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/channel.py +172 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/channel_ops.py +152 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/coverage.py +58 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/cursor.py +78 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/dialect.py +502 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/engine.py +510 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/errors.py +191 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/evaluator.py +631 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/formats.py +50 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/json_model.py +250 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/__init__.py +1 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/_ids.py +54 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/applicator.py +360 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/applicator_array.py +357 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/applicator_object.py +359 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/content.py +62 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/core.py +232 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/dialect2019.py +163 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/dialect2020.py +88 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/dialect7.py +189 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/dialects.py +30 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/format.py +151 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/legacy.py +378 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/meta_data.py +20 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/unevaluated.py +319 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/validation.py +656 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/loader.py +96 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/lowering.py +688 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/applicator.json +53 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/content.json +14 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/core.json +54 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/format.json +11 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/meta-data.json +34 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/schema.json +42 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/validation.json +95 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/applicator.json +45 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/content.json +14 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/core.json +48 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/format-annotation.json +11 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/format-assertion.json +11 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/meta-data.json +34 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/schema.json +58 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/unevaluated.json +12 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/validation.json +95 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/__init__.py +61 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/draft-06/schema.json +155 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/draft-07/schema.json +172 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/output.py +640 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/positions.py +287 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/py.typed +0 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/records.py +189 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/ref.py +35 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/regex.py +114 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/registry.py +554 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/result.py +258 -0
- json_schema_engine-0.0.3/src/json_schema_engine/core/uri.py +204 -0
- json_schema_engine-0.0.3/src/json_schema_engine/formats/__init__.py +136 -0
- json_schema_engine-0.0.3/src/json_schema_engine/formats/_abnf.py +70 -0
- json_schema_engine-0.0.3/src/json_schema_engine/formats/datetime_.py +161 -0
- json_schema_engine-0.0.3/src/json_schema_engine/formats/idna_.py +105 -0
- json_schema_engine-0.0.3/src/json_schema_engine/formats/misc.py +62 -0
- json_schema_engine-0.0.3/src/json_schema_engine/formats/net.py +165 -0
- json_schema_engine-0.0.3/src/json_schema_engine/formats/pointer.py +57 -0
- json_schema_engine-0.0.3/src/json_schema_engine/formats/py.typed +0 -0
- json_schema_engine-0.0.3/src/json_schema_engine/formats/uri.py +129 -0
- json_schema_engine-0.0.1/PKG-INFO +0 -48
- json_schema_engine-0.0.1/README.md +0 -26
- json_schema_engine-0.0.1/pyproject.toml +0 -50
- json_schema_engine-0.0.1/src/json_schema_engine/core/__init__.py +0 -7
- json_schema_engine-0.0.1/tests/test_import.py +0 -12
- {json_schema_engine-0.0.1 → json_schema_engine-0.0.3}/.gitignore +0 -0
- {json_schema_engine-0.0.1 → json_schema_engine-0.0.3}/LICENSE +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `json-schema-engine` (the Python engine). The
|
|
4
|
+
format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
5
|
+
versions follow [SemVer](https://semver.org/) with the 0.x caveat that
|
|
6
|
+
minor versions may change public API.
|
|
7
|
+
|
|
8
|
+
## [0.0.3] - 2026-09-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `compile_evaluator`: compiles a registered root schema into an evaluator
|
|
13
|
+
serving every output format the interpreter does (errors, annotations,
|
|
14
|
+
dropped records, the application trace), not only the verdict `flag`
|
|
15
|
+
level.
|
|
16
|
+
- Plan-time resolution of `$dynamicRef`/`$recursiveRef`: a reference site
|
|
17
|
+
whose target is the same on every path that can reach it compiles as an
|
|
18
|
+
ordinary static edge instead of falling back to the interpreter;
|
|
19
|
+
`explain_compilation` reports such sites through `resolved_dynamic_sites`.
|
|
20
|
+
- Runtime coverage tracking: an `unevaluated*` consumer whose evaluated
|
|
21
|
+
coverage depends on runtime branching (an `anyOf`/`oneOf` alternative, an
|
|
22
|
+
`if`'s condition) compiles directly instead of islanding, folding a
|
|
23
|
+
runtime coverage channel instead of a static licence.
|
|
24
|
+
- Public `json_schema_engine.core.lowering`: the compiler lowering IR a
|
|
25
|
+
custom keyword's `lower()` is built from, previously private to the
|
|
26
|
+
engine's own keyword modules.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `first_duplicate_pair` returns a list.
|
|
31
|
+
- Compiled error messages now match the interpreter's for `oneOf`,
|
|
32
|
+
`contains`, `uniqueItems`, and `type`.
|
|
33
|
+
- A dialect refusing unknown keywords no longer compiles those units
|
|
34
|
+
silently: an unknown keyword under such a dialect now falls back to the
|
|
35
|
+
interpreter like any other unlowerable case, rather than being planned
|
|
36
|
+
as if the keyword were absent.
|
|
37
|
+
|
|
38
|
+
## [0.0.2] - 2026-09-21
|
|
39
|
+
|
|
40
|
+
The first functional release. Everything below is new relative to the
|
|
41
|
+
0.0.1 name reservation.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- `json_schema_engine.core`: the interpreter tier. Draft 2020-12 (default),
|
|
46
|
+
2019-09, draft-07, and draft-06 with each draft's own semantics
|
|
47
|
+
(`$dynamicRef`, `$recursiveRef`, `$ref` sibling handling); `$vocabulary`
|
|
48
|
+
processing with the bundled metaschemas; loaders for remote references
|
|
49
|
+
with source-position reporting (`parse_json_with_ranges`,
|
|
50
|
+
`positions=True`, `Engine.locate`); metaschema validation on request
|
|
51
|
+
(`validate_schemas=True`); the `flag`, `basic`, `detailed`, `verbose`,
|
|
52
|
+
`list`, and `hierarchical` output formats with annotation selection,
|
|
53
|
+
error params, verbose levels, and the application trace; ECMA-262
|
|
54
|
+
regular expressions through `ecma-regex`, with a Python `re`
|
|
55
|
+
passthrough dialect and the optional `regex` backend; the
|
|
56
|
+
`reject_unsafe_regex` screen, `max_depth`, and O(n) `uniqueItems`.
|
|
57
|
+
- `json_schema_engine.compiler`: `compile_validator` (a verdict-only
|
|
58
|
+
Python function bound to a registry snapshot, trampolining into the
|
|
59
|
+
interpreter for whatever it cannot lower), `emit_standalone` (a module
|
|
60
|
+
importable without the compiler), and `explain_compilation`.
|
|
61
|
+
- `json_schema_engine.formats`: every format the four drafts define,
|
|
62
|
+
implemented from the RFCs and verified against the official
|
|
63
|
+
`optional/format` suites; the `idna` extra for IDNA2008
|
|
64
|
+
(`idn-hostname`, A-label checks in `hostname`); assertion opt-in through
|
|
65
|
+
`create_engine(formats=..., assert_formats=...)` and the 2020-12
|
|
66
|
+
format-assertion vocabulary.
|
|
67
|
+
- Every distribution portion ships a `py.typed` marker.
|
|
68
|
+
|
|
69
|
+
### Changed
|
|
70
|
+
|
|
71
|
+
- The distribution depends on `ecma-regex>=0.1,<0.2`.
|
|
72
|
+
- The sdist no longer carries the test tree, which needs the repository's
|
|
73
|
+
test-kit and the suite submodule.
|
|
74
|
+
|
|
75
|
+
## [0.0.1] - 2026-09-20
|
|
76
|
+
|
|
77
|
+
Name reservation on PyPI; no functionality.
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: json-schema-engine
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: A spec-complete, annotation-first JSON Schema engine for Python: interpreter, compiler, formats, every standard output format.
|
|
5
|
+
Project-URL: Homepage, https://github.com/handrews/py-json-schema-engine
|
|
6
|
+
Project-URL: Repository, https://github.com/handrews/py-json-schema-engine
|
|
7
|
+
Project-URL: Issues, https://github.com/handrews/py-json-schema-engine/issues
|
|
8
|
+
Author: Henry Andrews
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: json,json-schema,validation
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: ecma-regex<0.2,>=0.1
|
|
22
|
+
Provides-Extra: idna
|
|
23
|
+
Requires-Dist: idna>=3.7; extra == 'idna'
|
|
24
|
+
Provides-Extra: regex
|
|
25
|
+
Requires-Dist: regex; extra == 'regex'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# json-schema-engine (Python)
|
|
29
|
+
|
|
30
|
+
A JSON Schema implementation for Python that aims to be both spec-complete
|
|
31
|
+
and built for speed: an interpreter that is the reference semantics, a
|
|
32
|
+
compiler tier for hot paths, full annotation collection, and every standard
|
|
33
|
+
output format. It is the Python counterpart of
|
|
34
|
+
[handrews/json-schema-engine](https://github.com/handrews/json-schema-engine)
|
|
35
|
+
and shares its architecture: two tiers, one keyword registry, a frame-scoped
|
|
36
|
+
record channel, and the new (2026)
|
|
37
|
+
[IETF working group draft-03](https://www.ietf.org/archive/id/draft-ietf-jsonschema-json-schema-03.html)
|
|
38
|
+
relevance model.
|
|
39
|
+
|
|
40
|
+
Produced by Henry Andrews via Claude Code.
|
|
41
|
+
|
|
42
|
+
**Status: `0.0.3` is fully compliant except for draft-04 support.**
|
|
43
|
+
|
|
44
|
+
See [CHANGELOG.md](CHANGELOG.md) for the current release's contents.
|
|
45
|
+
[DESIGN.md](DESIGN.md) is the design contract and carries the milestone
|
|
46
|
+
status.
|
|
47
|
+
|
|
48
|
+
All `0.0.x` releases will have AI-written documentation. Version
|
|
49
|
+
`0.1.0` will indicate that the documentation has been audited and revised
|
|
50
|
+
by a human.
|
|
51
|
+
|
|
52
|
+
The regular-expression translator lives in its own package,
|
|
53
|
+
[`ecma-regex`](packages/ecma-regex/README.md) (`0.1.0`): ECMA-262 patterns
|
|
54
|
+
for Python, with JavaScript semantics, no dependency on this engine.
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
pip install json-schema-engine
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Python 3.12+. Extras: `[idna]` for IDNA2008 support (`idn-hostname`, and the
|
|
63
|
+
`xn--` label check inside `hostname`); `[regex]` to swap in the `regex`
|
|
64
|
+
package as the pattern backend.
|
|
65
|
+
|
|
66
|
+
## Use
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from json_schema_engine.core import create_engine
|
|
70
|
+
|
|
71
|
+
engine = create_engine()
|
|
72
|
+
uri = engine.register_schema(
|
|
73
|
+
{
|
|
74
|
+
"type": "object",
|
|
75
|
+
"properties": {"name": {"type": "string"}},
|
|
76
|
+
"required": ["name"],
|
|
77
|
+
},
|
|
78
|
+
"https://example.com/person",
|
|
79
|
+
)
|
|
80
|
+
assert engine.evaluate(uri, {"name": "Ada"}).valid
|
|
81
|
+
result = engine.evaluate(uri, {}, output="list")
|
|
82
|
+
assert result.errors is not None and result.errors[0]["evaluationPath"] == "/required"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Four drafts are built in: 2020-12 (the default), 2019-09, draft-07, and
|
|
86
|
+
draft-06. A document's `$schema` selects its dialect; `create_engine(
|
|
87
|
+
default_dialect=DIALECT_DRAFT_07)` sets the dialect for documents without
|
|
88
|
+
one. Each keeps its own semantics, so a draft-07 `$ref` ignores its
|
|
89
|
+
siblings while a 2019-09 one does not.
|
|
90
|
+
|
|
91
|
+
Every standard output format is available by name: `flag` (the default),
|
|
92
|
+
`basic`, `detailed`, and `verbose` from the
|
|
93
|
+
[IETF working group draft-03](https://www.ietf.org/archive/id/draft-ietf-jsonschema-json-schema-03.html)
|
|
94
|
+
output sections, and `list` and `hierarchical` from the
|
|
95
|
+
[machines-oriented proposal](https://github.com/json-schema-org/json-schema-spec/blob/main/specs/output/jsonschema-validation-output-machines.md).
|
|
96
|
+
Annotations are a separate control (`annotations=True`, or an
|
|
97
|
+
`AnnotationSelection`), `verbose=True` asks `list`/`hierarchical` for the
|
|
98
|
+
verbose level with irrelevant records marked as dropped, and `trace=True`
|
|
99
|
+
adds the application tree with error indexes into `result.errors`.
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
result = engine.evaluate(uri, {"name": 3}, output="hierarchical")
|
|
103
|
+
assert result.output_document == {
|
|
104
|
+
"valid": False,
|
|
105
|
+
"evaluationPath": "",
|
|
106
|
+
"schemaLocation": "https://example.com/person#",
|
|
107
|
+
"instanceLocation": "",
|
|
108
|
+
"details": [
|
|
109
|
+
{
|
|
110
|
+
"valid": False,
|
|
111
|
+
"evaluationPath": "/properties/name",
|
|
112
|
+
"schemaLocation": "https://example.com/person#/properties/name",
|
|
113
|
+
"instanceLocation": "/name",
|
|
114
|
+
"errors": {"type": "expected string"},
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`create_engine(validate_schemas=True)` checks every registered document
|
|
121
|
+
against its metaschema and raises `SchemaValidationError` with the errors.
|
|
122
|
+
A loader that reports source positions (see `parse_json_with_ranges`,
|
|
123
|
+
exported from `json_schema_engine.core`) lets `evaluate(..., positions=True)`
|
|
124
|
+
attach a `source` location to every error and annotation, and
|
|
125
|
+
`engine.locate()` answers the same question for any schema location.
|
|
126
|
+
|
|
127
|
+
## Compile
|
|
128
|
+
|
|
129
|
+
The compiler tier turns a registered schema into a Python function. It is
|
|
130
|
+
not a second implementation: any subschema it cannot emit (a `$dynamicRef`
|
|
131
|
+
whose target differs by path, an in-place cycle) calls back into the
|
|
132
|
+
interpreter, so a compiled validator is exactly
|
|
133
|
+
as correct as `Engine.evaluate` and never less complete. Tier choice is a
|
|
134
|
+
performance decision, not a semantic one.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from json_schema_engine.compiler import compile_validator, emit_standalone
|
|
138
|
+
|
|
139
|
+
compiled = compile_validator(engine, uri)
|
|
140
|
+
assert compiled.validate({"name": "Ada"}) is True
|
|
141
|
+
assert compiled.validate({}) is False
|
|
142
|
+
print(compiled.source) # the emitted module, for reading
|
|
143
|
+
module_source = emit_standalone(engine, uri) # importable without the compiler
|
|
144
|
+
assert "def validate" in module_source
|
|
145
|
+
assert "import json_schema_engine.compiler" not in module_source
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`compile_validator` returns a verdict-only validator (the flag level) that
|
|
149
|
+
binds a snapshot of the registries at compile time, so register everything
|
|
150
|
+
first. `emit_standalone` writes the same code as a module that imports
|
|
151
|
+
only the standard library and this package's pure helpers; it refuses,
|
|
152
|
+
with `StandaloneUnsupportedError`, a schema that would need the
|
|
153
|
+
interpreter at evaluation time. Compiled code assumes plain data as
|
|
154
|
+
`json.loads` produces it (`dict`, `list`, `str`, `int`, `float`, `bool`,
|
|
155
|
+
`None`); subclasses of those types belong to the interpreter.
|
|
156
|
+
|
|
157
|
+
`compile_evaluator` compiles a schema into an evaluator serving every
|
|
158
|
+
output format the interpreter does — errors, annotations, dropped
|
|
159
|
+
records, and the application trace, not only the verdict. The annotation
|
|
160
|
+
selection is fixed at compile time; every other control (`output`,
|
|
161
|
+
`error_params`, `verbose`, `trace`, `positions`) is chosen per call, same
|
|
162
|
+
as `Engine.evaluate`.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from json_schema_engine.compiler import compile_evaluator
|
|
166
|
+
|
|
167
|
+
evaluator = compile_evaluator(engine, uri, annotations=True)
|
|
168
|
+
compiled_result = evaluator.evaluate({}, output="list", error_params=True)
|
|
169
|
+
interpreted_result = engine.evaluate(
|
|
170
|
+
uri, {}, output="list", error_params=True, annotations=True
|
|
171
|
+
)
|
|
172
|
+
assert compiled_result.errors == interpreted_result.errors
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Formats
|
|
176
|
+
|
|
177
|
+
`format` annotates by default in every dialect (the specs' default, and
|
|
178
|
+
what the official `format.json` legs require). Assertion is opt-in, from a
|
|
179
|
+
format table implemented from each format's RFC and verified against the
|
|
180
|
+
official `optional/format` suite:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from json_schema_engine.core import create_engine
|
|
184
|
+
from json_schema_engine.formats import FORMATS_2020_12, format_table_for
|
|
185
|
+
|
|
186
|
+
# The 2020-12 format-assertion vocabulary: a metaschema declaring it makes
|
|
187
|
+
# `format` assert; names the table lacks are refused at registration.
|
|
188
|
+
engine = create_engine(formats=FORMATS_2020_12)
|
|
189
|
+
# Best effort in every standard dialect: known names assert, unknown names
|
|
190
|
+
# annotate only.
|
|
191
|
+
engine = create_engine(formats=FORMATS_2020_12, assert_formats=True)
|
|
192
|
+
uri = engine.register_schema({"format": "date-time"}, "https://example.com/dt")
|
|
193
|
+
assert engine.evaluate(uri, "1998-12-31T23:59:60Z").valid
|
|
194
|
+
assert not engine.evaluate(uri, "1998-12-31T22:59:60Z").valid
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`FORMATS_2020_12` (also 2019-09) carries the nineteen defined formats;
|
|
198
|
+
`FORMATS_DRAFT_07` and `FORMATS_DRAFT_06` carry each draft's list, and
|
|
199
|
+
`format_table_for(dialect_uri)` picks one. A metaschema that declares the
|
|
200
|
+
format-assertion vocabulary on an engine without a table raises
|
|
201
|
+
`FormatsRequiredError`, and `assert_formats=True` without a table does too.
|
|
202
|
+
A custom table is any mapping of names to `FormatDefinition(test, types)`;
|
|
203
|
+
`types` scopes a format to instance types other than strings.
|
|
204
|
+
|
|
205
|
+
`idn-hostname` and the A-label checks inside `hostname` need IDNA2008,
|
|
206
|
+
provided by the `idna` extra:
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
pip install 'json-schema-engine[idna]'
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Without it, asserting `idn-hostname` raises `FormatUnavailableError` at
|
|
213
|
+
registration, and `hostname` accepts a well-formed `xn--` label without
|
|
214
|
+
decoding it. Compiled validators assert formats too; a standalone module
|
|
215
|
+
imports the predicates it needs from `json_schema_engine.formats`, so the
|
|
216
|
+
extra must be installed wherever such a module runs.
|
|
217
|
+
|
|
218
|
+
## Documentation
|
|
219
|
+
|
|
220
|
+
- [User guide](docs/guide/index.md) — validation, output formats,
|
|
221
|
+
annotations, dialects, loaders, source positions, metaschemas, custom
|
|
222
|
+
keywords, security, compiling schemas, and formats, topic by topic.
|
|
223
|
+
- [API reference](docs/reference.md) — every public name, by package.
|
|
224
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) — setup, gates, and conventions.
|
|
225
|
+
- [CHANGELOG.md](CHANGELOG.md) — what changed, release by release.
|
|
226
|
+
- [DESIGN.md](DESIGN.md) — the engineering design contract and milestone
|
|
227
|
+
status.
|
|
228
|
+
|
|
229
|
+
## Security
|
|
230
|
+
|
|
231
|
+
Schemas and instances are both often untrusted input. The interpreter
|
|
232
|
+
generates no code — there is no `compile()` or `exec()` on its path — so
|
|
233
|
+
code-injection concerns do not apply to it; a denial-of-service bound is
|
|
234
|
+
best effort, not a guarantee, so treat wildly untrusted schemas with the
|
|
235
|
+
same care as any other untrusted program input. Three specific vectors have
|
|
236
|
+
a bound or an opt-out.
|
|
237
|
+
|
|
238
|
+
**Regular expressions (ReDoS).** `pattern` and `patternProperties` compile
|
|
239
|
+
untrusted regexes and run them against untrusted strings; Python's `re` can
|
|
240
|
+
backtrack catastrophically on a pattern like `(a+)+$`. `reject_unsafe_regex`
|
|
241
|
+
screens for nested unbounded quantifiers at registration:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from json_schema_engine.core import create_engine, UnsafeRegexError
|
|
245
|
+
|
|
246
|
+
engine = create_engine(reject_unsafe_regex=True)
|
|
247
|
+
try:
|
|
248
|
+
engine.register_schema({"pattern": "(a+)+$"}, "https://ex/redos")
|
|
249
|
+
except UnsafeRegexError:
|
|
250
|
+
pass # rejected before it ever runs
|
|
251
|
+
else:
|
|
252
|
+
raise AssertionError("expected UnsafeRegexError")
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Patterns are ECMA-262 by default, translated by the `ecma-regex` package to
|
|
256
|
+
the `re` backend (a `regex` backend is available via the `regex` extra);
|
|
257
|
+
neither backend is linear-time, so the screen is a heuristic, not a proof.
|
|
258
|
+
`regex_dialect="python"` hands patterns to `re` untouched, for schemas
|
|
259
|
+
written for Python only.
|
|
260
|
+
|
|
261
|
+
**Recursion depth.** `max_depth` (default 512) bounds both registration
|
|
262
|
+
nesting and evaluation nesting, raising the typed `MaxDepthExceededError`
|
|
263
|
+
before CPython's own stack limit can produce an untyped `RecursionError`; a
|
|
264
|
+
stray `RecursionError` that does slip through is still converted to the
|
|
265
|
+
same typed error. The engine stays usable afterward — each `evaluate` or
|
|
266
|
+
`register_schema` call runs in fresh state, so a rejected document does not
|
|
267
|
+
poison later calls.
|
|
268
|
+
|
|
269
|
+
**Array uniqueness.** `uniqueItems` compares elements in O(n) by bucketing
|
|
270
|
+
on a canonical key and confirming collisions with full JSON equality, so
|
|
271
|
+
large arrays of distinct values do not incur quadratic cost, while genuine
|
|
272
|
+
duplicates — including numbers equal across `int`/`float` and objects that
|
|
273
|
+
differ only in member order — are still reported.
|
|
274
|
+
|
|
275
|
+
## Development
|
|
276
|
+
|
|
277
|
+
```sh
|
|
278
|
+
uv sync --all-packages --all-groups
|
|
279
|
+
uv run pytest -q
|
|
280
|
+
uv run ruff check . && uv run ruff format --check .
|
|
281
|
+
uv run pyright
|
|
282
|
+
uv run lint-imports
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The official test suite is a git submodule at `test-suite/`; clone with
|
|
286
|
+
`--recurse-submodules` or run `git submodule update --init`.
|
|
287
|
+
|
|
288
|
+
The [Bowtie](https://bowtie.report) conformance leg builds a container
|
|
289
|
+
image and runs the suite through Bowtie's harness protocol. It needs a
|
|
290
|
+
reachable container engine (Docker, or `podman machine start`) and fetches
|
|
291
|
+
Bowtie through `uvx`:
|
|
292
|
+
|
|
293
|
+
```sh
|
|
294
|
+
uv run python scripts/bowtie_check.py
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Benchmarks
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
uv run python scripts/bench.py --budget-ms 250 --filter user
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
`scripts/bench.py` times every jse tier (the interpreter, the compiler's
|
|
304
|
+
flag validator and evaluator, and the standalone artifact) against two
|
|
305
|
+
competitors (fastjsonschema, jsonschema) over seven corpora in
|
|
306
|
+
`packages/bench`: three small hand-authored schemas, the official OpenAPI
|
|
307
|
+
3.1 schema against a real document, a generated API-payload corpus, and
|
|
308
|
+
two 2000-record corpora. Every jse tier is timed against every corpus,
|
|
309
|
+
`jse standalone` against `oas-document` included, since its `$dynamicRef`
|
|
310
|
+
sites resolve at plan time and leave no interpreted unit. Two subjects
|
|
311
|
+
measure the record-producing tier against the verdict-only tiers:
|
|
312
|
+
`jse interpreter list` and `jse compiled evaluator (list)`, both timing
|
|
313
|
+
`output="list"`. The bench is report-only (it enforces no performance
|
|
314
|
+
threshold) and, per the IP policy below, runs the competitors only, never
|
|
315
|
+
reading or porting their source. `--filter` takes a regex over
|
|
316
|
+
corpus/subject/partition names; omit `--out` to skip writing JSON;
|
|
317
|
+
`--compare BEFORE AFTER` prints a before/after comparison of two results
|
|
318
|
+
files. The committed run lives at `packages/bench/results/results.json`
|
|
319
|
+
(`--budget-ms 250`). The interpreter is the reference semantics, so ratios
|
|
320
|
+
are informational, not a compatibility claim.
|
|
321
|
+
[`packages/bench/README.md`](packages/bench/README.md) covers corpus
|
|
322
|
+
provenance and licensing, methodology, and the recorded exclusions.
|
|
323
|
+
|
|
324
|
+
## IP policy
|
|
325
|
+
|
|
326
|
+
The implementation is written from the JSON Schema specifications and the
|
|
327
|
+
official test suite only. Other validators are executed as benchmark subjects
|
|
328
|
+
and correctness oracles; their source is never used as an implementation
|
|
329
|
+
reference. See [DESIGN.md](DESIGN.md) §0.
|