Skip to main content

Utilities and base classes for FastAPI async projects (Beanie or SQLAlchemy)

Project description

FastAPI BaseKit

Utilidades asíncronas y clases base para construir APIs con FastAPI usando Beanie (MongoDB) o SQLAlchemy (AsyncSession). Provee una arquitectura consistente de repositorios, servicios y controladores, con tipado estricto vía Pydantic.

Características

  • Clases genéricas listas para heredar:

    • Repositorio: BaseRepository
    • Servicio: BaseService
    • Controlador: BaseController (Beanie) y SQLAlchemyBaseController (SQLA)
  • Esquemas de respuesta unificados: BaseResponse, BasePaginationResponse.

  • Manejo de errores con excepciones y handlers listos.

  • Servicios comunes (ej. JWT).

  • Totalmente asíncrono, facilita escalado horizontal.

  • Soporta Beanie y/o SQLAlchemy mediante extras opcionales.

  • Consultas pluggables por acción con get_kwargs_query() en servicios.

Instalación

El núcleo es liviano; elige tu stack:

# Base (sin ORM)
pip install fastapi-basekit

# Beanie
pip install "fastapi-basekit[beanie]"

# SQLAlchemy (async)
pip install "fastapi-basekit[sqlalchemy]"

# Todo
pip install "fastapi-basekit[all]"

Ejemplo 1: Beanie (MongoDB) — CRUD mínimo en 4 pasos

Crea un CRUD funcional sobre un Item con paginación y búsqueda.

# app.py
from contextlib import asynccontextmanager
from typing import Annotated,Optional

from beanie import Document, init_beanie
from motor.motor_asyncio import AsyncIOMotorClient
from fastapi import Depends, FastAPI, Query, APIRouter
from fastapi_restful.cbv import cbv

from fastapi_basekit.aio.beanie.repository.base import BaseRepository
from fastapi_basekit.aio.beanie.service.base import BaseService
from fastapi_basekit.aio.controller.base import BaseController
from fastapi_basekit.schema.base import BaseResponse, BasePaginationResponse
from fastapi_basekit.exceptions.handler import (
    api_exception_handler,
    validation_exception_handler,
)

# 1) Modelo Beanie
class Item(Document):
    name: str
    description: str | None = None

# 2) Repositorio
class ItemRepository(BaseRepository):
    model = Item

# 3) Servicio (reglas de negocio)
class ItemService(BaseService):
    repository: ItemRepository

    def __init__(self, repository: ItemRepository | None = None):
        super().__init__(repository or ItemRepository())
        self.search_fields = ["name", "description"]
        self.duplicate_check_fields = ["name"]  # opcional

    def get_kwargs_query(self) -> dict:
        # Personaliza por acción: list / retrieve / create / update / delete
        if self.action == "retrieve":
            return {"fetch_links": False}
        return super().get_kwargs_query()


# 3) Schemas

class ItemResponseSchema(BaseSchema):
    name: str
    description: Optional[str] = None

class ItemPResponseSchema(BasePaginationResponse[ItemResponseSchema]):
    pass    

# 4) Controlador (entra/sale HTTP)

item_router = APIRouter(tags=["core"])

@cbv(item_router)
class ItemController(BaseController):
    service: ItemService = Depends(ItemService)
    schema_class = Item  # para serializar/validar IO

    @item_router.get("/", response_model=ItemPResponseSchema)
    async def list(  # override para añadir paginación/búsqueda
        self,
        page: int = Query(1, ge=1, description="Número de página"),
        count: int = Query(10, ge=1, description="Cantidad de elementos"),
        search: str | None = Query(None, description="Término de búsqueda"),
    ) :
        return await super().list()

# FastAPI + Lifespan
@asynccontextmanager
async def lifespan(app: FastAPI):
    client = AsyncIOMotorClient("mongodb://localhost:27017")
    await init_beanie(database=client.basekit_demo, document_models=[Item])
    yield

app = FastAPI(title="BaseKit Beanie Demo", lifespan=lifespan)

# Handlers de error recomendados
app.add_exception_handler(Exception, api_exception_handler)
app.add_exception_handler(ValueError, validation_exception_handler)

# Rutas
app.include_router(item_router, prefix="/api/v1")


**Qué te da este ejemplo**

* CRUD completo con **paginación** (`page`, `limit`) y **búsqueda** (`search`) sin reescribir lógica.
* Posibilidad de **personalizar las consultas** por acción desde el servicio (`get_kwargs_query`).
* Respuestas uniformes con `BaseResponse`/`BasePaginationResponse`.

---

## Ejemplo 2: SQLAlchemy (Async) — CRUD mínimo con sesión por request

> Patrón recomendado: middleware que inyecta `AsyncSession` en la request.

