oscal-bindings (Python)
Typed Python data bindings for OSCAL (Open Security Controls Assessment Language) v1.2.3, generated from the official NIST JSON Schema using datamodel-code-generator (Pydantic v2).
Installation
pip install oscal-bindings
# or
uv add oscal-bindings
The distribution is oscal-bindings; the import package is oscal_bindings.
To install from a local checkout (e.g., as a uv workspace path dependency):
uv add ../oscal-bindings-python
# or
pip install -e ../oscal-bindings-python
To install directly from a Git tag, branch, or commit:
pip install "git+https://github.com/cairn-proofs/oscal-bindings-python.git@main"
Import paths
The bindings live under a namespace scoped to the OSCAL major version:
from oscal_bindings.v1 import parse_oscal, OscalDoc, Catalog
The top-level package re-exports the current major version, so from oscal_bindings import parse_oscal (and oscal_bindings.models, oscal_bindings.parser,
oscal_bindings.extensions) resolves to the same objects:
import oscal_bindings
import oscal_bindings.v1
oscal_bindings.Catalog is oscal_bindings.v1.models.Catalog # True
Examples below use the shorter top-level path. Importing from oscal_bindings.v1 is
equivalent and states which OSCAL major version your code is written against.
OSCAL is backward-compatible within a major version, so a routine 1.x schema refresh
regenerates v1 in place and never changes your imports. Only OSCAL 2.0 would
introduce a second namespace.
Which OSCAL release?
__oscal_schema_version__ reports the exact release the models were generated from:
from oscal_bindings import __oscal_schema_version__
__oscal_schema_version__ # '1.2.3'
Worth checking, because models are strict about unknown fields: a document from a newer 1.x release than the vendored one can fail validation on a field these bindings have never seen. The constant makes that gap visible rather than a mystery.
Usage
Parsing
from oscal_bindings import parse_oscal, parse_oscal_file, parse_catalog, parse_profile
# Parse any OSCAL document
doc = parse_oscal(json_string)
# Parse with type-specific convenience functions
catalog = parse_catalog(json_string) # returns CatalogDocument
profile = parse_profile(json_string) # returns ProfileDocument
# Parse from a file
doc = parse_oscal_file("catalog.json")
parse_oscal and the per-type parsers accept either str or bytes.
Passing bytes directly skips a UTF-8 decode step, which avoids one
document-size string allocation — preferable when the document comes
from a byte-source (HTTP response body, S3 GET, container layer pull):
import urllib.request
with urllib.request.urlopen("https://example.com/catalog.json") as resp:
doc = parse_oscal(resp.read()) # bytes → parsed model, no .decode() needed
Invalid UTF-8 bytes surface as OscalParseError, the same exception
type used for JSON and schema validation failures.
Accessing Parsed Data
catalog = parse_catalog(json_string)
print(catalog.catalog.metadata.title) # plain str
print(catalog.catalog.metadata.version)
Accessors
OscalDoc is a uniform facade over any of the 8 parsed wrappers
(CatalogDocument, ProfileDocument, SystemSecurityPlanDocument,
…). It exposes the structural fields shared across every OSCAL
top-level document — metadata, oscal_version, uuid — without
making the caller dispatch on body type.
from oscal_bindings import OscalDoc
doc = OscalDoc.from_file("ssp.json")
doc.oscal_version # '1.2.0'
doc.uuid # body-level UUID, regardless of document type
doc.metadata.title
doc.body # the underlying Catalog | Profile | SSP | ...
Wrap an already-parsed document directly:
from oscal_bindings import OscalDoc, parse_oscal_file
doc = OscalDoc(parse_oscal_file("ssp.json"))
For Assessment Plans and Assessment Results, assessment_period()
returns the (start, end) date range:
from oscal_bindings import OscalAccessError
doc = OscalDoc.from_file("assessment-results.json")
start, end = doc.assessment_period() # (date(2025, 1, 5), date(2025, 1, 30))
try:
OscalDoc.from_file("catalog.json").assessment_period()
except OscalAccessError as e:
print(e) # only defined for AP / AR
Building back-matter resources
from oscal_bindings import make_hash, make_resource, make_rlink
digest = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
resource = make_resource(
title="Evidence: signed audit report",
description="Q1 audit deliverable, two equivalent mirrors.",
rlinks=[
make_rlink("https://example.com/audit-q1.pdf", media_type="application/pdf",
hashes=[make_hash(digest)]),
make_rlink("s3://evidence-bucket/audit-q1.pdf",
hashes=[make_hash(digest)]),
],
)
# resource.uuid is a freshly generated UUID4
Serialization
from oscal_bindings import serialize_oscal
json_str = serialize_oscal(doc) # pretty-printed (indent: 2)
json_str = serialize_oscal(doc, indent=None) # compact
Validation
from oscal_bindings import validate_oscal
if validate_oscal(json_string):
print("Valid OSCAL document")
Error Handling
from oscal_bindings import parse_oscal, OscalParseError
try:
parse_oscal(invalid_json)
except OscalParseError as e:
print(e) # Human-readable message
print(e.errors) # Pydantic validation error details
Typed Parser Functions
Each OSCAL document type has a dedicated parser:
parse_catalog(json)→CatalogDocumentparse_profile(json)→ProfileDocumentparse_component_definition(json)→ComponentDefinitionDocumentparse_system_security_plan(json)→SystemSecurityPlanDocumentparse_assessment_plan(json)→AssessmentPlanDocumentparse_assessment_results(json)→AssessmentResultsDocumentparse_plan_of_action_and_milestones(json)→PlanOfActionAndMilestonesDocumentparse_mapping_collection(json)→MappingCollectionDocument
Building
hatch build
hatch run release # generate + lint + typing + test + coverage + docs
Architecture
- Version package (
src/oscal_bindings/v1/) — everything below, scoped to the OSCAL major version. Exposes__oscal_schema_version__. - Generated models (
src/oscal_bindings/v1/models.py) — Pydantic v2BaseModelclasses withAnnotatedfield constraints, produced bydatamodel-code-generatorfrom the OSCAL JSON Schema. - Post-processing (
scripts/postprocess_models.py) — renames classes from schema namespace paths to clean short names and names each document wrapper after its root key (e.g.CatalogDocument). - Vendored schemas (
schemas/<release>/) — one directory per OSCAL release; the active bundle is chosen by explicit path. - Runtime (
src/oscal_bindings/v1/parser.py) — parse / serialize / validate utilities and per-type typed parsers. - Extensions (
src/oscal_bindings/v1/extensions/) — hand-written helpers layered on top of the generated models:document.py—OscalDocfacade (uniformmetadata/uuid/oscal_version/assessment_period()accessors).builders.py—make_hash,make_rlink,make_resourceconstructors forback-matterelements.validate_element.py— element-level schema validation utilities.
- Top-level package (
src/oscal_bindings/__init__.pyand themodels/parser/extensionsalias modules) — pure re-exports of the current version package.
Public symbols are re-exported from oscal_bindings.v1 and from the top-level oscal_bindings package, so callers don't need to know which submodule a name lives in.
Requirements
- Python 3.11+
- Pydantic v2
License
Apache License 2.0; see LICENSE. The vendored OSCAL schemas are from NIST and are in the public domain; see NOTICE.
Metadata
Release files for oscal-bindings 0.1.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 | |
|---|---|---|---|
| oscal_bindings-0.1.0.tar.gz | 274.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| oscal_bindings-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 322.8 kB
Release files / oscal_bindings-0.1.0.tar.gz
| Download URL | oscal_bindings-0.1.0.tar.gz |
|---|---|
| Size | 274.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dbe6d9f089e527fa864a614ce82b12635f3b7f6e96e220fd50c42c69a887adb8
|
|
BLAKE2b-256 checksum How to use checksums |
cc591c87bed993cf58fc3074a0cf2fecef38c01a3e089bb6743f46b86e161a7e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / oscal_bindings-0.1.0-py3-none-any.whl
| Download URL | oscal_bindings-0.1.0-py3-none-any.whl |
|---|---|
| Size | 48.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
36134c3b6f5fd422bdd00b5646294851d08db25edd5ec2abe1f039850355d9aa
|
|
BLAKE2b-256 checksum How to use checksums |
dfa095dba80ef685a56f61e966fbd159059c0435eaae38e2a5014c9481a5352e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency log