Metadata-Version: 2.4
Name: better_crud
Version: 0.2.5
Summary: A better CRUD library for FastAPI.
Home-page: https://github.com/bigrivi/better_crud
Author: bigrivi
Author-email: sunjianghong@gmail.com
License: Apache License 2.0
Project-URL: Documentation, https://bigrivi.github.io/better_crud
Project-URL: Source Code, https://github.com/bigrivi/better_crud
Keywords: fastapi,better-crud,crud,async,sqlalchemy,pydantic
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Framework :: FastAPI
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi<1.0,>=0.111.0
Requires-Dist: sqlalchemy<3.0,>=2.0.30
Requires-Dist: fastapi_pagination<1.0,>=0.12.24
Requires-Dist: pydantic<3.0,>=2.7.3
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: project-url
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

<div align="center">
  <h1>⚡ BetterCRUD</h1>
</div>
<p align="center" markdown=1>
  <i>Stop writing CRUD boilerplate. Generate a complete, production-ready CRUD API from one decorator.</i></br>
  <sub>FastAPI CRUD routing library — class-based, fully async, fully testable.</sub>
</p>
<p align="center" markdown=1>
<a href="https://github.com/bigrivi/better_crud/actions/workflows/pytest.yml" target="_blank">
  <img src="https://github.com/bigrivi/better_crud/actions/workflows/pytest.yml/badge.svg" alt="Tests"/>
</a>
<a href="https://pypi.org/project/better_crud/" target="_blank">
  <img src="https://img.shields.io/pypi/v/better_crud?color=%2334D058&label=pypi%20package" alt="PyPi Version"/>
</a>
<a href="https://pypi.org/project/better_crud/" target="_blank">
  <img src="https://img.shields.io/pypi/pyversions/better_crud.svg?color=%2334D058" alt="Supported Python Versions"/>
</a>
<a href="https://codecov.io/github/bigrivi/better_crud" target="_blank">
 <img src="https://codecov.io/github/bigrivi/better_crud/graph/badge.svg?token=MEMUT1FH4K"/>
 </a>
</p>

---

**Documentation**: <a href="https://bigrivi.github.io/better_crud/" target="_blank">https://bigrivi.github.io/better_crud/</a>

**Source Code**: <a href="https://github.com/bigrivi/better_crud" target="_blank">https://github.com/bigrivi/better_crud</a>

---

## 🎯 The Problem

Every FastAPI project needs the same endpoints — `GET/POST /resource`, `GET/PUT/DELETE /resource/{id}`. And every project writes them by hand, over and over. The result: thousands of lines of nearly identical code, with filtering, pagination, and permissions bolted on inconsistently.

**BetterCRUD eliminates that entire category of boilerplate.** Define your model, write one decorator, and get a complete CRUD API — with rich filtering, pagination, sorting, relationship queries, soft delete, ACL hooks, and lifecycle events — all generated for you, while you stay in full control.

## ✨ What You Get

```python
from better_crud import crud

@crud(
    pet_router,
    dto={"create": PetCreate, "update": PetUpdate},
    serialize={"base": PetPublic},
)
class PetController():
    service: PetService = Depends(PetService)
```

This one decorator generates **8 routes** with:

- 🚀 **Rich filtering** — 27 operators (`$eq` `$cont` `$in` `$between` `$any` …), nested `$and`/`$or`, relationship queries
- 📄 **Pagination** — offset pagination, plus `always` / `optional` / `disabled` modes
- 🔗 **Relationship queries & storage** — joins, loads, many-to-many / one-to-many / many-to-one / one-to-one
- 🔐 **ACL hooks** — `get_feature` / `get_action` for permission guards, row-scoping via `auth.filter`
- ♻️ **Lifecycle hooks** — `on_before/after_create/update/delete`, plus soft delete with recover
- 🧩 **Custom endpoints** — `@crud_action` decorator for business actions like `adopt` / `approve`
- 🛠️ **Extensible** — custom backends, custom response schemas, custom pagination models

## Requirements
- **Python:** Version 3.9 or newer.
- **FastAPI:** BetterCRUD is built to work with FastAPI, so having FastAPI in your project is essential.
- <b>SQLAlchemy:</b> Version 2.0.30 or newer. BetterCRUD uses SQLAlchemy for database operations.
- <b>Pydantic:</b> Version 2.7.3 or newer. BetterCRUD leverages Pydantic models for data validation and serialization.

## Installation
```bash
pip install better-crud
```

## Why BetterCRUD over fastapi-crudrouter?

