Skip to content

About

FastAPI feature-based module manager (nest-cli style for FastAPI), written in Rust — pip install fastgen-cli

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

19 Commits

Folders and files

Repository files navigation

⚡ fastgen-cli

A nest-cli style module manager for FastAPI — scaffold modules, keep the project tidy, and let AI agents see the whole structure at a glance.

Zero-config. One command. Fill in the business logic yourself.

PyPI version PyPI downloads Rust License PyPI downloads

English | 简体中文

fastgen-cli: a nest-cli style module manager for FastAPI — scaffold projects and modules, keep structure tidy, keep an auto-maintained registry.


✨ Why fastgen-cli?

FastAPI is famously unopinionated — which is great for freedom, but bad for structure. Projects drift into chaos: routers scattered, entities everywhere, no one knows what modules exist.

fastgen-cli fixes exactly that. It manages module structure, not your business code:

  • 🏗️ Scaffold whole projects — fastgen new my-app creates a best-practice src/-layout FastAPI project (.env, src/main.py, src/core/, module registry, tests/, Alembic migrations) ready to run
  • 🗂️ One module = one folder (<src>/modules/<feature>/), with a consistent shape every time
  • ⚡ Rust binary — one self-contained executable, starts instantly, no Python runtime needed to run the tool
  • 🤖 AI-native — fastgen owns the structure, codex/opencode write the code inside it
  • 🧩 Minimal skeleton — ORM model, schemas, service boundary, router + shared session dependency. Just enough to see the module, never enough to get in the way
  • 📇 Auto-maintained registry — <src>/modules/__init__.py maps every module to its import path; AI agents and devs read it to understand the project instantly
  • 🔌 Shared DB core generated once — <src>/core/ with pydantic-settings config + async SQLAlchemy get_session (best-practice, expire_on_commit=False, AsyncAttrs)
  • 🔁 Alembic migrations out of the box — alembic upgrade head evolves your schema instead of deleting app.db; autogenerate picks up model changes automatically
  • 🛡️ Never overwrites your code — only generates what's missing or empty

📦 Installation

You usually don't need to install anything. uvx runs fastgen on demand:

uvx fastgen-cli make module order

If you use it daily, install it permanently:

uv tool install fastgen-cli     # or: pip install fastgen-cli / uv add fastgen-cli

fastgen is a single self-contained Rust binary shipped inside the wheel (the uv model) — the tool itself needs no Python runtime, and it lands on your PATH as fastgen. Wheels are published for Linux (x86_64 / aarch64, glibc 2.17+), macOS (Intel / Apple Silicon) and Windows.

Alternative installs:

cargo install fastgen-cli    # from crates.io (needs a Rust toolchain)
# or download a prebuilt binary from GitHub Releases:
# https://github.com/YIbaikaishui/fastgen-cli/releases/latest

The projects it scaffolds are ordinary Python 3.11+ FastAPI apps.


🚀 Quick start

Pick a layout, get a project — fastgen new asks which layout, then git clones it (nunu-style):

uvx fastgen-cli new my-app
  • advanced — worked CRUD example (note module), recommended
  • auth — JWT auth wired: register, login, protected routes, bcrypt
  • basic — minimal project, empty registry
  • local — the built-in scaffold: instant, offline, no git needed

Skip the prompt with --layout advanced|basic|local:

uvx fastgen-cli new my-app --layout advanced
cd my-app && uv sync && uv run alembic upgrade head && uv run uvicorn src.main:app --reload

Browse the layouts on GitHub directly (they are just repos — clone or click Use this template): advanced · auth · basic

Use your own layout — point at any git repo (fork, company standard, mirror):

uvx fastgen-cli new my-app -r https://gitee.com/your-org/fastgen-layout-advanced.git

Add a module whenever — one command, main.py never touched:

uvx fastgen-cli make module order

See the whole structure — for you and for your AI agent:

uvx fastgen-cli list

That's it. No config file, no YAML, no spec — run a command, get the skeleton:

$ fastgen list
             Registered modules
┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ module ┃ path             ┃ description    ┃
┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ user   │ src.modules.user │ User module.   │
└────────┴──────────────────┴────────────────┘

🔧 How it works

Four steps: fastgen make module user scaffolds the skeleton, writes the registry entry, auto-mounts the router in main.py, then fastgen list verifies it.

Every fastgen make module <feature> command does four things, in order:

  1. Scaffold — render the module skeleton into <src>/modules/<feature>/ (model / schemas / service / router / tests). It only ever writes missing or empty files; anything already there stays untouched.
  2. Register — add the module to the auto-maintained registry (<src>/modules/__init__.py), a plain modules: dict[str, str] mapping each module name to its import path.
  3. Auto-mount — idempotently sync <src>/main.py so it imports the registry and calls app.include_router(module.router) for each entry, guarded by a # --- fastgen: auto-mount (do not remove) --- marker. No hand-editing main.py when adding modules.
  4. Verify — fastgen list prints the registry as a table, so both you and AI agents see the whole module structure at a glance.

fastgen new <name> is the same mechanism applied to a whole project: it scaffolds src/core/, the registry, tests/, Alembic migrations and .fastgen.json, then lets make module grow the app from there.

The registry is deliberately boring — one plain dict, no metadata, no framework:

# src/modules/__init__.py — auto-maintained by fastgen
modules: dict[str, str] = {
    "user": "src.modules.user",
}

__all__ = ["modules"]

Because nothing generated by fastgen depends on fastgen at runtime, you can drop the tool anytime and keep a completely ordinary FastAPI project.


🤖 Optional: AI agents (codex / opencode)

By default fastgen is a pure deterministic scaffolder — no agent, no API keys, nothing to install beyond uv. If you want an agent to fill the scaffold with real code, opt in with --ai:

npm install -g @openai/codex   # or: curl -fsSL https://opencode.ai/install | bash

fastgen new myapp --ai "task manager API with projects and tasks"
fastgen make module order --ai "line items, status enum, total calculation"

How it works:

  1. fastgen renders the deterministic scaffold (instant, byte-stable)
  2. the agent (codex preferred, opencode as fallback; force one with --agent) fills it in, guided by a prompt that encodes fastgen's conventions
  3. fastgen verifies and reconciles: reports the files the agent touched, auto-registers any module it created, re-syncs the auto-mount block, and syntax-checks the result

Use --agent codex|opencode to pick a specific agent, or point fastgen at any other CLI agent with FASTGEN_AGENT_CMD (e.g. FASTGEN_AGENT_CMD="claude -p"). --dry-run previews the scaffold without running an agent.

🧱 What it generates

fastgen new <name>

A complete, runnable best-practice FastAPI project:

my-app/
├── .env / .env.example         # DATABASE_URL etc.
├── .gitignore                  # ignores .env, venv, __pycache__, *.db
├── .python-version             # 3.11
├── pyproject.toml              # deps + ruff / pytest config
├── README.md
├── .fastgen.json               # {"source_dir": "src"} — layout used by fastgen
├── src/
│   ├── __init__.py
│   ├── main.py                 # FastAPI app; module routers auto-load from the registry
│   ├── core/                   # shared infra (never overwritten)
│   │   ├── __init__.py
│   │   ├── config.py           # pydantic-settings Settings, reads .env
│   │   └── database.py         # Base (AsyncAttrs), async engine, get_session
│   └── modules/
│       └── __init__.py         # 📇 module registry (auto-maintained)
├── migrations/                  # Alembic migrations (alembic.ini at project root)
│   ├── env.py                   # async env; DATABASE_URL from settings, models from the registry
│   ├── script.py.mako
│   └── versions/
│       └── 0001_initial.py      # empty baseline revision
└── tests/
    ├── __init__.py
    ├── conftest.py             # httpx ASGI client fixture
    └── test_health.py          # /health smoke test

fastgen make module <feature>

