Skip to main content

Generate Pydantic v2 models and FastAPI routers from OpenAPI 3.0/3.1 specs

Project description

pyoas

Generate Pydantic v2 models and FastAPI routers from an OpenAPI spec. Organized as a uv workspace with independent, installable packages.

Packages

Package Purpose
pyoas Spec loading, ref resolution, tag extraction, Jinja2 rendering, Pydantic v2 model generation, CLI
pyoas[fastapi] FastAPI router generation + service stubs + test scaffolding (adds FastAPI dependency)
pyoas[claude] Claude Code skill generation (optional, no extra runtime dependencies)

pyoas[fastapi] and pyoas[claude] both extend the base pyoas package.

Quick start

# Install
uv add pyoas[fastapi]

# Create a minimal config
cat > pyoas.yaml << 'EOF'
spec: openapi.yaml
output:
  models: src/generated/models
  routers: src/generated/routers
EOF

# Generate Pydantic models and FastAPI routers
uv run pyoas generate

# Optionally scaffold service stubs and test files
uv run pyoas scaffold services

A more complete config with all optional features:

spec: openapi.yaml
output:
  models: src/generated/models
  routers: src/generated/routers
services:
  generate: true
  output: src/services
  import_path: myapp.services  # Python import path for service module
tests:
  generate: true
  output: tests/generated
  not_found_exception: "HTTPException(status_code=404, detail='Not found')"
skills:
  generate: true  # requires pyoas[claude]

Then:

uv run pyoas generate   # models + routers + service stubs + tests + skills

Configuration reference (pyoas.yaml)

spec

Path to the OpenAPI 3.0/3.1 spec file (YAML or JSON). Resolved relative to the config file. Required.

output

Key Default Description
models src/generated/models Output directory for generated model files
routers src/generated/routers Output directory for generated router files
models_import (derived) Python import path for models; derived from models path if omitted
routers_import (derived) Python import path for routers; derived from routers path if omitted
source_root src Filesystem prefix stripped when deriving Python import paths

default_tag

Default: "default". Operations with no tag are grouped under this name.

model_config

Key Default Description
extra "ignore" Pydantic extra setting for response/shared models
request_extra "forbid" Pydantic extra setting for request-only models
frozen false Makes generated models immutable
populate_by_name true Allow populating fields by Python name as well as alias

fields

Key Default Description
snake_case true Convert camelCase field names to snake_case with an alias
enums_as_literals true Render small enums as Literal[...] instead of Enum subclasses

format

Key Default Description
enabled true Run ruff format on generated files after writing

templates

Key Default Description
models null Path to a directory of custom Jinja2 templates overriding model templates
routers null Path to a directory of custom Jinja2 templates overriding router templates

services

Key Default Description
generate false Scaffold service stub files
output src/services Output directory for service files
overwrite false Overwrite existing service files on re-run
import_path "" Python import path used by routers to import the service (e.g. myapp.services)

tests

Key Default Description
generate false Scaffold pytest test stub files
output tests/generated Output directory for test files
overwrite false Overwrite existing test files on re-run (default: append new test classes only)
not_found_exception null Exception expression used in test_not_found stubs (e.g. HTTPException(status_code=404))

skills

Requires pyoas[claude] to be installed.

Key Default Description
generate false Generate Claude Code skill files
output .claude/commands Output directory for skill files
overwrite false Overwrite existing skill files on re-run

Generated output

Models (pyoas)

One file per tag: {models_output}/{tag}/models.py. Schemas referenced by multiple tags go to {models_output}/shared/models.py.

src/generated/models/
  __init__.py
  pets/
    models.py      # Pet, PetCreate, PetList, ...
  shared/
    models.py      # schemas used by more than one tag

Routers (pyoas[fastapi])

One file per tag: {routers_output}/{tag}/router.py. An __init__.py at the root re-exports all routers.

src/generated/routers/
  __init__.py      # from .pets import router as pets_router; ...
  pets/
    router.py      # APIRouter with typed endpoint stubs

Service stubs

One file per tag: {services_output}/{tag}.py. Scaffolded once; never overwritten by default.

src/services/
  pets.py          # PetsService class with async method stubs

Test scaffolding

One test file per tag plus a shared conftest.py with model factories.

tests/generated/
  conftest.py      # make_pet(), make_pet_list(), ...
  test_pets.py     # TestListPets, TestCreatePet, TestGetPet, ...

Each test class covers one endpoint and includes:

  • test_endpoint_exists — verifies the route returns something other than 404/405
  • Validation tests for required fields, numeric bounds, string constraints, enum violations
  • test_not_found — verifies 404 when the service raises the configured exception
  • test_success — happy-path stub (auto-implemented for GET/DELETE, stubbed for others)

CLI reference

pyoas models        # generate Pydantic models only
pyoas fastapi       # generate FastAPI routers only
pyoas generate      # generate models + routers (+ services/tests/skills if configured)
pyoas scaffold services  # scaffold service stubs (skips existing files)

All commands accept:

  • --config PATH — path to config file (default: pyoas.yaml)
  • --tags TAG1,TAG2 — limit generation to specific tags
  • --clean — purge output directory before generating

Claude Code integration (pyoas[claude])

Install pyoas[claude] and set skills.generate: true in your config. Running pyoas generate will write Claude Code skill files to .claude/commands/:

Skill Invocation Purpose
implement-tests.md /implement-tests tests/generated/test_pets.py Implement all pytest.skip("implement me") stubs in a test file
add-test-case.md /add-test-case tests/generated/test_pets.py "scenario" Add a new test method for the described scenario
review-generated.md /review-generated Cross-reference generated code against the OpenAPI spec and flag issues

Development

# Install in editable mode with all extras
uv sync --extra fastapi --extra claude

# Run all tests
uv run pytest

# Run tests for a single area
uv run pytest tests/fastapi/

# Update snapshots
uv run pytest tests/fastapi/ --snapshot-update

# Lint and type-check
uv run ruff check src/
uv run mypy src/

Project details


Download files

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

Source Distribution

pyoas-0.2.0.tar.gz (72.4 kB view details)

Uploaded Source

Built Distribution

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

pyoas-0.2.0-py3-none-any.whl (93.0 kB view details)

Uploaded Python 3

File details

Details for the file pyoas-0.2.0.tar.gz.

File metadata

  • Download URL: pyoas-0.2.0.tar.gz
  • Upload date:
  • Size: 72.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyoas-0.2.0.tar.gz
Algorithm Hash digest
SHA256 610e91a97006b95b807615e182ef87105ec2707d34a0abe6630606570dabadc1
MD5 48d32753c50994efdceeb0d4987b5013
BLAKE2b-256 b41faf63daa1768427f52edcadbac544e0aba9af8dca417f935705cfc5469263

See more details on using hashes here.

File details

Details for the file pyoas-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pyoas-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 93.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyoas-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d6b1d05c91e78b28a9c77202ee1e212208c58aca6688a34d408696fd126bb9e9
MD5 35f045335d44b56b44cd4b67d5579fc2
BLAKE2b-256 63062d4c11b97215581d1484b7a2159a8ca2f2e9b8df1ff57505a3839f9ae459

See more details on using hashes here.

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