Skip to main content

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 CrudRouterBuilder

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)
app.include_router(CrudRouterBuilder.for_model(Author, get_session).build())
app.include_router(CrudRouterBuilder.for_model(Book, get_session).build())

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.

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.for_model(Book, get_session)
    .with_auth(AuthConfig(dependency=get_current_user))
    .build()
)

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.for_model(Book, get_session)
    .with_auth(AuthConfig(dependency=get_current_user))
    .with_policy(OwnerPolicy())
    .build()
)

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.for_model(Book, get_session)
    .with_hooks(
        HooksConfig(
            before_create=validate_isbn,
            after_create=notify_new_book,
        )
    )
    .build()
)

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.for_model(Book, get_session)
    .with_pagination(PaginationConfig(default_limit=20, max_limit=100))
    .with_filters(
        FilterConfig(
            fields={"title": FilterFieldConfig(ops=frozenset({"eq"}))},
            sort_fields=["title", "isbn"],
        )
    )
    .build()
)

Selecting Operations

from auen import CrudRouterBuilder, Operation

app.include_router(
    CrudRouterBuilder.for_model(Book, get_session)
    .with_operations({Operation.LIST, Operation.READ})  # read-only API
    .build()
)

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

auen-0.2.4.tar.gz (112.9 kB view details)

Uploaded Source

Built Distribution

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

auen-0.2.4-py3-none-any.whl (18.9 kB view details)

Uploaded Python 3

File details

Details for the file auen-0.2.4.tar.gz.

File metadata

  • Download URL: auen-0.2.4.tar.gz
  • Upload date:
  • Size: 112.9 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

Hashes for auen-0.2.4.tar.gz
Algorithm Hash digest
SHA256 99c23eee9e7cf2dc691ffed3ba3bb369f46e6cd06e35607ae660ee98408ee1dc
MD5 6e4643c3be89f7a91943c3d4fa1991a1
BLAKE2b-256 72671e533f18da046a2a4bef0d4cab932b9d8bf3dc76d4ca988c4f8b9cab561b

See more details on using hashes here.

File details

Details for the file auen-0.2.4-py3-none-any.whl.

File metadata

  • Download URL: auen-0.2.4-py3-none-any.whl
  • Upload date:
  • Size: 18.9 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

Hashes for auen-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 faf59ece4274e07ad49176986626b0223b489ebbac7353b74ddb4395fa4b25b4
MD5 86529b7f8b825397832fe844814aaf11
BLAKE2b-256 01871f71e1051b0e55a88d0241a0816df52cc63d3cd40696c3197cdacab633e8

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