The long-time de-facto CRUD library for FastAPI, [fastapi-crudrouter](https://github.com/awtkns/fastapi-crudrouter), has been **unmaintained since Nov 2023**. BetterCRUD is actively maintained and provides a strict superset of its features:

| Feature | BetterCRUD | fastapi-crudrouter |
| ------- | :--------: | :----------------: |
| Actively maintained (2026) | ✅ | ❌ (stalled since 2023) |
| FastAPI 0.141+ support | ✅ | ❌ |
| SQLAlchemy 2.0 async | ✅ | ✅ |
| Rich filter operators (`$eq` `$cont` `$notany` `$in` …) | ✅ | ❌ |
| Nested relationship queries & joins | ✅ | ❌ |
| Many-to-many relationship storage | ✅ | ❌ |
| Lifecycle hooks (`on_after_create/update/...`) | ✅ | ❌ |
| ACL / permission guards | ✅ | ❌ |
| Soft delete + recover | ✅ | ❌ |
| Custom endpoints (`@crud_action`) | ✅ | ❌ |
| Pagination | ✅ | ✅ |
| Class views & function views | ✅ | ✅ |
| Test coverage | 99% | — |

> 💡 **Migrating from fastapi-crudrouter?** The route layout is nearly identical (`GET/POST /resource`, `GET/PUT/DELETE /resource/{id}`), so switching is mostly a drop-in change:

```python
# Before (fastapi-crudrouter)
from fastapi_crudrouter import SQLAlchemyCRUDRouter
router = SQLAlchemyCRUDRouter(schema=PetCreate, create_schema=PetCreate,
                              update_schema=PetUpdate, db_model=Pet,
                              db=get_session)

# After (better-crud)
from better_crud import crud
pet_router = APIRouter()

@crud(pet_router,
      dto={"create": PetCreate, "update": PetUpdate},
      serialize={"base": PetPublic})
class PetController():
    service: PetService = Depends(PetService)
```

Same routes, same REST semantics — but you gain filtering, pagination modes, ACL, soft delete, and a service layer you can override with business logic.


## Default Routes

| Route                | Method     | Description |
| -------------------- | ---------- | ----------- |
| /resource            | **GET**    | Get Many    |
| /resource/{id}       | **GET**    | Get One     |
| /resource            | **POST**   | Create One  |
| /resource/bulk       | **POST**   | Create Many |
| /resource/{id}       | **PUT**    | Update One  |
| /resource/{ids}/bulk | **PUT**    | Update Many |
| /resource/{ids}      | **DELETE** | Delete Many |



## Minimal Example

> Prerequisites: prepare your database. Only asynchronous mode is supported — use aiomysql or aiosqlite.

**db.py**
```python
from sqlalchemy.orm import DeclarativeBase, declared_attr
from typing import AsyncGenerator
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
DATABASE_URL = "sqlite+aiosqlite:///crud.db"

class MappedBase(DeclarativeBase):
    @declared_attr.directive
    def __tablename__(cls) -> str:
        return cls.__name__.lower()


class Base(MappedBase):
    __abstract__ = True


engine = create_async_engine(
    DATABASE_URL,
    echo=False,
    poolclass=NullPool
)

SessionLocal = sessionmaker(
    autocommit=False,
    autoflush=False,
    bind=engine,
    class_=AsyncSession,
    expire_on_commit=False,
)


async def get_session() -> AsyncGenerator[AsyncSession, None]:
    async with SessionLocal() as session:
        yield session


async def init_db():
    async with engine.begin() as conn:
        await conn.run_sync(MappedBase.metadata.create_all)
```

First Define Your Model And Schema

**model.py**
```python
from sqlalchemy import String, Integer, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from .db import Base


class Pet(Base):
    __tablename__ = "pet"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(100))
    description: Mapped[str] = mapped_column(String(100))

```

**schema.py**
```python
from typing import Optional, List
from pydantic import BaseModel


class PetBase(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None


class PetPublic(PetBase):
    id: int


class PetCreate(PetBase):
    pass


class PetUpdate(PetBase):
    pass

```

Next we need to create a service:

**service.py**
```python
from better_crud.service.sqlalchemy import SqlalchemyCrudService
from .model import Pet


class PetService(SqlalchemyCrudService[Pet]):
    def __init__(self):
        super().__init__(Pet)

```

Next we need to define the controller and decorate it with the crud decorator
Sure the controller is just a normal class,The crud decorator gives it super powers
**controller.py**
```python
from fastapi import APIRouter, Depends
from better_crud import crud
from .schema import PetCreate, PetUpdate, PetPublic
from .service import PetService

pet_router = APIRouter()


@crud(
    pet_router,
    dto={
        "create": PetCreate,
        "update": PetUpdate
    },
    serialize={
        "base": PetPublic,
    }
)
class PetController():
    service: PetService = Depends(PetService)

```

Next we can register router to the fastapi routing system

**main.py**
```python
from better_crud import BetterCrudGlobalConfig
from fastapi import FastAPI
from contextlib import asynccontextmanager
from .db import get_session, init_db

BetterCrudGlobalConfig.init(
    backend_config={
        "sqlalchemy": {
            "db_session": get_session
        }
    }
)


@asynccontextmanager
async def lifespan(_: FastAPI):
    await init_db()
    # Shutdown
    yield

app = FastAPI(lifespan=lifespan)


def register_router():
    from app.controller import pet_router
    app.include_router(pet_router, prefix="/pet")


register_router()


```

Congratulations, your first CRUD route has been created! 🎉


![OpenAPI Route Overview](https://raw.githubusercontent.com/bigrivi/better_crud/main/resources/RouteOverview.png)

## Author

👤 **bigrivi**
* GitHub: [bigrivi](https://github.com/bigrivi)

## 🤝 Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".

1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the Branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request

## Credits

This project draws inspiration from the following frameworks:

- [nestjsx-crud](https://github.com/nestjsx/crud)

## UseCases

BetterCRUD is used in production by:

- [black-panther](https://github.com/bigrivi/black-panther) — a real-world FastAPI project

## ⭐️ Support

If BetterCRUD saves you time, please help more developers discover it:

- **Give the repo a star ⭐️** — it directly helps others find the project
- Report bugs or request features via [issues](https://github.com/bigrivi/better_crud/issues)
- Share it in your team or community

## License

[MIT](LICENSE)
