Skip to main content

Jentic OpenAPI Data Models

Project description

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

Project details


Release history Release notifications | RSS feed

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.0a21.tar.gz (57.4 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.0a21-py3-none-any.whl (129.1 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for jentic_openapi_datamodels-1.0.0a21.tar.gz
Algorithm Hash digest
SHA256 829c3edf11fb69ca74874e416b52cde39a9c61ed31498d13e52f747ff0682032
MD5 edc870b42f1bb001695a74efcda1fe5e
BLAKE2b-256 3c57b122081bcdb903b560a1ce74b0d9bc100683e09a460df06d39f086f91c7f

See more details on using hashes here.

Provenance

The following attestation bundles were made for jentic_openapi_datamodels-1.0.0a21.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.0a21-py3-none-any.whl.

File metadata

File hashes

Hashes for jentic_openapi_datamodels-1.0.0a21-py3-none-any.whl
Algorithm Hash digest
SHA256 5a44cab9603e841a4d37590f0f638b954776be6a0b0cbafe0176983a0b0bb3e2
MD5 e5db5e5d2354672fcb810af98eba8fee
BLAKE2b-256 184c17eb6befee0e6b4303aafa4983112458a00453c4932dd7375d5368bbfb3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for jentic_openapi_datamodels-1.0.0a21-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.

Supported by

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