subschema 0.0.1__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.
- subschema/__init__.py +19 -0
- subschema/api.py +172 -0
- subschema/cli.py +75 -0
- subschema/dialects.py +484 -0
- subschema/exceptions.py +100 -0
- subschema/kernel/__init__.py +33 -0
- subschema/kernel/applicators.py +1644 -0
- subschema/kernel/certificates.py +14 -0
- subschema/kernel/composition.py +188 -0
- subschema/kernel/constraints.py +93 -0
- subschema/kernel/context.py +131 -0
- subschema/kernel/contracts.py +243 -0
- subschema/kernel/difference.py +6252 -0
- subschema/kernel/disjointness.py +133 -0
- subschema/kernel/domains/__init__.py +3 -0
- subschema/kernel/domains/arrays.py +913 -0
- subschema/kernel/domains/numbers.py +731 -0
- subschema/kernel/domains/objects.py +2219 -0
- subschema/kernel/domains/strings.py +744 -0
- subschema/kernel/domains/types.py +543 -0
- subschema/kernel/driver.py +104 -0
- subschema/kernel/engine.py +63 -0
- subschema/kernel/evaluation.py +924 -0
- subschema/kernel/finite.py +715 -0
- subschema/kernel/formulas.py +668 -0
- subschema/kernel/ir.py +735 -0
- subschema/kernel/json_data.py +88 -0
- subschema/kernel/normalization.py +65 -0
- subschema/kernel/overlaps.py +152 -0
- subschema/kernel/projection.py +99 -0
- subschema/kernel/references.py +1047 -0
- subschema/kernel/regex.py +1006 -0
- subschema/kernel/sat.py +3153 -0
- subschema/kernel/scalars.py +651 -0
- subschema/kernel/schemas.py +206 -0
- subschema/kernel/semantic.py +833 -0
- subschema/kernel/symbolic.py +201 -0
- subschema/kernel/validation.py +422 -0
- subschema/kernel/values.py +56 -0
- subschema/kernel/witnesses.py +824 -0
- subschema-0.0.1.dist-info/METADATA +102 -0
- subschema-0.0.1.dist-info/RECORD +45 -0
- subschema-0.0.1.dist-info/WHEEL +4 -0
- subschema-0.0.1.dist-info/entry_points.txt +2 -0
- subschema-0.0.1.dist-info/licenses/LICENSE +177 -0
subschema/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from subschema import api, exceptions
|
|
2
|
+
from subschema.dialects import Dialect
|
|
3
|
+
|
|
4
|
+
is_subschema = api.is_subschema
|
|
5
|
+
meet_schemas = api.meet_schemas
|
|
6
|
+
join_schemas = api.join_schemas
|
|
7
|
+
is_equivalent = api.is_equivalent
|
|
8
|
+
|
|
9
|
+
canonicalize_schema = api.canonicalize_schema
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Dialect",
|
|
13
|
+
"canonicalize_schema",
|
|
14
|
+
"exceptions",
|
|
15
|
+
"is_equivalent",
|
|
16
|
+
"is_subschema",
|
|
17
|
+
"join_schemas",
|
|
18
|
+
"meet_schemas",
|
|
19
|
+
]
|
subschema/api.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public API entrypoints.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
|
|
7
|
+
from subschema.dialects import (
|
|
8
|
+
resolve_dialect,
|
|
9
|
+
strip_inactive_keywords_for_dialect,
|
|
10
|
+
validate_supported_keywords,
|
|
11
|
+
)
|
|
12
|
+
from subschema.kernel.contracts import ProofBudgets, ProofOptions
|
|
13
|
+
from subschema.kernel.engine import ProofEngine
|
|
14
|
+
from subschema.kernel.json_data import ensure_json_value
|
|
15
|
+
from subschema.kernel.normalization import normalize_boolean_schemas
|
|
16
|
+
from subschema.kernel.validation import validate_raw_schema_for_dialect
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def canonicalize_schema(s, *, dialect=None):
|
|
20
|
+
"""Return a modern normalized schema without embedding removed checker objects."""
|
|
21
|
+
ensure_json_value(s, label="schema")
|
|
22
|
+
resolved_dialect = resolve_dialect(s, dialect=dialect)
|
|
23
|
+
_validate_public_schema(s, resolved_dialect)
|
|
24
|
+
schema = strip_inactive_keywords_for_dialect(
|
|
25
|
+
normalize_boolean_schemas(deepcopy(s)), resolved_dialect
|
|
26
|
+
)
|
|
27
|
+
validate_supported_keywords(schema, resolved_dialect)
|
|
28
|
+
return schema
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _validate_public_schema(schema, dialect):
|
|
32
|
+
stripped = strip_inactive_keywords_for_dialect(schema, dialect)
|
|
33
|
+
validate_raw_schema_for_dialect(stripped, dialect)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_proof_options(
|
|
37
|
+
proof_options=None,
|
|
38
|
+
*,
|
|
39
|
+
endeavor=False,
|
|
40
|
+
max_work=None,
|
|
41
|
+
timeout_ms=None,
|
|
42
|
+
):
|
|
43
|
+
if not isinstance(endeavor, bool):
|
|
44
|
+
raise TypeError("endeavor must be a boolean")
|
|
45
|
+
if proof_options is not None:
|
|
46
|
+
if not isinstance(proof_options, ProofOptions):
|
|
47
|
+
raise TypeError("proof_options must be a ProofOptions instance")
|
|
48
|
+
if endeavor or max_work is not None or timeout_ms is not None:
|
|
49
|
+
raise ValueError(
|
|
50
|
+
"proof_options cannot be combined with endeavor, max_work, or "
|
|
51
|
+
"timeout_ms"
|
|
52
|
+
)
|
|
53
|
+
return proof_options
|
|
54
|
+
if (max_work is not None or timeout_ms is not None) and not endeavor:
|
|
55
|
+
raise ValueError("max_work and timeout_ms require endeavor=True")
|
|
56
|
+
if endeavor:
|
|
57
|
+
return ProofOptions(
|
|
58
|
+
endeavor=endeavor,
|
|
59
|
+
budgets=ProofBudgets(
|
|
60
|
+
max_work=4096 if max_work is None else max_work,
|
|
61
|
+
timeout_ms=1000 if timeout_ms is None else timeout_ms,
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def is_subschema(
|
|
68
|
+
s1,
|
|
69
|
+
s2,
|
|
70
|
+
*,
|
|
71
|
+
dialect=None,
|
|
72
|
+
proof_options=None,
|
|
73
|
+
endeavor=False,
|
|
74
|
+
max_work=None,
|
|
75
|
+
timeout_ms=None,
|
|
76
|
+
):
|
|
77
|
+
"""Entry point for schema subtype checking."""
|
|
78
|
+
ensure_json_value(s1, label="lhs schema")
|
|
79
|
+
ensure_json_value(s2, label="rhs schema")
|
|
80
|
+
resolved_dialect = resolve_dialect(s1, s2, dialect=dialect)
|
|
81
|
+
_validate_public_schema(s1, resolved_dialect)
|
|
82
|
+
_validate_public_schema(s2, resolved_dialect)
|
|
83
|
+
options = _resolve_proof_options(
|
|
84
|
+
proof_options,
|
|
85
|
+
endeavor=endeavor,
|
|
86
|
+
max_work=max_work,
|
|
87
|
+
timeout_ms=timeout_ms,
|
|
88
|
+
)
|
|
89
|
+
return ProofEngine.for_schemas(
|
|
90
|
+
s1, s2, dialect=resolved_dialect, options=options
|
|
91
|
+
).is_subschema_bool(s1, s2)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def meet_schemas(
|
|
95
|
+
s1,
|
|
96
|
+
s2,
|
|
97
|
+
*,
|
|
98
|
+
dialect=None,
|
|
99
|
+
proof_options=None,
|
|
100
|
+
endeavor=False,
|
|
101
|
+
max_work=None,
|
|
102
|
+
timeout_ms=None,
|
|
103
|
+
):
|
|
104
|
+
"""Entry point for schema meet operation."""
|
|
105
|
+
ensure_json_value(s1, label="lhs schema")
|
|
106
|
+
ensure_json_value(s2, label="rhs schema")
|
|
107
|
+
resolved_dialect = resolve_dialect(s1, s2, dialect=dialect)
|
|
108
|
+
_validate_public_schema(s1, resolved_dialect)
|
|
109
|
+
_validate_public_schema(s2, resolved_dialect)
|
|
110
|
+
options = _resolve_proof_options(
|
|
111
|
+
proof_options,
|
|
112
|
+
endeavor=endeavor,
|
|
113
|
+
max_work=max_work,
|
|
114
|
+
timeout_ms=timeout_ms,
|
|
115
|
+
)
|
|
116
|
+
return ProofEngine.for_schemas(
|
|
117
|
+
s1, s2, dialect=resolved_dialect, options=options
|
|
118
|
+
).meet(s1, s2)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def join_schemas(
|
|
122
|
+
s1,
|
|
123
|
+
s2,
|
|
124
|
+
*,
|
|
125
|
+
dialect=None,
|
|
126
|
+
proof_options=None,
|
|
127
|
+
endeavor=False,
|
|
128
|
+
max_work=None,
|
|
129
|
+
timeout_ms=None,
|
|
130
|
+
):
|
|
131
|
+
"""Entry point for schema join operation."""
|
|
132
|
+
ensure_json_value(s1, label="lhs schema")
|
|
133
|
+
ensure_json_value(s2, label="rhs schema")
|
|
134
|
+
resolved_dialect = resolve_dialect(s1, s2, dialect=dialect)
|
|
135
|
+
_validate_public_schema(s1, resolved_dialect)
|
|
136
|
+
_validate_public_schema(s2, resolved_dialect)
|
|
137
|
+
options = _resolve_proof_options(
|
|
138
|
+
proof_options,
|
|
139
|
+
endeavor=endeavor,
|
|
140
|
+
max_work=max_work,
|
|
141
|
+
timeout_ms=timeout_ms,
|
|
142
|
+
)
|
|
143
|
+
return ProofEngine.for_schemas(
|
|
144
|
+
s1, s2, dialect=resolved_dialect, options=options
|
|
145
|
+
).join(s1, s2)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def is_equivalent(
|
|
149
|
+
s1,
|
|
150
|
+
s2,
|
|
151
|
+
*,
|
|
152
|
+
dialect=None,
|
|
153
|
+
proof_options=None,
|
|
154
|
+
endeavor=False,
|
|
155
|
+
max_work=None,
|
|
156
|
+
timeout_ms=None,
|
|
157
|
+
):
|
|
158
|
+
"""Entry point for schema equivalence check operation."""
|
|
159
|
+
options = _resolve_proof_options(
|
|
160
|
+
proof_options,
|
|
161
|
+
endeavor=endeavor,
|
|
162
|
+
max_work=max_work,
|
|
163
|
+
timeout_ms=timeout_ms,
|
|
164
|
+
)
|
|
165
|
+
return is_subschema(
|
|
166
|
+
s1, s2, dialect=dialect, proof_options=options
|
|
167
|
+
) and is_subschema(
|
|
168
|
+
s2,
|
|
169
|
+
s1,
|
|
170
|
+
dialect=dialect,
|
|
171
|
+
proof_options=options,
|
|
172
|
+
)
|
subschema/cli.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from subschema.api import is_subschema
|
|
4
|
+
from subschema.kernel import ProofBudgets, ProofOptions
|
|
5
|
+
from subschema.kernel.json_data import strict_json_load
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def int_at_least_minus_one(value):
|
|
9
|
+
try:
|
|
10
|
+
parsed = int(value)
|
|
11
|
+
except ValueError as err:
|
|
12
|
+
raise argparse.ArgumentTypeError(f"{value!r} is not an integer") from err
|
|
13
|
+
if parsed < -1:
|
|
14
|
+
raise argparse.ArgumentTypeError("value must be -1 or greater")
|
|
15
|
+
return parsed
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_json_file(path, label):
|
|
19
|
+
with open(path) as fh:
|
|
20
|
+
try:
|
|
21
|
+
return strict_json_load(fh)
|
|
22
|
+
except Exception as err:
|
|
23
|
+
raise SystemExit(f"{label} {err}") from err
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main():
|
|
27
|
+
"""CLI entry point for subschema"""
|
|
28
|
+
|
|
29
|
+
parser = argparse.ArgumentParser(
|
|
30
|
+
description=(
|
|
31
|
+
"CLI for subschema tool which checks whether a LHS JSON "
|
|
32
|
+
"schema is a subschema (<:) of another RHS JSON schema."
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--endeavor", action="store_true", help="enable finite expensive proof products"
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument("--max-work", type=int_at_least_minus_one, default=None)
|
|
39
|
+
parser.add_argument("--timeout-ms", type=int_at_least_minus_one, default=None)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"LHS",
|
|
42
|
+
metavar="lhs",
|
|
43
|
+
type=str,
|
|
44
|
+
help="Path to the JSON file which has the LHS JSON schema",
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"RHS",
|
|
48
|
+
metavar="rhs",
|
|
49
|
+
type=str,
|
|
50
|
+
help="Path to the JSON file which has the RHS JSON schema",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
args = parser.parse_args()
|
|
54
|
+
if (args.max_work is not None or args.timeout_ms is not None) and not args.endeavor:
|
|
55
|
+
parser.error("--max-work and --timeout-ms require --endeavor")
|
|
56
|
+
s1_file_path = args.LHS
|
|
57
|
+
s2_file_path = args.RHS
|
|
58
|
+
|
|
59
|
+
s1 = load_json_file(s1_file_path, "LHS file:")
|
|
60
|
+
s2 = load_json_file(s2_file_path, "RHS file:")
|
|
61
|
+
proof_options = None
|
|
62
|
+
if args.endeavor:
|
|
63
|
+
proof_options = ProofOptions(
|
|
64
|
+
endeavor=args.endeavor,
|
|
65
|
+
budgets=ProofBudgets(
|
|
66
|
+
max_work=4096 if args.max_work is None else args.max_work,
|
|
67
|
+
timeout_ms=1000 if args.timeout_ms is None else args.timeout_ms,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
print("LHS <: RHS", is_subschema(s1, s2, proof_options=proof_options))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
main()
|