Auto-generate FastAPI CRUD endpoints from SQLModel models
Project description
auen
Auto-generate FastAPI CRUD endpoints from SQLModel models.
Why auen?
Today writing repetitive code, like CRUD endpoints, became a single prompt to your Agent of Choice. So, why should we generate them from code instead? I believe, this is about standardization: Having the piece of mind that there is no artistic freedom in your CRUD endpoints, you'll expose to internal or external users. The ultimate goal here is to let you focus on the development of a data model beneath and let the interaction layer appear for free, at least for the set of endpoints that don't need any further customization.
Installation
pip install auen
Quickstart
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine
from sqlmodel import Field, SQLModel
from sqlmodel.ext.asyncio.session import AsyncSession
from auen import CrudRouterSpec, build_crud_routers
engine = create_async_engine("sqlite+aiosqlite:///bookstore.db")
class Author(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
bio: str | None = None
class Book(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
title: str
isbn: str
author_id: int | None = Field(default=None, foreign_key="author.id")
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
async with engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
yield
async def get_session() -> AsyncGenerator[AsyncSession, None]:
async with AsyncSession(engine) as session:
yield session
app = FastAPI(lifespan=lifespan)
routers = build_crud_routers(
specs=[
CrudRouterSpec(model=Author),
CrudRouterSpec(model=Book),
],
session_dep=get_session,
)
for router in routers:
app.include_router(router)
This spec-based interface is the canonical style. CrudRouterBuilder remains
available as a fluent compatibility adapter.
from auen import CrudRouterSpec, Operation, build_crud_routers
routers = build_crud_routers(
specs=[
CrudRouterSpec(model=Author, operations={Operation.LIST, Operation.READ}),
CrudRouterSpec(model=Book, operations={Operation.CREATE, Operation.LIST}),
],
session_dep=get_session,
)
for router in routers:
app.include_router(router)
Without auen, this requires writing five endpoint functions per model, request/response models, query parameter handling, error responses, and database session management — roughly 150 lines of code that looks the same in every project.
With auen, you get:
| Method | Path | Description | Method | Path | Description | |
|---|---|---|---|---|---|---|
| POST | /authors/ |
Create an author | POST | /books/ |
Create a book | |
| GET | /authors/ |
List authors | GET | /books/ |
List books | |
| GET | /authors/{id} |
Get an author | GET | /books/{id} |
Get a book | |
| PATCH | /authors/{id} |
Update an author | PATCH | /books/{id} |
Update a book | |
| DELETE | /authors/{id} |
Delete an author | DELETE | /books/{id} |
Delete a book |
Explicit Schemas
For production, define explicit schemas to control exactly which fields are exposed. Without auen, you'd maintain these schemas and write all the endpoint code that uses them — auen handles the latter.
If you want fully derived schema bundles (including nested payload models), use:
from auen import derive_schemas
schemas = derive_schemas(Book)
# schemas.create / schemas.read / schemas.update
# schemas.nested_create / schemas.nested_update
For advanced use cases, you can access the full schema container directly:
from auen import derive_schema_container
container = derive_schema_container(Book)
# container.nested_update is used by PATCH /{id}/nested
Authentication
Auth integration is a common copy-paste pattern — every project writes the same dependency injection and per-operation gating. auen standardizes it.
from fastapi import Header, HTTPException
from auen import AuthConfig, CrudRouterBuilder
def get_current_user(authorization: str = Header()) -> str:
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401)
return authorization[7:] # return user identity
app.include_router(
CrudRouterBuilder()
.with_auth(AuthConfig(dependency=get_current_user))
.build_routers(models=[Book], session_dep=get_session)[0]
)
Row-Level Policies
Row-level security is frequently reimplemented incorrectly — a missed filter on the LIST endpoint leaks data. auen makes it declarative so the policy is applied consistently across all operations.
from auen import AuthConfig, CrudRouterBuilder
class OwnerPolicy:
def can_create(self, user, obj_in):
return True
def can_read(self, user, db_obj):
return db_obj.author_id == user
def can_update(self, user, db_obj, obj_in):
return db_obj.author_id == user
def can_delete(self, user, db_obj):
return db_obj.author_id == user
def filter_list_query(self, user, query):
return query.where(Book.author_id == user)
app.include_router(
CrudRouterBuilder()
.with_auth(AuthConfig(dependency=get_current_user))
.with_policy(OwnerPolicy())
.build_routers(models=[Book], session_dep=get_session)[0]
)
Lifecycle Hooks
from auen import CrudRouterBuilder, HooksConfig
async def notify_new_book(session, book_obj, current_user):
... # send email, log event, publish to queue, etc.
async def validate_isbn(session, obj_in, current_user):
... # raise HTTPException to abort
app.include_router(
CrudRouterBuilder()
.with_hooks(
HooksConfig(
before_create=validate_isbn,
after_create=notify_new_book,
)
)
.build_routers(models=[Book], session_dep=get_session)[0]
)
Pagination & Filtering
Pagination and filtering are boilerplate that every API writes identically — offset/limit handling, sort-field validation, operator mapping. auen generates it from a declaration.
from auen import CrudRouterBuilder, PaginationConfig, FilterConfig, FilterFieldConfig
app.include_router(
CrudRouterBuilder()
.with_pagination(PaginationConfig(default_limit=20, max_limit=100))
.with_filters(
FilterConfig(
fields={"title": FilterFieldConfig(ops=frozenset({"eq"}))},
sort_fields=["title", "isbn"],
)
)
.build_routers(models=[Book], session_dep=get_session)[0]
)
Selecting Operations
from auen import CrudRouterBuilder, Operation
app.include_router(
CrudRouterBuilder()
.with_operations({Operation.LIST, Operation.READ}) # read-only API
.build_routers(models=[Book], session_dep=get_session)[0]
)
Development
# Install dependencies
uv sync
# Run tests
uv run pytest
# Format
uv run ruff format .
# Lint
uv run ruff check .
# Type check
uv run ty check
License
MIT
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
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 auen-0.6.0.tar.gz.
File metadata
- Download URL: auen-0.6.0.tar.gz
- Upload date:
- Size: 149.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","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 |
bbd7e69cba774476d15df512e7d53ea2ceb69597925f329e5ea4d0c85e06ad9a
|
|
| MD5 |
34ec316fa60ee7cbbaf9cd899cbdc14a
|
|
| BLAKE2b-256 |
afe36dadc77752b3a73f3e993ffc218b58fdd1d82de2469d0a93eb8ac0a78898
|
File details
Details for the file auen-0.6.0-py3-none-any.whl.
File metadata
- Download URL: auen-0.6.0-py3-none-any.whl
- Upload date:
- Size: 32.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","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 |
783898fe70b38567ab2610b6da2282163fe32c944e5516d2912ecaf7809a5f2c
|
|
| MD5 |
350f982bc5d2db6609d5b8dc544e5081
|
|
| BLAKE2b-256 |
45a9df90a09fdeb51b92aefbdc9b79bf1af6055f9f76f642755232a4c8e80d7c
|