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
Metadata
Release files for auen 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| auen-0.6.0.tar.gz | 149.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| auen-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 181.7 kB
Release files / auen-0.6.0.tar.gz
| Download URL | auen-0.6.0.tar.gz |
|---|---|
| Size | 149.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bbd7e69cba774476d15df512e7d53ea2ceb69597925f329e5ea4d0c85e06ad9a
|
|
BLAKE2b-256 checksum How to use checksums |
afe36dadc77752b3a73f3e993ffc218b58fdd1d82de2469d0a93eb8ac0a78898
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / auen-0.6.0-py3-none-any.whl
| Download URL | auen-0.6.0-py3-none-any.whl |
|---|---|
| Size | 32.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
783898fe70b38567ab2610b6da2282163fe32c944e5516d2912ecaf7809a5f2c
|
|
BLAKE2b-256 checksum How to use checksums |
45a9df90a09fdeb51b92aefbdc9b79bf1af6055f9f76f642755232a4c8e80d7c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Security for auen 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.