Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

jentic-openapi-datamodels

Low-level data models for OpenAPI specifications.

This package provides data model classes for representing OpenAPI specification objects in Python.

Features

Low-Level Architecture

  • Preserve Everything: All data from source documents preserved exactly as-is, including invalid values
  • Zero Validation: No validation or coercion during parsing - deferred to higher layers
  • Separation of Concerns: Low-level model focuses on faithful representation; validation belongs elsewhere

Source Tracking

  • Complete Source Fidelity: Every field tracks its exact YAML node location
  • Precise Error Reporting: Line and column numbers via start_mark and end_mark
  • Metadata Preservation: Full position tracking for accurate diagnostics

Python Integration

  • Python-Idiomatic Naming: snake_case field names (e.g., bearer_format, property_name)
  • Spec-Aligned Mapping: Automatic YAML name mapping (e.g., bearerFormatbearer_format)
  • Type Safety: Full type hints with Generic types (FieldSource[T], KeySource[T], ValueSource[T])

Extensibility

  • Extension Support: Automatic extraction of OpenAPI x-* specification extensions
  • Unknown Field Tracking: Capture typos and invalid fields for validation tools
  • Generic Builder Pattern: Core build_model() function with object-specific builders for complex cases

Performance

  • Memory Efficient: Immutable frozen dataclasses with __slots__ for optimal memory usage
  • Shared Context: All instances share a single YAML constructor for efficiency

Version Support

  • OpenAPI 2.0: Planned for future release
  • OpenAPI 3.0.x: Fully implemented
  • OpenAPI 3.1.x: Fully implemented with JSON Schema 2020-12 support
  • OpenAPI 3.2.x: Planned for future release

Installation

pip install jentic-openapi-datamodels

Prerequisites:

  • Python 3.11+

Quick Start

Parsing with the datamodel-low Parser Backend (Recommended)

The easiest way to parse OpenAPI documents into datamodels is using the datamodel-low backend from jentic-openapi-parser. This backend automatically detects the OpenAPI version and returns the appropriate typed datamodel:

from jentic.apitools.openapi.parser.core import OpenAPIParser
from jentic.apitools.openapi.parser.backends.datamodel_low import DataModelLow
from jentic.apitools.openapi.datamodels.low.v30.openapi import OpenAPI30
from jentic.apitools.openapi.datamodels.low.v31.openapi import OpenAPI31

# Create parser with datamodel-low backend
parser = OpenAPIParser("datamodel-low")

# Parse OpenAPI 3.0 document - automatically returns OpenAPI30
doc = parser.parse("""
openapi: 3.0.4
info:
  title: Pet Store API
  version: 1.0.0
paths:
  /pets:
    get:
      summary: List all pets
      responses:
        '200':
          description: A list of pets
""", return_type=DataModelLow)

assert isinstance(doc, OpenAPI30)
print(doc.openapi.value)  # "3.0.4"
print(doc.info.value.title.value)  # "Pet Store API"

# Parse OpenAPI 3.1 document - automatically returns OpenAPI31
doc = parser.parse("""
openapi: 3.1.2
info:
  title: Pet Store API
  version: 1.0.0
paths: {}
""", return_type=DataModelLow)

assert isinstance(doc, OpenAPI31)
print(doc.openapi.value)  # "3.1.2"

Benefits of datamodel-low backend:

  • Automatic version detection (no manual version checking)
  • Returns strongly-typed OpenAPI30 or OpenAPI31 objects
  • Complete source tracking preserved
  • Single-step parsing (no need to manually call build())

Manual Parsing with Builder Functions

For advanced use cases or custom workflows, you can manually parse and build datamodels:

Parsing OpenAPI 3.0 Documents

The manual approach uses the ruamel-ast backend followed by calling the builder function:

from jentic.apitools.openapi.parser.core import OpenAPIParser
from jentic.apitools.openapi.parser.backends.ruamel_ast import MappingNode
from jentic.apitools.openapi.datamodels.low.v30 import build

# Parse OpenAPI document
parser = OpenAPIParser("ruamel-ast")
root = parser.parse("""
openapi: 3.0.4
info:
  title: Pet Store API
  version: 1.0.0
paths:
  /pets:
    get:
      summary: List all pets
      responses:
        '200':
          description: A list of pets
""", return_type=MappingNode)

# Build OpenAPI document model
openapi_doc = build(root)

# Access document fields via Python naming (snake_case)
print(openapi_doc.openapi.value)  # "3.0.4"
print(openapi_doc.info.value.title.value)  # "Pet Store API"
print(openapi_doc.info.value.version.value)  # "1.0.0"

# Access nested fields with full type safety
for path_key, path_item in openapi_doc.paths.value.path_items.items():
    print(f"Path: {path_key.value}")  # "/pets"
    if path_item.value.get:
        operation = path_item.value.get.value
        print(f"  Summary: {operation.summary.value}")  # "List all pets"

Parsing OpenAPI 3.1 Documents with JSON Schema 2020-12

OpenAPI 3.1 fully supports JSON Schema 2020-12, including advanced features like boolean schemas, conditional validation and vocabulary declarations:

