Docs / fastkit-cli / make

make

Generate production-ready FastAPI modules and individual files. The primary FastKit CLI command — use make module for a full stack at once, or individual sub-commands to generate specific files.

make module

Generates a complete module with all six files in one command — the fastest path from idea to working endpoint.

fastkit make module <name> [OPTIONS]

Options

OptionShortDefaultDescription
--dir-dmodulesRoot directory where the module folder is created
--async-aFalseGenerate async repository, service, and router
--signals-sFalseAlso generate signals.py and listeners.py
--force-fFalseOverwrite existing files

Examples

bash
# Basic sync module
fastkit make module Invoice

# Async module
fastkit make module Invoice --async

# Module with signals and listeners
fastkit make module Invoice --signals

# Async module with signals
fastkit make module Invoice --async --signals

# Custom output directory
fastkit make module Invoice --dir src/modules

# Compound name (PascalCase or snake_case — both work)
fastkit make module InvoiceItem
fastkit make module invoice_item   # same result

# Overwrite existing files
fastkit make module Invoice --force

Generated files

__init__.py
Package init — exports model, service and router
models.py
SQLAlchemy model with BaseWithTimestamps and IntIdMixin
schemas.py
Pydantic Create, Update, and Response schemas
repository.py
Extends Repository (or AsyncRepository with --async)
service.py
Extends BaseCrudService (or AsyncBaseCrudService with --async)
router.py
FastAPI router with index, show, store, update, destroy endpoints
signals.py
Signal instances for created, updated, deleted events — generated with --signals
listeners.py
Receiver stubs connected to signals — generated with --signals

make module also automatically registers the new model in alembic/env.py so it's picked up by the next fastkit migrate make.

make model

Generates only the SQLAlchemy model file. Useful when you have an existing module and need to add a model separately.

fastkit make model <name> [OPTIONS]
OptionShortDefaultDescription
--path-p.Target directory for the generated file
--force-fFalseOverwrite existing file
bash
fastkit make model Invoice
fastkit make model Invoice --path modules/invoices

Generated models.py

python
from fastkit_core.database import BaseWithTimestamps, IntIdMixin
# from fastkit_core.database import UUIDMixin, SoftDeleteMixin, SlugMixin

class Invoice(BaseWithTimestamps, IntIdMixin):
    __tablename__ = "invoices"
    # Define your fields here

make schema

Generates only the Pydantic schemas file with Create, Update, and Response classes.

fastkit make schema <name> [OPTIONS]
OptionShortDefaultDescription
--path-p.Target directory for the generated file
--force-fFalseOverwrite existing file
bash
fastkit make schema Invoice
fastkit make schema Invoice --path modules/invoices

Generated schemas.py

python
from fastkit_core.validation import BaseSchema

class InvoiceCreate(BaseSchema):
    pass  # Define your fields here

class InvoiceUpdate(BaseSchema):
    pass  # All fields optional for partial updates

class InvoiceResponse(BaseSchema):
    id: int
    model_config = {"from_attributes": True}

make repository

Generates only the repository file. Supports both sync and async variants.

fastkit make repository <name> [OPTIONS]
OptionShortDefaultDescription
--path-p.Target directory for the generated file
--async-aFalseGenerate async repository using AsyncRepository
--force-fFalseOverwrite existing file
bash
fastkit make repository Invoice
fastkit make repository Invoice --async
fastkit make repository Invoice --path modules/invoices
python
from fastkit_core.database import Repository
from sqlalchemy.orm import Session
from .models import Invoice

class InvoiceRepository(Repository[Invoice]):
    def __init__(self, session: Session):
        super().__init__(Invoice, session)
python
from fastkit_core.database import AsyncRepository
from sqlalchemy.ext.asyncio import AsyncSession
from .models import Invoice

class InvoiceRepository(AsyncRepository[Invoice]):
    def __init__(self, session: AsyncSession):
        super().__init__(Invoice, session)

make service

Generates only the service file. The place for your business logic — add lifecycle hooks and custom methods after generation.

