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.1.1.tar.gz (65.8 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.1.1-py3-none-any.whl (85.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyoas-0.1.1.tar.gz
  • Upload date:
  • Size: 65.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.10 {"installer":{"name":"uv","version":"0.11.10","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.1.1.tar.gz
Algorithm Hash digest
SHA256 1baa505c24d3ce1675e16fba794f1f0403f9ab50ef8a9b9a46acf7ca63cbcada
MD5 13b446a528ddfc8fbaec6a2d40613125
BLAKE2b-256 5c10b03ede60f6b9048ab66236817e86ef60ff680fbebcadd623750890a40ceb

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pyoas-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 85.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.10 {"installer":{"name":"uv","version":"0.11.10","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.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 0a75a063269e2e6804f893a4b4f1171af3686a405a179d9aeafa3f969cd7ffde
MD5 5504033a1cea42381aef0795a8acb3a8
BLAKE2b-256 40e59415e1278c8c30568f923f062deef3db1f29a2ac76b7bb2ca537a171c79b

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