from jentic.apitools.openapi.parser.core import OpenAPIParser
from jentic.apitools.openapi.parser.backends.ruamel_ast import MappingNode
from jentic.apitools.openapi.datamodels.low.v31 import build

# Parse OpenAPI 3.1 document with JSON Schema 2020-12 features
parser = OpenAPIParser("ruamel-ast")
root = parser.parse("""
openapi: 3.1.2
info:
  title: Pet Store API
  version: 1.0.0
paths:
  /pets:
    get:
      responses:
        '200':
          description: Pet list
          content:
            application/json:
              schema:
                type: array
                prefixItems:
                  - type: string
                  - type: integer
                items: false
                contains:
                  type: object
                  required: [id]
""", return_type=MappingNode)

openapi_doc = build(root)

# Access JSON Schema 2020-12 features
schema = openapi_doc.paths.value.path_items["/pets"].value.get.value.responses.value["200"].value.content.value["application/json"].value.schema
print(schema.prefix_items.value[0].type.value)  # "string"
print(schema.items.value)  # False (boolean schema)
print(schema.contains.value.required.value[0].value)  # "id"

Parsing Individual Spec Objects

You can also parse individual OpenAPI specification objects:

from jentic.apitools.openapi.parser.core import OpenAPIParser
from jentic.apitools.openapi.parser.backends.ruamel_ast import MappingNode
from jentic.apitools.openapi.datamodels.low.v30.security_scheme import build as build_security_scheme

# Parse a Security Scheme object
parser = OpenAPIParser("ruamel-ast")
root = parser.parse("""
type: http
scheme: bearer
bearerFormat: JWT
""", return_type=MappingNode)

security_scheme = build_security_scheme(root)

# Access via Python field names (snake_case)
print(security_scheme.bearer_format.value)  # "JWT"

# Access source location information
print(security_scheme.bearer_format.key_node.value)  # "bearerFormat"
print(security_scheme.bearer_format.key_node.start_mark.line)  # Line number

You can also parse OpenAPI 3.1 Schema objects with JSON Schema 2020-12 features:

from jentic.apitools.openapi.parser.core import OpenAPIParser
from jentic.apitools.openapi.parser.backends.ruamel_ast import MappingNode
from jentic.apitools.openapi.datamodels.low.v31.schema import build as build_schema

# Parse a Schema object with JSON Schema 2020-12 features
parser = OpenAPIParser("ruamel-ast")
root = parser.parse("""
type: object
properties:
  id:
    type: integer
  tags:
    type: array
    prefixItems:
      - type: string
      - type: string
    items: false
patternProperties:
  "^x-":
    type: string
unevaluatedProperties: false
if:
  properties:
    premium:
      const: true
then:
  required: [support_tier]
""", return_type=MappingNode)

schema = build_schema(root)

# Access JSON Schema 2020-12 fields via Python naming (snake_case)
print(schema.properties.value["id"].type.value)  # "integer"
print(schema.pattern_properties.value["^x-"].type.value)  # "string"
print(schema.unevaluated_properties.value)  # False
print(schema.prefix_items.value[0].type.value)  # "string"

# Access conditional schema fields
print(schema.if_.value.properties.value["premium"].const.value)  # True
print(schema.then_.value.required.value[0].value)  # "support_tier"

# Access source location information
print(schema.type.key_node.start_mark.line)  # Line number for "type" key

Field Name Mapping

YAML camelCase fields automatically map to Python snake_case:

  • bearerFormatbearer_format
  • authorizationUrlauthorization_url
  • openIdConnectUrlopenid_connect_url

Special cases for Python reserved keywords and $ fields:

  • inin_
  • ifif_
  • thenthen_
  • elseelse_
  • notnot_
  • $refref
  • $idid
  • $schemaschema

Source Tracking

The package provides three immutable wrapper types for preserving source information:

FieldSource[T] - For OpenAPI fields with key-value pairs

  • Used for: Fixed fields (name, bearer_format) and patterned fields (status codes, path items, schema properties)
  • Tracks: Both key and value nodes
  • Example: SecurityScheme.bearer_format is FieldSource[str], response status codes are FieldSource[Response]

KeySource[T] - For dictionary keys

  • Used for: keys in OpenAPI fields, x-* extensions and mapping dictionaries
  • Tracks: Only key node
  • Example: Keys in Discriminator.mapping are KeySource[str]

ValueSource[T] - For dictionary values and array items

  • Used for: values in OpenAPI fields, in x-* extensions, mapping dictionaries and array items
  • Tracks: Only value node
  • Example: Values in Discriminator.mapping are ValueSource[str]
from jentic.apitools.openapi.parser.core import OpenAPIParser
from jentic.apitools.openapi.parser.backends.ruamel_ast import MappingNode
from jentic.apitools.openapi.datamodels.low.v30 import build

# FieldSource: Fixed specification fields in OpenAPI document
parser = OpenAPIParser("ruamel-ast")
root = parser.parse("""
openapi: 3.0.4
info:
  title: Pet Store API
  version: 1.0.0
paths: {}
""", return_type=MappingNode)
openapi_doc = build(root)