fastkit make service <name> [OPTIONS]
OptionShortDefaultDescription
--path-p.Target directory for the generated file
--async-aFalseGenerate async service using AsyncBaseCrudService
--force-fFalseOverwrite existing file
bash
fastkit make service Invoice
fastkit make service Invoice --async
python
from fastkit_core.services import BaseCrudService
from sqlalchemy.orm import Session
from .models import Invoice
from .schemas import InvoiceCreate, InvoiceUpdate, InvoiceResponse
from .repository import InvoiceRepository

class InvoiceService(BaseCrudService[Invoice, InvoiceCreate, InvoiceUpdate, InvoiceResponse]):
    def __init__(self, session: Session):
        super().__init__(
            InvoiceRepository(session),
            response_schema=InvoiceResponse
        )
    # Add your business logic, lifecycle hooks, and custom methods here
python
from fastkit_core.services import AsyncBaseCrudService
from sqlalchemy.ext.asyncio import AsyncSession
from .models import Invoice
from .schemas import InvoiceCreate, InvoiceUpdate, InvoiceResponse
from .repository import InvoiceRepository

class InvoiceService(AsyncBaseCrudService[Invoice, InvoiceCreate, InvoiceUpdate, InvoiceResponse]):
    def __init__(self, session: AsyncSession):
        super().__init__(
            InvoiceRepository(session),
            response_schema=InvoiceResponse
        )
    # Add your async business logic, lifecycle hooks, and custom methods here

make signals

Generates signals.py and listeners.py for an existing module. Use this when you want to add the signal system to a module that was created without the --signals flag.

fastkit make signals <name> [OPTIONS]
OptionShortDefaultDescription
--path-p.Target directory for the generated files
--force-fFalseOverwrite existing files
bash
# Add signals to an existing module
fastkit make signals Invoice

# Target a specific path
fastkit make signals Invoice --path modules/invoices

# Overwrite existing signals.py and listeners.py
fastkit make signals Invoice --force

Generated signals.py

python
from fastkit_core.events import Signal

__all__ = ['invoice_created', 'invoice_updated', 'invoice_deleted']

invoice_created = Signal('invoice.created')
invoice_updated = Signal('invoice.updated')
invoice_deleted = Signal('invoice.deleted')

Generated listeners.py

python
from .signals import invoice_created, invoice_updated, invoice_deleted

# @invoice_created.connect
# async def on_invoice_created(payload: dict) -> None:
#     """Called after an Invoice is successfully created."""
#     pass

# @invoice_updated.connect
# async def on_invoice_updated(payload: dict) -> None:
#     """Called after an Invoice is successfully updated."""
#     pass

# @invoice_deleted.connect
# async def on_invoice_deleted(payload: dict) -> None:
#     """Called after an Invoice is successfully deleted."""
#     pass

Important: listeners.py must be imported at application startup or receivers will never register. Add import modules.invoices.listeners to your main.py lifespan. Without this import, signals fire silently with no effect — no error, just silence.

Using signals in the service

After generating the files, fire signals from your service lifecycle hooks. The service only calls send() — it has no knowledge of who is listening:

python
from .signals import invoice_created, invoice_deleted

class InvoiceService(AsyncBaseCrudService[...]):

    async def after_create(self, instance: Invoice) -> None:
        await invoice_created.send({
            'id':          instance.id,
            'customer_id': instance.customer_id,
            'total':       instance.total,
        })

    async def after_delete(self, id: int) -> None:
        await invoice_deleted.send({'id': id})

For full documentation on the Signal system — error isolation, payload conventions, testing, and custom backends — see fastkit-core → Events.

make router

Generates only the router file with all five CRUD endpoints pre-wired to the service.

fastkit make router <name> [OPTIONS]
OptionShortDefaultDescription
--path-p.Target directory for the generated file
--async-aFalseGenerate async router using get_async_db
--force-fFalseOverwrite existing file
bash
fastkit make router Invoice
fastkit make router Invoice --async

Generated endpoints