src/  (or app/ for a legacy project; fastgen auto-detects the layout)
├── core/                        # auto-created on first use (never overwritten)
│   ├── __init__.py
│   ├── config.py                # pydantic-settings Settings, DATABASE_URL from .env
│   └── database.py              # Base (AsyncAttrs), async engine, get_session
└── modules/                     # 📇 vertical slices: one folder per business domain
    ├── __init__.py              # module registry (auto-maintained)
    └── user/                    # each module is internally layered
        ├── __init__.py          # re-exports the router from .api.router
        ├── domain/              # entities + repository port (no I/O or framework)
        │   ├── model.py         # SQLAlchemy entity on Base (__tablename__ = plural)
        │   └── repository.py    # UserRepository Protocol (add/get/list/delete)
        ├── application/         # use cases + DTOs (free of HTTP)
        │   ├── schemas.py       # UserBase / UserCreate / UserUpdate / UserRead
        │   │                    # UserRead has from_attributes=True so ORM objects serialize
        │   └── user_service.py  # UserService (constructor-injected repo) + UserError hierarchy
        ├── infrastructure/      # SQLAlchemy adapter for the repository port
        │   └── user_repository.py
        ├── api/                 # FastAPI layer: SessionDep + router, maps exceptions to HTTP
        │   └── router.py        # APIRouter (prefix="/users")
        └── tests/               # in-memory SQLite test DB + get_session override
            ├── conftest.py
            └── test_user.py

Routers are auto-mounted: fastgen make module idempotently syncs main.py to import registered modules from the registry and app.include_router(...) each — no hand-editing main.py when adding a module (guarded by a fastgen: auto-mount marker).

fastgen make module scaffolds the full vertical-slice skeleton above; every layer already wires the shared session dependency, so you just add endpoints and business logic. The generated router.py looks like:

from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.database import get_session
from src.modules.user.application.user_service import UserNotFound, UserService
from src.modules.user.domain.model import User
from src.modules.user.infrastructure.user_repository import SqlUserRepository

SessionDep = Annotated[AsyncSession, Depends(get_session)]

router = APIRouter(prefix="/users", tags=["users"])


def _service(session: AsyncSession) -> UserService:
    return UserService.from_repository(SqlUserRepository(session))


@router.get("", response_model=list[User])
async def list_users(session: SessionDep) -> list[User]:
    ...

fastgen make resource <name>

make module gives you a skeleton — the shape, with the payload handling left for you. make resource gives you a finished CRUD: declare the fields and the generated code actually works, tests included.

fastgen make resource product --fields "title:str, price:decimal, stock:int, active:bool?, launched:date?"
Field type SQLAlchemy column Python type
str String(255) str
text Text str
int Integer int
float Float float
decimal Numeric(12, 2) Decimal
bool Boolean bool
datetime DateTime(timezone=True) datetime
date Date date
uuid Uuid (auto-generated) UUID

A ? suffix makes a field optional — a nullable column, an X | None schema field, and it's left out of nothing (it still gets exercised by the tests). Omit --fields entirely and you get a single name: str field.

What you get beyond make module:

  • Real columns on the model, from your spec.
  • A service that maps payloads — create builds the entity from the request body and update applies only the fields you actually sent.
  • Offset pagination — GET /products?limit=20&offset=0 returns {total, limit, offset, items}, with limit bounded so an accidental ?limit=100000 is a 422 instead of a full table scan.
  • A passing test suite that walks the whole loop: create → read → update → delete, pagination, 404s and payload validation.
$ uv run pytest
.......                                                    [100%]
7 passed

Resources live in <src>/modules/<name>/ like modules do, so the registry, auto-mount, fastgen list and fastgen doctor work on them unchanged. And because they're strictly better than the skeleton, make resource <name> --force upgrades an existing module in place.


🔁 Migrations (Alembic)

fastgen new ships Alembic scaffolding (alembic.ini + migrations/) wired to your settings and models — schema is managed by migrations, not create_all at startup, so you evolve the dev DB instead of deleting app.db.