field = openapi_doc.info.value.title  # FieldSource[str]
print(field.value)  # "Pet Store API" - The actual value
print(field.key_node)  # YAML node for "title"
print(field.value_node)  # YAML node for "Pet Store API"

# KeySource/ValueSource: Dictionary fields (extensions, mapping)
# Extensions in OpenAPI objects use KeySource/ValueSource
root = parser.parse("""
openapi: 3.0.4
info:
  title: API
  version: 1.0.0
  x-custom: value
  x-another: data
paths: {}
""", return_type=MappingNode)
openapi_doc = build(root)

for key, value in openapi_doc.info.value.extensions.items():
    print(key.value)  # KeySource[str]: "x-custom" or "x-another"
    print(key.key_node)  # YAML node for the extension key
    print(value.value)  # ValueSource: "value" or "data"
    print(value.value_node)  # YAML node for the extension value

Location Ranges

Access precise location ranges within the source document using start_mark and end_mark:

from jentic.apitools.openapi.parser.core import OpenAPIParser
from jentic.apitools.openapi.parser.backends.ruamel_ast import MappingNode
from jentic.apitools.openapi.datamodels.low.v30 import build

yaml_content = """
openapi: 3.0.4
info:
  title: Pet Store API
  version: 1.0.0
  description: A sample Pet Store API
paths: {}
"""

parser = OpenAPIParser("ruamel-ast")
root = parser.parse(yaml_content, return_type=MappingNode)
openapi_doc = build(root)

# Access location information for any field
field = openapi_doc.info.value.title

# Key location (e.g., "title")
print(f"Key start: line {field.key_node.start_mark.line}, col {field.key_node.start_mark.column}")
print(f"Key end: line {field.key_node.end_mark.line}, col {field.key_node.end_mark.column}")

# Value location (e.g., "Pet Store API")
print(f"Value start: line {field.value_node.start_mark.line}, col {field.value_node.start_mark.column}")
print(f"Value end: line {field.value_node.end_mark.line}, col {field.value_node.end_mark.column}")

# Full field range (from key start to value end)
start = field.key_node.start_mark
end = field.value_node.end_mark
print(f"Field range: ({start.line}:{start.column}) to ({end.line}:{end.column})")

Invalid Data Handling

Low-level models preserve invalid data without validation:

from jentic.apitools.openapi.parser.core import OpenAPIParser
from jentic.apitools.openapi.parser.backends.ruamel_ast import MappingNode
from jentic.apitools.openapi.datamodels.low.v30 import build

parser = OpenAPIParser("ruamel-ast")
root = parser.parse("""
openapi: 3.0.4
info:
  title: 123  # Intentionally wrong type for demonstration (should be string)
  version: 1.0.0
paths: {}
""", return_type=MappingNode)

openapi_doc = build(root)
print(openapi_doc.info.value.title.value)  # 123 (preserved as-is)
print(type(openapi_doc.info.value.title.value))  # <class 'int'>

# Invalid data is preserved with full source tracking for validation tools
print(openapi_doc.info.value.title.value_node.start_mark.line)  # Line number

Error Reporting

This architecture—where the low-level model preserves data without validation and validation tools consume that data—allows the low-level model to remain simple while enabling sophisticated validation tools to provide user-friendly error messages with exact source locations.

Testing

Run the test suite:

uv run --package jentic-openapi-datamodels pytest packages/jentic-openapi-datamodels -v

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jentic_openapi_datamodels-1.0.0a57.tar.gz (57.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jentic_openapi_datamodels-1.0.0a57-py3-none-any.whl (129.4 kB view details)

Uploaded Python 3

File details

Details for the file jentic_openapi_datamodels-1.0.0a57.tar.gz.

File metadata

File hashes

Hashes for jentic_openapi_datamodels-1.0.0a57.tar.gz
Algorithm Hash digest
SHA256 2bbf8e582e60849c44fa52c9299ff6f04780d8b4cb00c26a746e1df50f4af871
MD5 840012144f710eccf5e521a3db91f417
BLAKE2b-256 ca535d44e6984f3d49497395080d36741f6c0d4ba3267998670002d6796d8657

See more details on using hashes here.

Provenance

The following attestation bundles were made for jentic_openapi_datamodels-1.0.0a57.tar.gz:

Publisher: release.yml on jentic/jentic-openapi-tools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jentic_openapi_datamodels-1.0.0a57-py3-none-any.whl.

File metadata

File hashes

Hashes for jentic_openapi_datamodels-1.0.0a57-py3-none-any.whl
Algorithm Hash digest
SHA256 a56c935221227778c10e0ef72793c157719e68642ad69c6426b6e7e2959768fa
MD5 6acb036e7081e87f8aff2f08f9772e2c
BLAKE2b-256 1054bc10a5f18fbfb4c37f06f7e9d2fad913148f5d2c2b52e73ea12af5e735e2

See more details on using hashes here.

Provenance

The following attestation bundles were made for jentic_openapi_datamodels-1.0.0a57-py3-none-any.whl:

Publisher: release.yml on jentic/jentic-openapi-tools

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.0.0a57 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page