```python
# app_sqlalchemy.py
from contextlib import asynccontextmanager
from typing import Annotated, AsyncGenerator

from fastapi import APIRouter, Depends, FastAPI, Query, Request
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase
from starlette.middleware.base import BaseHTTPMiddleware

from fastapi_basekit.aio.sqlalchemy.repository.base import BaseRepository
from fastapi_basekit.aio.sqlalchemy.service.base import BaseService
from fastapi_basekit.aio.sqlalchemy.controller.base import SQLAlchemyBaseController
from fastapi_basekit.schema.base import BaseResponse, BasePaginationResponse
from fastapi_basekit.exceptions.handler import global_exception_handler, validation_exception_handler

# 0) Base declarativa y modelo
class Base(DeclarativeBase): pass

class ItemORM(Base):
    __tablename__ = "items"
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    name: Mapped[str]
    description: Mapped[str | None]

# 1) Motor y sessionmaker
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost:5432/basekit"
engine = create_async_engine(DATABASE_URL, pool_pre_ping=True)
SessionLocal = async_sessionmaker(bind=engine, expire_on_commit=False)

# 2) Middleware de sesión por request
class DatabaseSessionMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        async with SessionLocal() as session:
            request.state.db_session = session
            try:
                response = await call_next(request)
                await session.commit()
                return response
            except Exception:
                await session.rollback()
                raise

def get_db_session(request: Request) -> AsyncSession:
    if not hasattr(request.state, "db_session"):
        raise RuntimeError("DatabaseSessionMiddleware no está configurado")
    return request.state.db_session

# 3) Repositorio
class ItemSQLARepository(BaseRepository):
    model = ItemORM

# 4) Servicio
class ItemSQLAService(BaseService):
    repository: ItemSQLARepository

    def __init__(self, repository: ItemSQLARepository, request: Request):
        super().__init__(repository=repository, request=request)
        self.search_fields = ["name", "description"]

    def get_kwargs_query(self) -> dict:
        # Por ejemplo, joins sólo en retrieve
        if self.action == "retrieve":
            return {"joins": []}
        return super().get_kwargs_query()

def get_item_service(request: Request) -> ItemSQLAService:
    db = get_db_session(request)
    repo = ItemSQLARepository(db)
    return ItemSQLAService(repository=repo, request=request)

# 5) Controlador + rutas
router = APIRouter()

class ItemsController(SQLAlchemyBaseController):
    service: Annotated[ItemSQLAService, Depends(get_item_service)]
    schema_class = ItemORM  # para tipado de respuesta

    @router.get("/items", response_model=BasePaginationResponse[ItemORM])
    async def list_items(
        self,
        page: Annotated[int, Query(1, ge=1)],
        limit: Annotated[int, Query(10, ge=1, le=100)],
        search: Annotated[str | None, Query(None)] = None,
    ):
        return await self.list(page=page, count=limit, search=search)

    @router.get("/items/{id}", response_model=BaseResponse[ItemORM])
    async def retrieve_item(self, id: int):
        return await self.retrieve(id)

    @router.post("/items", response_model=BaseResponse[ItemORM])
    async def create_item(self, payload: dict):
        return await self.create(payload)

    @router.patch("/items/{id}", response_model=BaseResponse[ItemORM])
    async def update_item(self, id: int, payload: dict):
        return await self.update(id, payload)

    @router.delete("/items/{id}")
    async def delete_item(self, id: int):
        return await self.delete(id)

app = FastAPI(title="BaseKit SQLAlchemy Demo")
app.add_middleware(DatabaseSessionMiddleware)
app.add_exception_handler(Exception, global_exception_handler)
app.add_exception_handler(ValueError, validation_exception_handler)
app.include_router(router, prefix="")

Notas rápidas

  • El repositorio recibe la sesión AsyncSession en el constructor.
  • El servicio centraliza reglas (ej. campos de búsqueda, duplicados, get_kwargs_query).
  • El controlador sólo adapta IO HTTP y reusa los métodos base.

Convenciones clave

  • Acciones: list, retrieve, create, update, delete. En BaseService puedes leer self.action para decidir comportamiento por acción.
  • Búsqueda: define self.search_fields en el servicio.
  • Chequeo de duplicados: usa self.duplicate_check_fields.
  • Consultas por acción: sobreescribe get_kwargs_query() en el servicio.
  • Respuestas: usa BaseResponse[T] y BasePaginationResponse[T] para uniformidad.

Errores y handlers

Incluye los handlers recomendados para respuestas limpias:

from fastapi_basekit.exceptions.handler import (
    api_exception_handler,
    duplicate_key_exception_handler,  # útil en Beanie/Mongo
    global_exception_handler,
    validation_exception_handler,
    value_exception_handler,
)

Servicios adicionales

  • JWTService: creación/validación/refresh de JWT (importa desde fastapi_basekit.servicios).

Tests

Instala dependencias y ejecuta:

pytest

Licencia

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

fastapi_basekit-0.1.12.tar.gz (20.6 kB view details)

Uploaded Source

Built Distribution

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

fastapi_basekit-0.1.12-py3-none-any.whl (21.9 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_basekit-0.1.12.tar.gz.

File metadata

  • Download URL: fastapi_basekit-0.1.12.tar.gz
  • Upload date:
  • Size: 20.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.7

File hashes

Hashes for fastapi_basekit-0.1.12.tar.gz
Algorithm Hash digest
SHA256 d15d4ae59912efe131f3d372614403bc59d270a909116a5875b6e40b634eaab6
MD5 e0d977202eac26735a9911e7cc2e88d8
BLAKE2b-256 61961726f0bb7c349c26e3b0cb5c47e41c4a70614ce3ff482cad57c630d62cd6

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_basekit-0.1.12.tar.gz:

Publisher: publish.yml on mundobien2025/fastapi-basekit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastapi_basekit-0.1.12-py3-none-any.whl.

File metadata

File hashes

Hashes for fastapi_basekit-0.1.12-py3-none-any.whl
Algorithm Hash digest
SHA256 7b411056344d6e9fb783e53c4984473b96aab60dd4dcb76247866000c23ab3a1
MD5 2749eb57919c428d3d6b197bb6d495fd
BLAKE2b-256 f2458b7e33d0c8bed90c2d621c1e8ba137ad18db94dc25db16813192419583ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_basekit-0.1.12-py3-none-any.whl:

Publisher: publish.yml on mundobien2025/fastapi-basekit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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