uv run alembic upgrade head               # apply all pending migrations (baseline included)
uv run alembic revision --autogenerate -m "add user email"   # diff models -> new migration
uv run alembic upgrade head               # apply it
uv run alembic downgrade -1               # roll back one step
  • migrations/env.py imports every registered module's model so autogenerate sees all tables.
  • Adding Alembic to an existing project: fastgen init alembic writes the scaffolding idempotently (never overwrites). If the DB was already created via create_all, adopt it with uv run alembic stamp head, or drop the dev DB and re-create via upgrade head.

🩺 doctor — keep the structure honest

As a project grows, the registry drifts: someone deletes a module folder but not its entry (which crashes main.py at boot), or adds one by hand and never registers it (its router silently never mounts). doctor makes the drift visible — and repairs what it can:

fastgen doctor            # report; exit code 1 when the app is broken
fastgen doctor --fix      # register orphans, drop stale entries, re-inject the mount block
fastgen doctor --strict   # warnings fail too
fastgen doctor --json     # for scripts and CI

Gate your CI on it:

- run: uvx fastgen-cli doctor --strict

⚖️ How does it compare?

vs. other FastAPI module generators & frameworks

Tool What it is Runtime dependency you must keep Generated module
fastgen-cli Generator only — plain FastAPI (Rust CLI) None Model / schemas / service / router + tests + auto-maintained registry; Alembic migrations
PyNest Framework on FastAPI (NestJS-style) pynest-api (nest.core) Module with @Module / @Controller / @Injectable, DI container
FastKit Meta-framework + CLI (Laravel-style) fastkit-core Full CRUD module (model / schema / repository / service / router)
Gondola CLI with Rails-like conventions gondola-cli + default PostgreSQL stack Models / routers / services / mailers / tests, Alembic migrations
FastStack Full framework (Django-like) faststack-frame App module (models / routes / schemas / services / admin)
RapidKit Module engine + CLI (FastAPI & NestJS) rapidkit-core + npx/poetry toolchain Kits (fastapi.standard / fastapi.ddd) + installable module catalog

Where fastgen stands out

  • Zero runtime lock-in. Everything fastgen generates is plain Python on top of vanilla FastAPI + SQLAlchemy — nothing requires fastgen at runtime. The others all ship their own framework/runtime that your project keeps depending on.
  • No new concepts to learn. No @Module/@Injectable decorators, no DI container, no repository base classes, no workspace metadata. The skeleton uses idioms you already know (SessionDep = Annotated[AsyncSession, Depends(get_session)]).
  • Incremental, not all-or-nothing. fastgen make module grows an existing project (src/ or app/ layout) instead of forcing you to start inside a framework — you can adopt it on top of any FastAPI project, including the ones above.
  • AI/agent-friendly. An auto-maintained registry (src/modules/__init__.py) plus fastgen list means both humans and AI agents see the whole module structure at a glance.
  • Never overwrites. src/core/ is only generated when missing or empty.

Honest trade-off

The others generate more for you: FastKit's full CRUD router, Gondola's mailers, PyNest's dependency injection for complex enterprise apps, RapidKit's module upgrade/rollback lifecycle. Choose them when you want those batteries and can accept their runtime and conventions. Choose fastgen when you want a lean, standard, zero-coupling base that you shape yourself.

Contract-first generators (fastapi-code-generator, OpenAPI Generator python-fastapi) are a different category: they turn an OpenAPI spec into code and complement fastgen when your spec is the source of truth.

vs. starting with uv init

uv init my-app is the natural baseline — minimal, universal, no lock-in. The trade-offs:

uv init fastgen new
What you get pyproject.toml + main.py hello world Complete FastAPI app: .env, src/main.py (lifespan + /health), src/core/ (pydantic-settings + async SQLAlchemy), module registry, tests/, Alembic migrations, ruff/pytest config
Then you must Add deps, build the src/ layout, write lifespan/config/DB/tests by hand Add your business logic
Resulting structure Differs per developer Identical across projects
Module management later None fastgen make module keeps a registry you can fastgen list
Lock-in None Layout is plain files; drop fastgen anytime, nothing generated forces it

Pros of uv init: universal, minimal, zero opinion, and you already have uv installed. Cons: every FastAPI-specific decision (layout, DB session wiring, config, tests) is left to you, so each project ends up structured differently.

