Skip to main content

Scaffold production FastAPI services from YAML

Project description

feathers

Scaffold production FastAPI services from one YAML file — in under 10 seconds.

PyPI · Why · Architecture · Demo · Schema Example

pypi python ci coverage mypy license


What it is

feathers is a CLI that reads a single YAML schema and emits a production-ready, MVC-structured FastAPI service — with tests, migrations, observability, CI, platform middleware, and Docker, all wired up and running.

Unlike a cookiecutter (which gives you a dead tree the moment you touch it), feathers add uses libcst AST rewriting to slot new code into existing services without clobbering a single line you wrote. Regeneration stays safe for the lifetime of the project.


Why it's different

Most scaffolders feathers
Cookiecutter templates — stale after first edit Incremental AST codegen — regeneration stays safe forever
String templating Pydantic v2 schema validated before any file is written
Hand-wired middleware per service Platform middleware shipped with every generated service
You protect your edits with prayer Fence markers (# feathers: begin hand-written) — regen never touches protected regions
Pick your own stack Opinionated & consistent — FastAPI + uv + Alembic + structlog + Prometheus + OpenTelemetry

Quick start

pip install feathers-cli

feathers new --schema demos/users.yaml --name hello-users --out .
cd hello-users && make run
# → http://localhost:8000/docs

Add an endpoint later without regenerating the whole service:

# edit demos/users.yaml → append a new endpoint
feathers add endpoint --schema demos/users.yaml --service ./hello-users

Your hand-written code between # feathers: begin hand-written fences stays untouched.


CLI reference

Command Purpose
feathers new --schema FILE --name NAME --out DIR Generate a new service from a schema
feathers add endpoint --schema FILE --service DIR Slot a new endpoint into an existing service
feathers add model --schema FILE --service DIR Add a new model stub
feathers lint SCHEMA Validate a YAML schema without generating
feathers doctor Environment health check (Python, uv)
feathers bench (v0.2) Run Locust benchmarks against a generated service

How it works

flowchart TD
    YAML[schema.yaml] --> Loader[loader<br/>YAML → dict]
    Loader --> Schema[schema<br/>Pydantic v2 frozen validation]
    Schema --> Context[context<br/>type mapping · snake/pascal · plural]
    Context --> Renderer[renderer<br/>Jinja2 · 21 templates]
    Context --> Patcher[ast_patcher<br/>libcst · for feathers add]
    Renderer --> Service[Generated FastAPI service]
    Patcher --> Service

new vs add

sequenceDiagram
    participant User
    participant CLI as feathers CLI
    participant Val as Pydantic validator
    participant Gen as Jinja renderer
    participant AST as libcst patcher
    participant FS as File system

    User->>CLI: feathers new
    CLI->>Val: validate schema
    Val-->>CLI: ok
    CLI->>Gen: render 21 templates
    Gen->>FS: write full service tree

    User->>CLI: feathers add endpoint
    CLI->>Val: validate schema
    Val-->>CLI: ok
    CLI->>AST: patch routers/*.py
    AST->>FS: read existing file
    AST->>AST: find marker fences
    AST->>AST: insert between markers
    AST->>FS: write file (hand-written code untouched)

See docs/ARCHITECTURE.md for the deep dive.


Schema anatomy

service:
  name: hello_users
  description: A minimal users service
  python: "3.12"

models:
  - name: User
    fields:
      - { name: id,       type: uuid,     primary: true }
      - { name: email,    type: str,      unique: true, indexed: true }
      - { name: name,     type: str }
      - { name: created,  type: datetime }
    soft_delete: true
    audit: true

endpoints:
  - { method: GET,  path: /users/{id}, handler: user.get,    auth: any }
  - { method: POST, path: /users,      handler: user.create, auth: admin }
  - { method: GET,  path: /users,      handler: user.list,   auth: any, paginate: cursor }

observability:
  metrics: prometheus
  tracing: otel
  logging: structlog

deploy:
  target: render
  min_instances: 1
  health: /health

Every field is validated by frozen Pydantic v2 models — if the schema is wrong, feathers refuses to write a single file.


Generated service layout

hello-users/
├── src/hello_users/
│   ├── main.py                  # FastAPI app + middleware
│   ├── api/routers/             # One router per model
│   ├── services/                # Business logic layer
│   ├── repositories/            # Data access layer
│   ├── models/                  # Dataclass stubs (SQLAlchemy wiring in v0.2)
│   ├── schemas/                 # Pydantic DTOs
│   └── core/
│       └── platform.py          # /health, /version, X-Request-ID, X-Platform-Token
├── tests/
│   └── test_health.py
├── .github/workflows/ci.yml     # lint → test → build
├── Dockerfile
├── Makefile                     # make run | test | lint | format | typecheck
├── render.yaml                  # one-click Render deploy
└── pyproject.toml               # uv-managed

Tech stack

Concern Choice
CLI framework Typer
YAML validation Pydantic v2 (frozen models)
Template engine Jinja2 (21 templates per service)
AST rewriting libcst
Package manager uv
Lint / Types ruff + mypy strict
Tests pytest + coverage

Engineering philosophy

Principle How it shows up
Spec-TDD 45 tests across loader, schema, renderer, AST patcher, CLI. Red-first.
Negative-space programming Literal types for field types, HTTP methods, auth roles. Frozen Pydantic models. Schema validation rejects invalid input before any file is written.
MVC-style layering (adapted for CLI) cli → generator → schema. Each layer has one responsibility and never reaches across.
Typed everything mypy --strict passes. No any in source. Public APIs fully type-hinted.
Pure core, imperative shell Schema validation, context building, and rendering are pure. File I/O lives only in the CLI entry points.
One responsibility per module loader (I/O), service (schema defs), context (view transforms), renderer (Jinja), ast_patcher (AST).

Testing

make test                                  # full suite
uv run pytest --cov=src/feathers --cov-report=term-missing
uv run pytest -m "not slow"                # skip e2e generation + boot
Metric Value
Test count 45 tests
Coverage 86% (target: 100%)
E2E @pytest.mark.slow — generates the users service, uv sync, boots uvicorn, hits /health
CI GitHub Actions: ruff → mypy → pytest → uv build

Roadmap to 100% coverage

Module Current Gap
cli.py 73% add_endpoint / add_model error paths, name-mismatch warning
generator/context.py 88% plural() edge cases ("entry" → "entries", "status" → "status")
generator/renderer.py 92% Template load / render error paths
schema/loader.py 94% OSError on file read, YAML non-dict root
generator/ast_patcher.py 96% Missing routers_dir / models_dir error branches

Benchmarks (v0.2 target)

Metric Target
Generated GET /users/{id} throughput ≥ 10,000 req/s
Generated GET /users/{id} p99 latency < 30 ms

Run via feathers bench (Locust) against local Postgres.


Deployment

  • feathers-cli itself → published to PyPI on v* tag push via GitHub Actions
  • Generated services → deploy to Render, Fly.io, or Docker (target chosen in schema)

Contributing

git clone https://github.com/Abdul-Muizz1310/feathers.git
cd feathers
uv sync --all-extras
make test
make lint

Guidelines:

  1. Open an issue before non-trivial PRs
  2. Red-first TDD — failing test in the commit before the fix
  3. make lint typecheck test must pass

License

MIT — see LICENSE.


Built to make regenerating FastAPI services boring.

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

feathers_cli-0.1.1.tar.gz (16.0 kB view details)

Uploaded Source

Built Distribution

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

feathers_cli-0.1.1-py3-none-any.whl (24.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: feathers_cli-0.1.1.tar.gz
  • Upload date:
  • Size: 16.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","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 feathers_cli-0.1.1.tar.gz
Algorithm Hash digest
SHA256 f4154469aac6fcca0662f30f418dcbf9cd03b1a73b2d101dfdc03be308671ebc
MD5 6be3052be8875b2d8331b5f92b378c8d
BLAKE2b-256 e366bc1e2975c33eea8ab34f7bb7969e13ff776bd901a72c8e7d410d021c5e65

See more details on using hashes here.

File details

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

File metadata

  • Download URL: feathers_cli-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 24.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","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 feathers_cli-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a394a6079027a3e3a15b73cbb0013365a570d4ede6bd42c3a6166a3a20acdd49
MD5 31d73e570e81e14cff71b733dc4a6e51
BLAKE2b-256 553642f6ab5e97cc16e2432e25750134125bd4d7f404b3ef21ccba228a345262

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