SchemaIR for Python
Types as data: Portable contracts and type calculations in Python.
SchemaIR declarations are ordinary JSON-compatible dictionaries. You can store a contract with a project, exchange it with a TypeScript tool, and inspect or calculate types without executing the application behind the contract.
This is the Python 0.2.0 binding for SchemaIR V1.
It implements the shared protocol rather than Python annotations or Pydantic
models. The TypeScript implementation remains the behavioral reference.
Install from source
Use Python 3.11 or newer. From the repository root:
python -m pip install -e ./python
The package is published on PyPI as schemair.
Store, calculate, and validate a contract
import json
from schemair import authoring as s
from schemair import (
check_schema_relation,
evaluate_expression,
validate_payload,
validate_schema,
)
# A SchemaNode describes the concrete output shape as data.
user = s.record([
s.required_field("id", s.string()),
s.required_field("name", s.string()),
])
# Reload the declaration without importing its original application.
loaded = json.loads(json.dumps(user))
assert validate_schema(loaded).status == "accepted"
# Build a SchemaTypeExpr, then evaluate it before executing an operation.
selection = s.field_of(loaded, "name")
name_type = evaluate_expression(selection)
assert name_type.status == "evaluated"
assert check_schema_relation(name_type.value, s.string()).status == "assignable"
# Validate actual values independently of type calculation.
payload = validate_payload({"id": "123", "name": "Ada"}, loaded)
assert payload.status == "accepted"
Python exposes snake_case helpers directly from schemair.authoring and also
provides schemair.authoring.schema and schemair.authoring.expression
namespaces matching the TypeScript authoring boundary. Wire fields retain the
protocol's camelCase spelling, such as elementSchema, refPath, and
semanticPath.
API and results
| Task | Entry points | Results |
|---|---|---|
| Validate declarations | validate_schema, validate_operation, validate_operation_container, validate_structured_operation, validate_structured_operation_container, validate_schema_type_expr, validate_schema_type_term |
accepted, rejected |
| Validate data | validate_payload, validate_payload_async |
accepted, rejected, unknown |
| Compare contracts | check_schema_relation, check_operation_relation and their _async companions |
assignable, incompatible, unknown, rejected |
| Calculate types | evaluate_expression, evaluate_schema_type_term and their _async companions |
evaluated, incomplete, rejected |
| Compare type terms | check_type_term_relation, check_type_term_relation_async |
Relation statuses |
| Infer variables | solve_type_variables, solve_type_variables_async |
solved, unknown, incompatible, rejected |
These functions return a Result with status, issues, and value.
Expression results put the evaluated term in value and expose unresolved
variable names through unresolved_type_vars; solver results put the binding
dictionary there. An evaluated term may still be an expression, so check its
shape before using it as a concrete schema. Diagnostics contain a code,
path, message, and optional details.
validate_expression(value, term=True) remains available for validating either
a schema or an expression. Definition validation does not execute operations
or resolve external references.
Solve a type variable
from schemair import authoring as s, evaluate_expression, solve_type_variables
solution = solve_type_variables(
["T"],
[{"source": s.number(), "target": s.type_var("T")}],
)
assert solution.status == "solved"
output = evaluate_expression(s.array_of(s.type_var("T")), type_vars=solution.value)
assert output.value == {"kind": "array", "elementSchema": s.number()}
The solver infers from supported source-to-target constraints. It is finite,
not a complete Python or TypeScript generic type checker. Inspect unknown
and diagnostics when candidate relations cannot be proved.
Operations and host callbacks
An operation is a concrete declaration of input, output, errors, and emitted
channels. Structured payloads contain SchemaNode values. Functions and
unresolved expressions are not operation payload schemas. See the
operation model.
Reference resolvers receive a tuple of path segments. For example:
import asyncio
from schemair import authoring as s, validate_payload_async
async def resolve_ref(path):
return s.string() if path == ("UserName",) else None
async def main():
result = await validate_payload_async("Ada", s.ref(["UserName"]), resolve_ref=resolve_ref)
assert result.status == "accepted"
asyncio.run(main())
The payload and schema relation APIs accept resolve_ref, semantic_provider,
and constraint_provider. Python payload callbacks receive (semantic_path,
value) or (constraints, value); relation callbacks receive the source and
target paths or constraint lists. Term relation APIs additionally accept
host_type_relation(source_path, target_path) for opaque host types.
Use async APIs when callbacks perform I/O. Async wrappers resolve references and adapt providers before invoking the synchronous core. Nested arrays, records, tagged unions, and typed additional fields are traversed for provider calls, including structured operation errors and emitted channels. Async expression evaluation enforces the same step/depth budgets and resolver decision states as the synchronous surface.
Standard vocabulary
schemair.standard exposes STANDARD_SEMANTIC_PATHS,
STANDARD_CONSTRAINT_PATHS, definition maps, standard_vocabulary_registry,
and classify_semantic_path. Standard validation is opt-in:
from schemair import authoring as s, standard, validate_payload
email = s.string(semantic_path=standard.STANDARD_SEMANTIC_PATHS["string"]["email"])
result = validate_payload("ada@example.com", email, **standard.standard_payload_context)
assert result.status == "accepted"
standard_schema_satisfiability, refine_standard_schema, and
intersect_standard_schemas expose the same conservative standard constraint
profile used by the TypeScript reference, including binary64 interval
boundaries, exact float-derived multipleOf checks, pattern handling, and
standard argument domains. The relation context implements the shared
semantic narrowing and constraint implication rules; host regex/date behavior
still follows Python's libraries.
Host policies matter: Python uses its date/time, IP, and regex libraries, while
TypeScript uses JavaScript predicates. Python integers are unbounded, but
standard multipleOf converts numbers to binary64 and compares exact rational
representations. Do not assume arbitrary-precision decimal behavior or
identical regex/date acceptance across hosts. See
standard vocabulary host policies.
Integrations
from schemair import authoring as s, project_json_schema
schema = s.record([s.required_field("name", s.string())])
projection = project_json_schema(schema, target="draft-2020-12")
assert projection.fidelity == "exact"
print(projection.schema, projection.diagnostics)
Targets are draft-2020-12, draft-07, and openapi-3.0.
schema_node_to_json_schema and schema_node_to_openapi_schema return a
diagnostic-bearing projection. to_json_schema returns only the dictionary;
prefer the projection API when fidelity matters. Supported options include
both Python snake_case names and TypeScript-compatible camelCase aliases
for ref and mapper strategies. Fidelity results are exact, lossy, or
unsupported.
to_standard_schema(schema, **context) returns a ~standard adapter whose
validate callable is async and preserves accepted input values.
schemaIR_to_standard_json_schema(schema) exposes jsonSchema.input and
jsonSchema.output callables; each takes a target string and raises ValueError
for unsupported projections. These are Python callable surfaces, not
JavaScript package interface types.
Development and verification scope
From python/, run:
python -m unittest discover -s tests -v
python -m compileall -q schemair
Tests read shared schema, operation, expression, solver, and vocabulary vectors
from ../conformance/, alongside local integration tests. Shared expression
vectors assert evaluated term contents and unresolved variables; solver vectors
assert bindings; rejected cases assert diagnostics; local tests cover provider
decision shapes, async traversal, resolver cycles, and interpreter budgets.
The binding does not execute operations, supply a scheduler or reference
catalog, or provide TypeScript's compile-time Infer utilities. See the
roadmap for remaining parity and ecosystem work.
Metadata
Release files for schemair 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| schemair-0.2.0.tar.gz | 47.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| schemair-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 93.8 kB
Release files / schemair-0.2.0.tar.gz
| Download URL | schemair-0.2.0.tar.gz |
|---|---|
| Size | 47.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f117cbf7abe44f0688ff40debd2299af6f30c65cf8c659284c7e619cfea6b5a
|
|
BLAKE2b-256 checksum How to use checksums |
4d32dcdd974667cd52e79ad389a01e94705f3528f50480a6529f40fb5a6ad0cf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.11
|
Release files / schemair-0.2.0-py3-none-any.whl
| Download URL | schemair-0.2.0-py3-none-any.whl |
|---|---|
| Size | 46.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dc179b573c16f1b80a633af07dae69e0593c490ff5f4126855f329156fb3bb33
|
|
BLAKE2b-256 checksum How to use checksums |
4daacfac9c2b29b7f7efcf75c54413d98280e560f02811efe062f6e85eedd049
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.10.11
|