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.
Files changed (89) hide show
  1. json_schema_engine-0.0.3/CHANGELOG.md +77 -0
  2. json_schema_engine-0.0.3/PKG-INFO +329 -0
  3. json_schema_engine-0.0.3/README.md +302 -0
  4. json_schema_engine-0.0.3/pyproject.toml +175 -0
  5. json_schema_engine-0.0.3/src/json_schema_engine/compiler/__init__.py +250 -0
  6. json_schema_engine-0.0.3/src/json_schema_engine/compiler/emit.py +332 -0
  7. json_schema_engine-0.0.3/src/json_schema_engine/compiler/errors.py +16 -0
  8. json_schema_engine-0.0.3/src/json_schema_engine/compiler/plan.py +780 -0
  9. json_schema_engine-0.0.3/src/json_schema_engine/compiler/py.typed +0 -0
  10. json_schema_engine-0.0.3/src/json_schema_engine/compiler/runtime.py +227 -0
  11. json_schema_engine-0.0.3/src/json_schema_engine/compiler/runtime_compile.py +37 -0
  12. json_schema_engine-0.0.3/src/json_schema_engine/compiler/serialize/__init__.py +276 -0
  13. json_schema_engine-0.0.3/src/json_schema_engine/compiler/serialize/body.py +1116 -0
  14. json_schema_engine-0.0.3/src/json_schema_engine/compiler/serialize/context.py +230 -0
  15. json_schema_engine-0.0.3/src/json_schema_engine/compiler/serialize/units.py +221 -0
  16. json_schema_engine-0.0.3/src/json_schema_engine/compiler/standalone.py +155 -0
  17. json_schema_engine-0.0.3/src/json_schema_engine/core/__init__.py +158 -0
  18. json_schema_engine-0.0.3/src/json_schema_engine/core/channel.py +172 -0
  19. json_schema_engine-0.0.3/src/json_schema_engine/core/channel_ops.py +152 -0
  20. json_schema_engine-0.0.3/src/json_schema_engine/core/coverage.py +58 -0
  21. json_schema_engine-0.0.3/src/json_schema_engine/core/cursor.py +78 -0
  22. json_schema_engine-0.0.3/src/json_schema_engine/core/dialect.py +502 -0
  23. json_schema_engine-0.0.3/src/json_schema_engine/core/engine.py +510 -0
  24. json_schema_engine-0.0.3/src/json_schema_engine/core/errors.py +191 -0
  25. json_schema_engine-0.0.3/src/json_schema_engine/core/evaluator.py +631 -0
  26. json_schema_engine-0.0.3/src/json_schema_engine/core/formats.py +50 -0
  27. json_schema_engine-0.0.3/src/json_schema_engine/core/json_model.py +250 -0
  28. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/__init__.py +1 -0
  29. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/_ids.py +54 -0
  30. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/applicator.py +360 -0
  31. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/applicator_array.py +357 -0
  32. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/applicator_object.py +359 -0
  33. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/content.py +62 -0
  34. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/core.py +232 -0
  35. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/dialect2019.py +163 -0
  36. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/dialect2020.py +88 -0
  37. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/dialect7.py +189 -0
  38. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/dialects.py +30 -0
  39. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/format.py +151 -0
  40. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/legacy.py +378 -0
  41. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/meta_data.py +20 -0
  42. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/unevaluated.py +319 -0
  43. json_schema_engine-0.0.3/src/json_schema_engine/core/keywords/validation.py +656 -0
  44. json_schema_engine-0.0.3/src/json_schema_engine/core/loader.py +96 -0
  45. json_schema_engine-0.0.3/src/json_schema_engine/core/lowering.py +688 -0
  46. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/applicator.json +53 -0
  47. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/content.json +14 -0
  48. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/core.json +54 -0
  49. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/format.json +11 -0
  50. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/meta-data.json +34 -0
  51. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/schema.json +42 -0
  52. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2019-09/validation.json +95 -0
  53. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/applicator.json +45 -0
  54. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/content.json +14 -0
  55. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/core.json +48 -0
  56. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/format-annotation.json +11 -0
  57. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/format-assertion.json +11 -0
  58. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/meta-data.json +34 -0
  59. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/schema.json +58 -0
  60. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/unevaluated.json +12 -0
  61. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/2020-12/validation.json +95 -0
  62. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/__init__.py +61 -0
  63. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/draft-06/schema.json +155 -0
  64. json_schema_engine-0.0.3/src/json_schema_engine/core/metaschemas/draft-07/schema.json +172 -0
  65. json_schema_engine-0.0.3/src/json_schema_engine/core/output.py +640 -0
  66. json_schema_engine-0.0.3/src/json_schema_engine/core/positions.py +287 -0
  67. json_schema_engine-0.0.3/src/json_schema_engine/core/py.typed +0 -0
  68. json_schema_engine-0.0.3/src/json_schema_engine/core/records.py +189 -0
  69. json_schema_engine-0.0.3/src/json_schema_engine/core/ref.py +35 -0
  70. json_schema_engine-0.0.3/src/json_schema_engine/core/regex.py +114 -0
  71. json_schema_engine-0.0.3/src/json_schema_engine/core/registry.py +554 -0
  72. json_schema_engine-0.0.3/src/json_schema_engine/core/result.py +258 -0
  73. json_schema_engine-0.0.3/src/json_schema_engine/core/uri.py +204 -0
  74. json_schema_engine-0.0.3/src/json_schema_engine/formats/__init__.py +136 -0
  75. json_schema_engine-0.0.3/src/json_schema_engine/formats/_abnf.py +70 -0
  76. json_schema_engine-0.0.3/src/json_schema_engine/formats/datetime_.py +161 -0
  77. json_schema_engine-0.0.3/src/json_schema_engine/formats/idna_.py +105 -0
  78. json_schema_engine-0.0.3/src/json_schema_engine/formats/misc.py +62 -0
  79. json_schema_engine-0.0.3/src/json_schema_engine/formats/net.py +165 -0
  80. json_schema_engine-0.0.3/src/json_schema_engine/formats/pointer.py +57 -0
  81. json_schema_engine-0.0.3/src/json_schema_engine/formats/py.typed +0 -0
  82. json_schema_engine-0.0.3/src/json_schema_engine/formats/uri.py +129 -0
  83. json_schema_engine-0.0.1/PKG-INFO +0 -48
  84. json_schema_engine-0.0.1/README.md +0 -26
  85. json_schema_engine-0.0.1/pyproject.toml +0 -50
  86. json_schema_engine-0.0.1/src/json_schema_engine/core/__init__.py +0 -7
  87. json_schema_engine-0.0.1/tests/test_import.py +0 -12
  88. {json_schema_engine-0.0.1 → json_schema_engine-0.0.3}/.gitignore +0 -0
  89. {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.