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.3.0.tar.gz (118.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.3.0-py3-none-any.whl (21.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: auen-0.3.0.tar.gz
  • Upload date:
  • Size: 118.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.3.0.tar.gz
Algorithm Hash digest
SHA256 3158af0204589a4d1d55d37189226cd9b7ab59cfbab405b9572b246a0651897a
MD5 5d43335ba301c899df1ba570c082196e
BLAKE2b-256 6fec55a6ab1bedc72ad8a9aeae8ba5441112dd57867b465e7dbb9ea1573b78c1

See more details on using hashes here.

File details

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

File metadata

  • Download URL: auen-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 21.1 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a65472160ffd6fcce6e7ea7528242f3f161d3ba73e05b15238446a8cb09d4cba
MD5 43fd31c9cfb2f49465a131302775daae
BLAKE2b-256 3ec71e64c53f0f33225903befcfbcc48c7cfabb1134c0e3aae19093d40c852a0

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