Skip to content

Python API

The pyodcs package wraps the Rust implementation via PyO3. All parsing and validation semantics match the odcs crate.

Installation

pip install pyodcs

Module overview

import pyodcs

pyodcs.__version__                # package version
pyodcs.UPSTREAM_SPEC_VERSION      # "3.1.0"
pyodcs.UPSTREAM_REPOSITORY_URL    # upstream ODCS GitHub URL
pyodcs.CODES                      # dict of diagnostic code constants
pyodcs.VALIDATION_PHASES          # dict of validation phase name constants (since 0.6.0)

CODES

Maps short names to stable odcs:* identifiers:

pyodcs.CODES["INVALID_KIND"]  # "odcs:invalid-kind"

See diagnostics.md for when each code fires.

VALIDATION_PHASES

Maps short names to validationPhase JSON values (metadata, not error codes):

pyodcs.VALIDATION_PHASES["DOCUMENT"]     # "document"
pyodcs.VALIDATION_PHASES["JSON_SCHEMA"]  # "jsonSchema"

Validation reports include validationPhase on each validation-stage diagnostic. Parse-stage diagnostics omit the field.

For choosing between parse_and_validate, parse, and validate_result, see API decision guide.

import pyodcs

report = pyodcs.parse_and_validate(open("contract.yaml", "rb").read(), format="yaml")
assert pyodcs.is_valid(report)

Data shapes

Parse result (parse(), parse_file())

{
    "contract": {...} | None,   # parsed contract dict when parse succeeded
    "report": {
        "diagnostics": [...]
    }
}

Validation report (validate(), parse_and_validate(), validate_result())

{
    "diagnostics": [
        {
            "id": "odcs:missing-required-field",
            "severity": "error",
            "stage": "validation",
            "category": "structure",
            "message": "...",
            "object_ref": "id",
            "remediation": None
        }
    ]
}

validate_result() may add internal cache keys (_odcs_validated) — do not rely on them in application code.

A report is valid when is_valid(report) is True (no error-severity diagnostics). is_valid() also accepts a parse result dict and reads diagnostics from report.

Parsing

parse(content, format="yaml")

Parse a document from text or bytes. Returns a dict with contract and report keys.

result = pyodcs.parse(open("contract.yaml", "rb").read(), format="yaml")
contract = result["contract"]   # dict or None
report = result["report"]       # {"diagnostics": [...]}

format may be "yaml", "yml", or "json".

parse_file(path)

Parse from a file path. Raises FileNotFoundError when the file is missing. Raises ValueError for unsupported file extensions.

result = pyodcs.parse_file("examples/minimal.odcs.yaml")

Validation

validate(contract)

Validate a parsed contract dict. Returns {"diagnostics": [...]}. Validation includes pinned ODCS v3.1.0 JSON Schema checks.

validation = pyodcs.validate(contract)

validate_result(result)

Merge parse-time and validation diagnostics from a parse() / parse_file() result.

report = pyodcs.validate_result(result)

parse_and_validate(content, format="yaml")

Parse and validate in one step. Returns {"diagnostics": [...]}.

report = pyodcs.parse_and_validate(content, format="yaml")

parse_and_validate_paths(primary, deps=None, *, includes=None, registry=None)

Parse and validate a primary contract with optional dependency paths and registry root (since 0.9.0).

report = pyodcs.parse_and_validate_paths(
    "consumer.yaml",
    registry="./contracts/",
)

Registry helpers (0.9.0+)

indexed = pyodcs.registry_index_and_save("./contracts/")
entry = pyodcs.registry_lookup("./contracts/", "provider-contract")
entries = pyodcs.registry_list("./contracts/")

registry_index builds an in-memory index without writing to disk. registry_load reads an existing index file.

Each registry helper returns JSON-compatible dicts. Index operations return:

{
    "entries": [
        {
            "id": "provider-contract",
            "version": "1.0.0",
            "path": "contracts/provider.yaml",
            "apiVersion": "v3.1.0",
            "tags": [],
            "contentHash": "...",
            "indexedAt": "...",
        }
    ],
    "report": {"diagnostics": [...]}
}

registry_lookup returns an entry dict or None. registry_list returns a list of entry dicts.

Compatibility diff

Compare two parsed contract dicts:

old = pyodcs.parse_file("old.yaml")["contract"]
new = pyodcs.parse_file("new.yaml")["contract"]
report = pyodcs.diff(old, new)

Report shape

{
    "hasBreaking": True,
    "changes": [
        {
            "kind": "breaking",
            "code": "odcs:compatibility-breaking",
            "message": "removed property 'email'",
            "path": "schema[customers].properties[email]",
        }
    ],
}
Field Description
hasBreaking True when any change has kind: "breaking"
changes List of changes with kind, code, message, path

See Compatibility analysis.

pinned_schema(*, json_metadata=False)

Return the pinned ODCS v3.1.0 JSON Schema dict.

schema = pyodcs.pinned_schema()
metadata = pyodcs.pinned_schema(json_metadata=True)

is_valid(report)

Returns True when no error-level diagnostics are present.

assert pyodcs.is_valid(report)

Inspection

inspect(contract)

Human-readable summary string.

print(pyodcs.inspect(contract))

inspect_summary(contract)

Structured summary dict (same fields as odcs inspect --json).

summary = pyodcs.inspect_summary(contract)
# {"id", "name", "version", "apiVersion", "kind", "status",
#  "schemaCount", "qualityCount"}

quality_rules_count(contract)

Count nested quality rules across all schema objects and properties.

count = pyodcs.quality_rules_count(contract)

CLI

The pyodcs console script mirrors the Rust odcs CLI:

pyodcs validate examples/minimal.odcs.yaml
pyodcs validate consumer.yaml --dep provider.yaml
pyodcs validate consumer.yaml --registry ./contracts/
pyodcs inspect examples/minimal.odcs.yaml --json
pyodcs diagnostics examples/minimal.odcs.yaml
pyodcs diff old.yaml new.yaml
pyodcs registry index ./contracts/
pyodcs registry lookup ./contracts/ provider-contract
pyodcs schema
pyodcs schema --json
pyodcs schema --url-only
pyodcs version
pyodcs version --json

Exit codes match the Rust CLI: 0 valid, 1 validation error, 2 parse/I/O failure.

Error handling pattern

import pyodcs
import sys

try:
    result = pyodcs.parse_file("contract.yaml")
except (FileNotFoundError, OSError, ValueError) as e:
    print(e, file=sys.stderr)
    sys.exit(2)

report = pyodcs.validate_result(result)
if not pyodcs.is_valid(report):
    for d in report["diagnostics"]:
        print(f"{d['id']}: {d['message']}")
    sys.exit(1)

Rust parity

Python Rust
parse() parse()
parse_file() parse_file()
validate() validate()
parse_and_validate() parse_and_validate()
diagnostic_codes() / CODES Diagnostic code constants
validation_phases() / VALIDATION_PHASES Validation phase name constants (since 0.6.0)
parse_and_validate_paths() parse_and_validate_set_with_registry()
diff() diff()
registry_*() index_registry, load_registry, Registry
pinned_schema() odcs schema

See also cli.md and diagnostics.md.