Pros of fastgen new: one command yields a complete best-practice base; consistent across the whole org; modules stay discoverable via the registry; never overwrites your code; easy for AI agents to reason about. Cons: opinionated layout (src/ + core/ + registry) — if you need a non-standard structure you adapt it yourself; FastAPI-only.

They're complementary, not competing: a fastgen new project is still managed by uv (uv sync, uv run). And if you did start from uv init, you can adopt fastgen later — run fastgen make module <feature> in the project and it creates core/, modules/ and the registry for you (it auto-detects the layout).


🛠️ CLI reference

Command Description
fastgen new <name> Scaffold a new best-practice src/-layout FastAPI project (core + registry + tests + Alembic)
fastgen make module <feature> Scaffold a feature module (model / schemas / service / router / tests), auto-mount its router, register it
fastgen make resource <name> --fields "title:str, price:decimal" Generate a working CRUD (real columns, payload mapping, pagination, passing tests) and register it
fastgen init alembic Add Alembic migration scaffolding to an existing project (idempotent)
fastgen list List registered modules, import paths, and purposes
fastgen doctor [--fix] [--strict] [--json] Check structure drift (stale registry entries, unmounted modules, missing auto-mount, syntax errors); exit code 1 on errors — CI-gateable
fastgen --version / -V Show version

Options

Flag Applies to Description
--dir <path> / -d new, make module, make resource, init alembic, list, doctor Target project root (default: current dir)
--title <name> new Human-readable app title (defaults to the project name)
--layout <name> new Layout: advanced, basic, local, or a git URL/path (prompts in a terminal)
--repo <url> / -r new Shorthand for --layout <url> — clone any layout repository
--description <text> new Short project description
--dry-run new, make module, make resource, init alembic Preview files without writing anything
--force / -f new, make module, make resource Overwrite existing files

📐 Conventions (fixed)

  • Layout — fastgen new creates a src/ layout and records it in .fastgen.json. fastgen resolves src from .fastgen.json, then by auto-detection, and finally falls back to app/ for existing projects.
  • Modules live in <src>/modules/<feature>/ — one business unit per folder, scaffolded as a vertical slice: domain/ (model.py + repository.py port), application/ (schemas.py XBase/XCreate/XUpdate/XRead with from_attributes on XRead, plus <feature>_service.py), infrastructure/ (Sql*Repository), api/ (router.py), and tests/.
  • Router exposes prefix="/<plural>" (REST-style), reuses SessionDep from <src>.core.database, is auto-mounted into main.py from the registry (guarded by a fastgen: auto-mount marker — don't remove it), and maps domain exceptions to HTTPException.
  • Registry — <src>/modules/__init__.py maps module name → import path. Always kept in sync by fastgen; don't hand-edit.
  • Core — <src>/core/config.py and database.py are generated only when missing or empty. Existing code is never touched, even with --force.
  • Schema — managed by Alembic migrations (not create_all at startup).

🔭 Roadmap

  • new — scaffold a whole best-practice src/-layout project
  • make module — model / schemas / service / router / tests + auto-mount
  • Module registry + fastgen list
  • Alembic migrations (init alembic, autogenerate, upgrade)
  • AI agent integration — codex/opencode fill the scaffolds (--ai), fastgen verifies and reconciles
  • More layouts — advanced, auth and basic, each its own repo (like nunu)
  • make resource — full CRUD with real fields, pagination and passing tests
  • More layouts — task-queue, microservice

🧑‍💻 Development

git clone https://github.com/YIbaikaishui/fastgen-cli.git
cd fastgen-cli
cargo build --release
cargo test

Lint / format: cargo clippy --all-targets and cargo fmt --check.

Publish wheels to PyPI (uv model — maturin + ziglang for portable, low-glibc wheels):

maturin build --release --zig --target <target>   # one per platform
maturin sdist
maturin publish

📄 License

MIT © 一白开水

About

FastAPI feature-based module manager (nest-cli style for FastAPI), written in Rust — pip install fastgen-cli

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages