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 exceptiontest_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
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1baa505c24d3ce1675e16fba794f1f0403f9ab50ef8a9b9a46acf7ca63cbcada
|
|
| MD5 |
13b446a528ddfc8fbaec6a2d40613125
|
|
| BLAKE2b-256 |
5c10b03ede60f6b9048ab66236817e86ef60ff680fbebcadd623750890a40ceb
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a75a063269e2e6804f893a4b4f1171af3686a405a179d9aeafa3f969cd7ffde
|
|
| MD5 |
5504033a1cea42381aef0795a8acb3a8
|
|
| BLAKE2b-256 |
40e59415e1278c8c30568f923f062deef3db1f29a2ac76b7bb2ca537a171c79b
|