Metadata-Version: 2.4
Name: schemair
Version: 0.2.0
Summary: Python bindings for the SchemaIR portable contract protocol
Author: SchemaIR contributors
License-Expression: Apache-2.0
Project-URL: Repository, https://github.com/schemair/schemair
Project-URL: Documentation, https://github.com/schemair/schemair#readme
Project-URL: Issues, https://github.com/schemair/schemair/issues
Keywords: schema,types,contracts,protocol
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# 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:

```sh
python -m pip install -e ./python
```

The package is published on PyPI as `schemair`.

## Store, calculate, and validate a contract

```python
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

```python
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](../spec/protocol/operations.md).

Reference resolvers receive a tuple of path segments. For example:

```python
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:

```python
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](../spec/protocol/standard-vocabulary.md).

## Integrations

```python
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:

```sh
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](../ROADMAP.md) for remaining parity and ecosystem work.
