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.3.0.tar.gz (76.1 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.3.0-py3-none-any.whl (97.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyoas-0.3.0.tar.gz
  • Upload date:
  • Size: 76.1 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.3.0.tar.gz
Algorithm Hash digest
SHA256 93372fdb13c0656ca5ae296ec661269b5281bc0c001d7854fa6d673c0acfaa5b
MD5 c2e88274cc04ddcfadc5a9fb640000d6
BLAKE2b-256 b609489f33a19f9a4ee543930483cb6ba758f67cda2e593e21a69f6cb91e80e7

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pyoas-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 97.2 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0d586d9396a6e2762319e05b86309cb1d7a91191472a5b8b24ba78d4f2581d8f
MD5 bcc7bdd1eb0425215bba35de671a3557
BLAKE2b-256 7e11611e99db27e0e8035e216360ff48feb0de9be789a957d7cd0726c491abfd

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