corvus-json-schema 0.1.0__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.
- corvus_json_schema/__init__.py +260 -0
- corvus_json_schema/_metaschemas.py +27 -0
- corvus_json_schema/cli.py +95 -0
- corvus_json_schema/codegen.py +1864 -0
- corvus_json_schema/collecting.py +743 -0
- corvus_json_schema/compiler.py +803 -0
- corvus_json_schema/dialect.py +104 -0
- corvus_json_schema/formats.py +693 -0
- corvus_json_schema/loader.py +345 -0
- corvus_json_schema/node.py +325 -0
- corvus_json_schema/options.py +60 -0
- corvus_json_schema/pattern.py +522 -0
- corvus_json_schema/py.typed +0 -0
- corvus_json_schema/results.py +213 -0
- corvus_json_schema/runtime.py +212 -0
- corvus_json_schema/uri.py +174 -0
- corvus_json_schema-0.1.0.dist-info/METADATA +245 -0
- corvus_json_schema-0.1.0.dist-info/RECORD +21 -0
- corvus_json_schema-0.1.0.dist-info/WHEEL +4 -0
- corvus_json_schema-0.1.0.dist-info/entry_points.txt +2 -0
- corvus_json_schema-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""A high-performance JSON Schema evaluator (draft 4, 6, 7, 2019-09 and 2020-12) that compiles schemas to
|
|
2
|
+
specialised Python, with results collection and annotations. A port of the Corvus.Text.Json V5 runtime evaluator, by
|
|
3
|
+
way of its TypeScript port.
|
|
4
|
+
|
|
5
|
+
>>> import corvus_json_schema as cjs
|
|
6
|
+
>>> validate = cjs.compile({"type": "object", "required": ["id"], "properties": {"id": {"type": "integer"}}})
|
|
7
|
+
>>> validate({"id": 3})
|
|
8
|
+
True
|
|
9
|
+
>>> validate({"id": "3"})
|
|
10
|
+
False
|
|
11
|
+
|
|
12
|
+
Instances are the values ``json.loads`` produces (``dict``, ``list``, ``str``, ``int``, ``float``, ``bool`` and
|
|
13
|
+
``None``); a validator tests their exact types, so subclasses such as ``OrderedDict`` are not objects to it.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import builtins
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import threading
|
|
23
|
+
import types
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
from dataclasses import replace
|
|
26
|
+
from typing import Any, Protocol, cast
|
|
27
|
+
|
|
28
|
+
from . import runtime
|
|
29
|
+
from .codegen import CodeGenerator, GeneratedCode
|
|
30
|
+
from .collecting import CollectingProgram, evaluate_with_collector, serialize_program
|
|
31
|
+
from .compiler import CompiledSchema, SchemaCompiler, resolve_annotations
|
|
32
|
+
from .dialect import Dialect
|
|
33
|
+
from .options import (
|
|
34
|
+
CompileOptions,
|
|
35
|
+
DocumentResolver,
|
|
36
|
+
FormatValidator,
|
|
37
|
+
SchemaCompilationError,
|
|
38
|
+
SchemaEvaluationDepthError,
|
|
39
|
+
)
|
|
40
|
+
from .results import (
|
|
41
|
+
Annotation,
|
|
42
|
+
JsonSchemaResultsCollector,
|
|
43
|
+
ResultsLevel,
|
|
44
|
+
SchemaResult,
|
|
45
|
+
collect_annotations,
|
|
46
|
+
enumerate_annotations,
|
|
47
|
+
schema_location_fragment,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__version__ = "0.1.0"
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"Annotation",
|
|
54
|
+
"CompileOptions",
|
|
55
|
+
"Dialect",
|
|
56
|
+
"DocumentResolver",
|
|
57
|
+
"FormatValidator",
|
|
58
|
+
"JsonSchemaResultsCollector",
|
|
59
|
+
"ResultsLevel",
|
|
60
|
+
"SchemaCompilationError",
|
|
61
|
+
"SchemaEvaluationDepthError",
|
|
62
|
+
"SchemaResult",
|
|
63
|
+
"Validator",
|
|
64
|
+
"collect_annotations",
|
|
65
|
+
"compile",
|
|
66
|
+
"enumerate_annotations",
|
|
67
|
+
"generate_module",
|
|
68
|
+
"schema_location_fragment",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Validator(Protocol):
|
|
73
|
+
"""A compiled validator: returns True when the instance is valid against the schema."""
|
|
74
|
+
|
|
75
|
+
def __call__(self, instance: Any) -> bool: ...
|
|
76
|
+
|
|
77
|
+
def evaluate(self, instance: Any, collector: JsonSchemaResultsCollector | None = None) -> bool:
|
|
78
|
+
"""Evaluates the instance, reporting results to the collector when one is given (every keyword is evaluated
|
|
79
|
+
and reported, at the collector's level); without a collector this is the validator itself."""
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def source(self) -> str:
|
|
84
|
+
"""The generated Python (the body of the validator's module)."""
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _options(options: CompileOptions | None, overrides: Mapping[str, Any]) -> CompileOptions:
|
|
89
|
+
base = options if options is not None else CompileOptions()
|
|
90
|
+
return replace(base, **overrides) if overrides else base
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _parse_schema(schema: Any) -> Any:
|
|
94
|
+
return json.loads(schema) if isinstance(schema, (str, bytes, bytearray)) else schema
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _generate(schema: Any, options: CompileOptions) -> tuple[GeneratedCode, CompiledSchema]:
|
|
98
|
+
program = SchemaCompiler.compile(_parse_schema(schema), options)
|
|
99
|
+
return CodeGenerator(program).generate(), program
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _collecting_program(program: CompiledSchema) -> CollectingProgram:
|
|
103
|
+
resolve_annotations(program)
|
|
104
|
+
return CollectingProgram(
|
|
105
|
+
program.nodes, program.root, program.uses_dynamic_scope, program.options.max_depth, program.options.formats
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _assemble(g: GeneratedCode, functions: bool = True) -> str:
|
|
110
|
+
s = ""
|
|
111
|
+
if g.uses_dynamic_scope:
|
|
112
|
+
s += "DS = []\n"
|
|
113
|
+
if g.uses_depth:
|
|
114
|
+
s += f"depth = 0\nMAXDEPTH = {g.max_depth}\n"
|
|
115
|
+
s += (g.declarations if functions else g.constants) + "\n"
|
|
116
|
+
if g.uses_dynamic_scope or g.uses_depth:
|
|
117
|
+
# The dynamic scope and the depth count are module state: one evaluation at a time (re-entrant, for a custom
|
|
118
|
+
# format that validates with the same validator).
|
|
119
|
+
s += "LOCK = THREADING.RLock()\n"
|
|
120
|
+
s += "def validate(x):\n"
|
|
121
|
+
s += " global depth\n" if g.uses_depth else ""
|
|
122
|
+
s += " with LOCK:\n"
|
|
123
|
+
if g.uses_depth:
|
|
124
|
+
s += " saved = depth\n depth = 0\n"
|
|
125
|
+
if g.uses_dynamic_scope:
|
|
126
|
+
s += f" scope = DS[:]\n DS.clear()\n DS.append({g.root_resource})\n"
|
|
127
|
+
s += " try:\n"
|
|
128
|
+
s += f" return {g.root}(x)\n"
|
|
129
|
+
s += " finally:\n"
|
|
130
|
+
restore = []
|
|
131
|
+
if g.uses_depth:
|
|
132
|
+
restore.append(" depth = saved\n")
|
|
133
|
+
if g.uses_dynamic_scope:
|
|
134
|
+
restore.append(" DS[:] = scope\n")
|
|
135
|
+
s += "".join(restore)
|
|
136
|
+
else:
|
|
137
|
+
s += f"validate = {g.root}\n"
|
|
138
|
+
return s
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# Programs with more source than this (characters) compile each function on its first call.
|
|
142
|
+
LAZY_MIN_SOURCE = int(os.environ.get("CORVUS_PY_LAZY_MIN", 64 * 1024))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _stub_template(*args: Any) -> Any:
|
|
146
|
+
return MATERIALISE("@", args) # type: ignore[name-defined] # noqa: F821 - a global of the generated module
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _install_lazy_functions(namespace: dict[str, Any], functions: list[tuple[str, str]]) -> None:
|
|
150
|
+
"""Defines each function as a stub that, on its first call, compiles the function's source and takes its code:
|
|
151
|
+
the stub object becomes the function, so references already taken (dispatch tables) need no update."""
|
|
152
|
+
sources = dict(functions)
|
|
153
|
+
template = _stub_template.__code__
|
|
154
|
+
|
|
155
|
+
def materialise(name: str, args: tuple[Any, ...]) -> Any:
|
|
156
|
+
stub = namespace[name]
|
|
157
|
+
module = builtins.compile(sources[name], "<corvus-json-schema>", "exec")
|
|
158
|
+
stub.__code__ = next(c for c in module.co_consts if isinstance(c, types.CodeType))
|
|
159
|
+
return stub(*args)
|
|
160
|
+
|
|
161
|
+
namespace["MATERIALISE"] = materialise
|
|
162
|
+
for name, _ in functions:
|
|
163
|
+
code = template.replace(co_consts=tuple(name if c == "@" else c for c in template.co_consts), co_name=name)
|
|
164
|
+
namespace[name] = types.FunctionType(code, namespace, name)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _namespace(custom_formats: list[Any]) -> dict[str, Any]:
|
|
168
|
+
return {
|
|
169
|
+
"__name__": "corvus_json_schema.generated",
|
|
170
|
+
"R": runtime,
|
|
171
|
+
"F": custom_formats,
|
|
172
|
+
"JSON": json,
|
|
173
|
+
"RE": re,
|
|
174
|
+
"THREADING": threading,
|
|
175
|
+
"NoneType": types.NoneType,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def compile(schema: Any, options: CompileOptions | None = None, **kwargs: Any) -> Validator:
|
|
180
|
+
"""Compiles a schema (a parsed JSON value, or JSON text) into a validator.
|
|
181
|
+
|
|
182
|
+
The schema is loaded, its references are resolved and analysed, and the result is emitted as specialised Python
|
|
183
|
+
and compiled once. Options are given as a ``CompileOptions`` or as its fields by keyword (``default_dialect``,
|
|
184
|
+
``assert_format``, ``formats``, ``resolve_document``, ``base_uri``, ``entry_point``, ``max_depth`` and so on).
|
|
185
|
+
"""
|
|
186
|
+
opts = _options(options, kwargs)
|
|
187
|
+
g, program = _generate(schema, opts)
|
|
188
|
+
source = _assemble(g)
|
|
189
|
+
namespace = _namespace(g.custom_formats)
|
|
190
|
+
try:
|
|
191
|
+
if len(source) <= LAZY_MIN_SOURCE:
|
|
192
|
+
exec(builtins.compile(source, "<corvus-json-schema>", "exec"), namespace)
|
|
193
|
+
else:
|
|
194
|
+
# A large program: each function is compiled on its first call, so functions no instance reaches (much of
|
|
195
|
+
# a large schema, for most instances) are never compiled.
|
|
196
|
+
_install_lazy_functions(namespace, g.functions)
|
|
197
|
+
exec(builtins.compile(_assemble(g, functions=False), "<corvus-json-schema>", "exec"), namespace)
|
|
198
|
+
except SyntaxError as e:
|
|
199
|
+
raise SchemaCompilationError(f"The schema produced invalid code: {e}") from e
|
|
200
|
+
validate = namespace["validate"]
|
|
201
|
+
# The collecting program (annotations included) is built on the first evaluation with a collector.
|
|
202
|
+
collecting: list[CollectingProgram] = []
|
|
203
|
+
|
|
204
|
+
def evaluate(instance: Any, collector: JsonSchemaResultsCollector | None = None) -> bool:
|
|
205
|
+
if collector is None:
|
|
206
|
+
return bool(validate(instance))
|
|
207
|
+
if not collecting:
|
|
208
|
+
collecting.append(_collecting_program(program))
|
|
209
|
+
return evaluate_with_collector(collecting[0], instance, collector)
|
|
210
|
+
|
|
211
|
+
# The generated function itself is the validator, so a call costs nothing beyond the checks. When the module keeps
|
|
212
|
+
# evaluation state (a dynamic scope or a depth count), `validate` is already a wrapper that resets it.
|
|
213
|
+
validate.source = source
|
|
214
|
+
validate.evaluate = evaluate
|
|
215
|
+
return cast(Validator, validate)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def generate_module(
|
|
219
|
+
schema: Any,
|
|
220
|
+
options: CompileOptions | None = None,
|
|
221
|
+
*,
|
|
222
|
+
runtime_import: str = "corvus_json_schema.runtime",
|
|
223
|
+
collecting: bool = True,
|
|
224
|
+
collecting_import: str = "corvus_json_schema.collecting",
|
|
225
|
+
**kwargs: Any,
|
|
226
|
+
) -> str:
|
|
227
|
+
"""Generates a standalone Python module for the schema, defining ``validate(instance) -> bool``.
|
|
228
|
+
|
|
229
|
+
The module depends only on the runtime helpers (``corvus_json_schema.runtime``), so the schema compiler is not
|
|
230
|
+
needed where it runs. With ``collecting`` (the default) it also defines ``evaluate(instance, collector=None)``,
|
|
231
|
+
embedding the program image for results collection. Custom format functions cannot be serialised; supply them to
|
|
232
|
+
``compile`` instead.
|
|
233
|
+
"""
|
|
234
|
+
opts = _options(options, kwargs)
|
|
235
|
+
g, program = _generate(schema, opts)
|
|
236
|
+
if g.custom_formats:
|
|
237
|
+
raise SchemaCompilationError("Custom format functions cannot be emitted into a standalone module.")
|
|
238
|
+
text = "# Generated by corvus-json-schema. Do not edit.\n"
|
|
239
|
+
text += "# ruff: noqa\n# fmt: off\n"
|
|
240
|
+
text += "import json as JSON\nimport re as RE\nimport threading as THREADING\nfrom types import NoneType\n\n"
|
|
241
|
+
text += f"import {runtime_import} as R\n"
|
|
242
|
+
if collecting:
|
|
243
|
+
text += f"from {collecting_import} import evaluate_with_collector, load_program\n"
|
|
244
|
+
text += "\n" + _assemble(g)
|
|
245
|
+
text += "\n__all__ = ['validate'" + (", 'evaluate'" if collecting else "") + "]\n"
|
|
246
|
+
if collecting:
|
|
247
|
+
image = serialize_program(_collecting_program(program))
|
|
248
|
+
text += (
|
|
249
|
+
f"\n_image = {image!r}\n"
|
|
250
|
+
"_program = None\n\n\n"
|
|
251
|
+
"def evaluate(instance, collector=None):\n"
|
|
252
|
+
' """Evaluates the instance, reporting to the results collector when one is given."""\n'
|
|
253
|
+
" global _program\n"
|
|
254
|
+
" if collector is None:\n"
|
|
255
|
+
" return validate(instance)\n"
|
|
256
|
+
" if _program is None:\n"
|
|
257
|
+
" _program = load_program(_image)\n"
|
|
258
|
+
" return evaluate_with_collector(_program, instance, collector)\n"
|
|
259
|
+
)
|
|
260
|
+
return text
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# <auto-generated> by scripts/embed_metaschemas.py from src/Corvus.Text.Json/metaschema. Do not edit.
|
|
2
|
+
|
|
3
|
+
"""The standard metaschemas, keyed by their canonical URI (no trailing empty fragment)."""
|
|
4
|
+
|
|
5
|
+
METASCHEMAS: dict[str, str] = {
|
|
6
|
+
"http://json-schema.org/draft-04/schema": '{"id":"http://json-schema.org/draft-04/schema#","$schema":"http://json-schema.org/draft-04/schema#","description":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"positiveInteger":{"type":"integer","minimum":0},"positiveIntegerDefault0":{"allOf":[{"$ref":"#/definitions/positiveInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true}},"type":"object","properties":{"id":{"type":"string"},"$schema":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"multipleOf":{"type":"number","minimum":0,"exclusiveMinimum":true},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"boolean","default":false},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"boolean","default":false},"maxLength":{"$ref":"#/definitions/positiveInteger"},"minLength":{"$ref":"#/definitions/positiveIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/positiveInteger"},"minItems":{"$ref":"#/definitions/positiveIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxProperties":{"$ref":"#/definitions/positiveInteger"},"minProperties":{"$ref":"#/definitions/positiveIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"anyOf":[{"type":"boolean"},{"$ref":"#"}],"default":{}},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"dependencies":{"exclusiveMaximum":["maximum"],"exclusiveMinimum":["minimum"]},"default":{}}',
|
|
7
|
+
"http://json-schema.org/draft-06/schema": '{"$schema":"http://json-schema.org/draft-06/schema#","$id":"http://json-schema.org/draft-06/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"examples":{"type":"array","items":{}},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":{},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":{}}',
|
|
8
|
+
"http://json-schema.org/draft-07/schema": '{"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"writeOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}',
|
|
9
|
+
"https://json-schema.org/draft/2019-09/schema": '{"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/schema","$vocabulary":{"https://json-schema.org/draft/2019-09/vocab/core":true,"https://json-schema.org/draft/2019-09/vocab/applicator":true,"https://json-schema.org/draft/2019-09/vocab/validation":true,"https://json-schema.org/draft/2019-09/vocab/meta-data":true,"https://json-schema.org/draft/2019-09/vocab/format":false,"https://json-schema.org/draft/2019-09/vocab/content":true},"$recursiveAnchor":true,"title":"Core and Validation specifications meta-schema","allOf":[{"$ref":"meta/core"},{"$ref":"meta/applicator"},{"$ref":"meta/validation"},{"$ref":"meta/meta-data"},{"$ref":"meta/format"},{"$ref":"meta/content"}],"type":["object","boolean"],"properties":{"definitions":{"$comment":"While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.","type":"object","additionalProperties":{"$recursiveRef":"#"},"default":{}},"dependencies":{"$comment":"\\"dependencies\\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \\"dependentSchemas\\" and \\"dependentRequired\\"","type":"object","additionalProperties":{"anyOf":[{"$recursiveRef":"#"},{"$ref":"meta/validation#/$defs/stringArray"}]}}}}',
|
|
10
|
+
"https://json-schema.org/draft/2020-12/schema": '{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/schema","$vocabulary":{"https://json-schema.org/draft/2020-12/vocab/core":true,"https://json-schema.org/draft/2020-12/vocab/applicator":true,"https://json-schema.org/draft/2020-12/vocab/unevaluated":true,"https://json-schema.org/draft/2020-12/vocab/validation":true,"https://json-schema.org/draft/2020-12/vocab/meta-data":true,"https://json-schema.org/draft/2020-12/vocab/format-annotation":true,"https://json-schema.org/draft/2020-12/vocab/content":true},"$dynamicAnchor":"meta","title":"Core and Validation specifications meta-schema","allOf":[{"$ref":"meta/core"},{"$ref":"meta/applicator"},{"$ref":"meta/unevaluated"},{"$ref":"meta/validation"},{"$ref":"meta/meta-data"},{"$ref":"meta/format-annotation"},{"$ref":"meta/content"}],"type":["object","boolean"],"$comment":"This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.","properties":{"definitions":{"$comment":"\\"definitions\\" has been replaced by \\"$defs\\".","type":"object","additionalProperties":{"$dynamicRef":"#meta"},"deprecated":true,"default":{}},"dependencies":{"$comment":"\\"dependencies\\" has been split and replaced by \\"dependentSchemas\\" and \\"dependentRequired\\" in order to serve their differing semantics.","type":"object","additionalProperties":{"anyOf":[{"$dynamicRef":"#meta"},{"$ref":"meta/validation#/$defs/stringArray"}]},"deprecated":true,"default":{}},"$recursiveAnchor":{"$comment":"\\"$recursiveAnchor\\" has been replaced by \\"$dynamicAnchor\\".","$ref":"meta/core#/$defs/anchorString","deprecated":true},"$recursiveRef":{"$comment":"\\"$recursiveRef\\" has been replaced by \\"$dynamicRef\\".","$ref":"meta/core#/$defs/uriReferenceString","deprecated":true}}}',
|
|
11
|
+
"https://json-schema.org/draft/2019-09/meta/applicator": '{"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/applicator","$recursiveAnchor":true,"title":"Applicator vocabulary meta-schema","type":["object","boolean"],"properties":{"additionalItems":{"$recursiveRef":"#"},"unevaluatedItems":{"$recursiveRef":"#"},"items":{"anyOf":[{"$recursiveRef":"#"},{"$ref":"#/$defs/schemaArray"}]},"contains":{"$recursiveRef":"#"},"additionalProperties":{"$recursiveRef":"#"},"unevaluatedProperties":{"$recursiveRef":"#"},"properties":{"type":"object","additionalProperties":{"$recursiveRef":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$recursiveRef":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependentSchemas":{"type":"object","additionalProperties":{"$recursiveRef":"#"}},"propertyNames":{"$recursiveRef":"#"},"if":{"$recursiveRef":"#"},"then":{"$recursiveRef":"#"},"else":{"$recursiveRef":"#"},"allOf":{"$ref":"#/$defs/schemaArray"},"anyOf":{"$ref":"#/$defs/schemaArray"},"oneOf":{"$ref":"#/$defs/schemaArray"},"not":{"$recursiveRef":"#"}},"$defs":{"schemaArray":{"type":"array","minItems":1,"items":{"$recursiveRef":"#"}}}}',
|
|
12
|
+
"https://json-schema.org/draft/2019-09/meta/content": '{"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/content","$recursiveAnchor":true,"title":"Content vocabulary meta-schema","type":["object","boolean"],"properties":{"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"contentSchema":{"$recursiveRef":"#"}}}',
|
|
13
|
+
"https://json-schema.org/draft/2019-09/meta/core": '{"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/core","$recursiveAnchor":true,"title":"Core vocabulary meta-schema","type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference","$comment":"Non-empty fragments not allowed.","pattern":"^[^#]*#?$"},"$schema":{"type":"string","format":"uri"},"$anchor":{"type":"string","pattern":"^[A-Za-z][-A-Za-z0-9.:_]*$"},"$ref":{"type":"string","format":"uri-reference"},"$recursiveRef":{"type":"string","format":"uri-reference"},"$recursiveAnchor":{"type":"boolean","default":false},"$vocabulary":{"type":"object","propertyNames":{"type":"string","format":"uri"},"additionalProperties":{"type":"boolean"}},"$comment":{"type":"string"},"$defs":{"type":"object","additionalProperties":{"$recursiveRef":"#"},"default":{}}}}',
|
|
14
|
+
"https://json-schema.org/draft/2019-09/meta/format": '{"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/format","$recursiveAnchor":true,"title":"Format vocabulary meta-schema","type":["object","boolean"],"properties":{"format":{"type":"string"}}}',
|
|
15
|
+
"https://json-schema.org/draft/2019-09/meta/hyper-schema": '{"$schema":"https://json-schema.org/draft/2019-09/hyper-schema","$id":"https://json-schema.org/draft/2019-09/meta/hyper-schema","$recursiveAnchor":true,"title":"JSON Hyper-Schema Vocabulary Schema","type":["object","boolean"],"properties":{"base":{"type":"string","format":"uri-template"},"links":{"type":"array","items":{"$ref":"https://json-schema.org/draft/2019-09/links"}}},"links":[{"rel":"self","href":"{+%24id}"}]}',
|
|
16
|
+
"https://json-schema.org/draft/2019-09/meta/meta-data": '{"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/meta-data","$recursiveAnchor":true,"title":"Meta-data vocabulary meta-schema","type":["object","boolean"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"default":true,"deprecated":{"type":"boolean","default":false},"readOnly":{"type":"boolean","default":false},"writeOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true}}}',
|
|
17
|
+
"https://json-schema.org/draft/2019-09/meta/validation": '{"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/validation","$recursiveAnchor":true,"title":"Validation vocabulary meta-schema","type":["object","boolean"],"properties":{"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/$defs/nonNegativeInteger"},"minLength":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"maxItems":{"$ref":"#/$defs/nonNegativeInteger"},"minItems":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxContains":{"$ref":"#/$defs/nonNegativeInteger"},"minContains":{"$ref":"#/$defs/nonNegativeInteger","default":1},"maxProperties":{"$ref":"#/$defs/nonNegativeInteger"},"minProperties":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"required":{"$ref":"#/$defs/stringArray"},"dependentRequired":{"type":"object","additionalProperties":{"$ref":"#/$defs/stringArray"}},"const":true,"enum":{"type":"array","items":true},"type":{"anyOf":[{"$ref":"#/$defs/simpleTypes"},{"type":"array","items":{"$ref":"#/$defs/simpleTypes"},"minItems":1,"uniqueItems":true}]}},"$defs":{"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"$ref":"#/$defs/nonNegativeInteger","default":0},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}}}',
|
|
18
|
+
"https://json-schema.org/draft/2020-12/meta/applicator": '{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/applicator","$dynamicAnchor":"meta","title":"Applicator vocabulary meta-schema","type":["object","boolean"],"properties":{"prefixItems":{"$ref":"#/$defs/schemaArray"},"items":{"$dynamicRef":"#meta"},"contains":{"$dynamicRef":"#meta"},"additionalProperties":{"$dynamicRef":"#meta"},"properties":{"type":"object","additionalProperties":{"$dynamicRef":"#meta"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$dynamicRef":"#meta"},"propertyNames":{"format":"regex"},"default":{}},"dependentSchemas":{"type":"object","additionalProperties":{"$dynamicRef":"#meta"},"default":{}},"propertyNames":{"$dynamicRef":"#meta"},"if":{"$dynamicRef":"#meta"},"then":{"$dynamicRef":"#meta"},"else":{"$dynamicRef":"#meta"},"allOf":{"$ref":"#/$defs/schemaArray"},"anyOf":{"$ref":"#/$defs/schemaArray"},"oneOf":{"$ref":"#/$defs/schemaArray"},"not":{"$dynamicRef":"#meta"}},"$defs":{"schemaArray":{"type":"array","minItems":1,"items":{"$dynamicRef":"#meta"}}}}',
|
|
19
|
+
"https://json-schema.org/draft/2020-12/meta/content": '{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/content","$dynamicAnchor":"meta","title":"Content vocabulary meta-schema","type":["object","boolean"],"properties":{"contentEncoding":{"type":"string"},"contentMediaType":{"type":"string"},"contentSchema":{"$dynamicRef":"#meta"}}}',
|
|
20
|
+
"https://json-schema.org/draft/2020-12/meta/core": '{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/core","$dynamicAnchor":"meta","title":"Core vocabulary meta-schema","type":["object","boolean"],"properties":{"$id":{"$ref":"#/$defs/uriReferenceString","$comment":"Non-empty fragments not allowed.","pattern":"^[^#]*#?$"},"$schema":{"$ref":"#/$defs/uriString"},"$ref":{"$ref":"#/$defs/uriReferenceString"},"$anchor":{"$ref":"#/$defs/anchorString"},"$dynamicRef":{"$ref":"#/$defs/uriReferenceString"},"$dynamicAnchor":{"$ref":"#/$defs/anchorString"},"$vocabulary":{"type":"object","propertyNames":{"$ref":"#/$defs/uriString"},"additionalProperties":{"type":"boolean"}},"$comment":{"type":"string"},"$defs":{"type":"object","additionalProperties":{"$dynamicRef":"#meta"}}},"$defs":{"anchorString":{"type":"string","pattern":"^[A-Za-z_][-A-Za-z0-9._]*$"},"uriString":{"type":"string","format":"uri"},"uriReferenceString":{"type":"string","format":"uri-reference"}}}',
|
|
21
|
+
"https://json-schema.org/draft/2020-12/meta/format-annotation": '{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/format-annotation","$dynamicAnchor":"meta","title":"Format vocabulary meta-schema for annotation results","type":["object","boolean"],"properties":{"format":{"type":"string"}}}',
|
|
22
|
+
"https://json-schema.org/draft/2020-12/meta/format-assertion": '{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/format-assertion","$dynamicAnchor":"meta","title":"Format vocabulary meta-schema for assertion results","type":["object","boolean"],"properties":{"format":{"type":"string"}}}',
|
|
23
|
+
"https://json-schema.org/draft/2020-12/meta/hyper-schema": '{"$schema":"https://json-schema.org/draft/2020-12/hyper-schema","$id":"https://json-schema.org/draft/2020-12/meta/hyper-schema","$dynamicAnchor":"meta","title":"JSON Hyper-Schema Vocabulary Schema","type":["object","boolean"],"properties":{"base":{"type":"string","format":"uri-template"},"links":{"type":"array","items":{"$ref":"https://json-schema.org/draft/2020-12/links"}}},"links":[{"rel":"self","href":"{+%24id}"}]}',
|
|
24
|
+
"https://json-schema.org/draft/2020-12/meta/meta-data": '{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/meta-data","$dynamicAnchor":"meta","title":"Meta-data vocabulary meta-schema","type":["object","boolean"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"default":true,"deprecated":{"type":"boolean","default":false},"readOnly":{"type":"boolean","default":false},"writeOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true}}}',
|
|
25
|
+
"https://json-schema.org/draft/2020-12/meta/unevaluated": '{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/unevaluated","$dynamicAnchor":"meta","title":"Unevaluated applicator vocabulary meta-schema","type":["object","boolean"],"properties":{"unevaluatedItems":{"$dynamicRef":"#meta"},"unevaluatedProperties":{"$dynamicRef":"#meta"}}}',
|
|
26
|
+
"https://json-schema.org/draft/2020-12/meta/validation": '{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/validation","$dynamicAnchor":"meta","title":"Validation vocabulary meta-schema","type":["object","boolean"],"properties":{"type":{"anyOf":[{"$ref":"#/$defs/simpleTypes"},{"type":"array","items":{"$ref":"#/$defs/simpleTypes"},"minItems":1,"uniqueItems":true}]},"const":true,"enum":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/$defs/nonNegativeInteger"},"minLength":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"maxItems":{"$ref":"#/$defs/nonNegativeInteger"},"minItems":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxContains":{"$ref":"#/$defs/nonNegativeInteger"},"minContains":{"$ref":"#/$defs/nonNegativeInteger","default":1},"maxProperties":{"$ref":"#/$defs/nonNegativeInteger"},"minProperties":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"required":{"$ref":"#/$defs/stringArray"},"dependentRequired":{"type":"object","additionalProperties":{"$ref":"#/$defs/stringArray"}}},"$defs":{"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"$ref":"#/$defs/nonNegativeInteger","default":0},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}}}',
|
|
27
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""corvus-json-schema: emit a standalone validator module for a schema, or validate JSON documents against a schema.
|
|
2
|
+
|
|
3
|
+
corvus-json-schema generate <schema.json> [-o <out.py>] [--runtime-import <module>] [options]
|
|
4
|
+
corvus-json-schema validate <schema.json> <instance.json>... [options]
|
|
5
|
+
|
|
6
|
+
Options: --default-dialect <4|6|7|2019-09|2020-12>, --assert-format, --base-uri <uri>, --entry-point <ref>,
|
|
7
|
+
--schema-dir <dir> (resolve remote $refs from local files named after the URI's last path segment).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
from urllib.parse import unquote, urlsplit
|
|
18
|
+
|
|
19
|
+
from . import Dialect, compile, generate_module
|
|
20
|
+
|
|
21
|
+
DIALECTS = {
|
|
22
|
+
"4": Dialect.DRAFT4,
|
|
23
|
+
"6": Dialect.DRAFT6,
|
|
24
|
+
"7": Dialect.DRAFT7,
|
|
25
|
+
"2019-09": Dialect.DRAFT201909,
|
|
26
|
+
"2020-12": Dialect.DRAFT202012,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _read_json(file: str) -> Any:
|
|
31
|
+
return json.loads(Path(file).read_text(encoding="utf-8"))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main(argv: list[str] | None = None) -> int:
|
|
35
|
+
parser = argparse.ArgumentParser(
|
|
36
|
+
prog="corvus-json-schema", description=__doc__.split("\n\n")[0] if __doc__ else None
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument("command", choices=["generate", "validate"])
|
|
39
|
+
parser.add_argument("files", nargs="+")
|
|
40
|
+
parser.add_argument("-o", "--output", help="the file to write the generated module to (generate)")
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--runtime-import", default="corvus_json_schema.runtime", help="the module of the runtime helpers"
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument("--default-dialect", help="dialect for schemas without $schema (default 2020-12)")
|
|
45
|
+
parser.add_argument("--assert-format", action="store_true", help='assert "format" (otherwise it is an annotation)')
|
|
46
|
+
parser.add_argument("--base-uri", help="base URI of the root schema")
|
|
47
|
+
parser.add_argument("--entry-point", help="evaluate from a subschema, e.g. #/$defs/item")
|
|
48
|
+
parser.add_argument("--schema-dir", help="resolve referenced documents from files in this directory")
|
|
49
|
+
args = parser.parse_args(argv)
|
|
50
|
+
|
|
51
|
+
options: dict[str, Any] = {}
|
|
52
|
+
if args.default_dialect is not None:
|
|
53
|
+
name = args.default_dialect.removeprefix("draft").removeprefix("-")
|
|
54
|
+
if name not in DIALECTS:
|
|
55
|
+
parser.error(f"unknown dialect '{args.default_dialect}'")
|
|
56
|
+
options["default_dialect"] = DIALECTS[name]
|
|
57
|
+
if args.assert_format:
|
|
58
|
+
options["assert_format"] = True
|
|
59
|
+
if args.base_uri is not None:
|
|
60
|
+
options["base_uri"] = args.base_uri
|
|
61
|
+
if args.entry_point is not None:
|
|
62
|
+
options["entry_point"] = args.entry_point
|
|
63
|
+
if args.schema_dir is not None:
|
|
64
|
+
directory = Path(args.schema_dir)
|
|
65
|
+
|
|
66
|
+
def resolve(uri: str) -> Any:
|
|
67
|
+
file = directory / unquote(urlsplit(uri).path.split("/")[-1])
|
|
68
|
+
return json.loads(file.read_text(encoding="utf-8")) if file.is_file() else None
|
|
69
|
+
|
|
70
|
+
options["resolve_document"] = resolve
|
|
71
|
+
|
|
72
|
+
if args.command == "generate":
|
|
73
|
+
if len(args.files) != 1:
|
|
74
|
+
parser.error("generate takes one schema file")
|
|
75
|
+
source = generate_module(_read_json(args.files[0]), runtime_import=args.runtime_import, **options)
|
|
76
|
+
if args.output is None:
|
|
77
|
+
sys.stdout.write(source)
|
|
78
|
+
else:
|
|
79
|
+
Path(args.output).write_text(source, encoding="utf-8")
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
if len(args.files) < 2:
|
|
83
|
+
parser.error("validate takes a schema file and at least one instance file")
|
|
84
|
+
validate = compile(_read_json(args.files[0]), **options)
|
|
85
|
+
failed = 0
|
|
86
|
+
for file in args.files[1:]:
|
|
87
|
+
valid = validate(_read_json(file))
|
|
88
|
+
if not valid:
|
|
89
|
+
failed += 1
|
|
90
|
+
print(f"{file}: {'valid' if valid else 'invalid'}")
|
|
91
|
+
return 0 if failed == 0 else 1
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
sys.exit(main())
|