HandlerMethodPathDescription
indexGET/invoicesPaginated list with page and per_page params
showGET/invoices/{id}Single record — 404 if not found
storePOST/invoicesCreate — returns 201
updatePUT/invoices/{id}Update — partial update supported
destroyDELETE/invoices/{id}Delete — returns 204
python
from fastapi import APIRouter, Depends
from fastkit_core.database import get_db
from fastkit_core.http import success_response, paginated_response
from sqlalchemy.orm import Session
from .schemas import InvoiceCreate, InvoiceUpdate
from .service import InvoiceService

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

def get_service(session: Session = Depends(get_db)) -> InvoiceService:
    return InvoiceService(session)

@router.get("/")
def index(page: int = 1, per_page: int = 20, svc: InvoiceService = Depends(get_service)):
    items, meta = svc.paginate(page=page, per_page=per_page)
    return paginated_response(items=items, pagination=meta)

@router.get("/{id}")
def show(id: int, svc: InvoiceService = Depends(get_service)):
    return success_response(data=svc.find_or_fail(id).model_dump())

@router.post("/", status_code=201)
def store(data: InvoiceCreate, svc: InvoiceService = Depends(get_service)):
    return success_response(data=svc.create(data).model_dump())

@router.put("/{id}")
def update(id: int, data: InvoiceUpdate, svc: InvoiceService = Depends(get_service)):
    return success_response(data=svc.update(id, data).model_dump())

@router.delete("/{id}", status_code=204)
def destroy(id: int, svc: InvoiceService = Depends(get_service)):
    svc.delete(id)
python
from fastapi import APIRouter, Depends
from fastkit_core.database import get_async_db
from fastkit_core.http import success_response, paginated_response
from sqlalchemy.ext.asyncio import AsyncSession
from .schemas import InvoiceCreate, InvoiceUpdate
from .service import InvoiceService

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

def get_service(session: AsyncSession = Depends(get_async_db)) -> InvoiceService:
    return InvoiceService(session)

@router.get("/")
async def index(page: int = 1, per_page: int = 20, svc: InvoiceService = Depends(get_service)):
    items, meta = await svc.paginate(page=page, per_page=per_page)
    return paginated_response(items=items, pagination=meta)

@router.get("/{id}")
async def show(id: int, svc: InvoiceService = Depends(get_service)):
    return success_response(data=(await svc.find_or_fail(id)).model_dump())

@router.post("/", status_code=201)
async def store(data: InvoiceCreate, svc: InvoiceService = Depends(get_service)):
    return success_response(data=(await svc.create(data)).model_dump())

@router.put("/{id}")
async def update(id: int, data: InvoiceUpdate, svc: InvoiceService = Depends(get_service)):
    return success_response(data=(await svc.update(id, data)).model_dump())

@router.delete("/{id}", status_code=204)
async def destroy(id: int, svc: InvoiceService = Depends(get_service)):
    await svc.delete(id)

Common Options

All make sub-commands share these patterns:

--force / overwriting files

By default FastKit CLI refuses to overwrite existing files to prevent accidental data loss. Use --force to override:

bash
# Will fail if modules/invoices/ already exists
fastkit make module Invoice

# Overwrites all files in modules/invoices/
fastkit make module Invoice --force

--force overwrites files without confirmation. Commit your changes before using it.

--async vs sync

Choose sync or async based on your project's database setup:

Sync (default)
  • Uses Session from get_db
  • Uses Repository
  • Uses BaseCrudService
  • Standard def handlers
  • Simpler — good for most projects
Async (--async)
  • Uses AsyncSession from get_async_db
  • Uses AsyncRepository
  • Uses AsyncBaseCrudService
  • async def handlers with await
  • Better for high-concurrency I/O workloads

Sub-command summary

Sub-commandGeneratesSupports --async
make moduleAll 6 files (+ signals with --signals)
make modelmodels.py
make schemaschemas.py
make repositoryrepository.py
make serviceservice.py
make routerrouter.py
make signalssignals.py + listeners.py