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) ySQLAlchemyBaseController(SQLA)
- Repositorio:
-
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
Itemcon 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
AsyncSessionen 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. EnBaseServicepuedes leerself.actionpara decidir comportamiento por acción. - Búsqueda: define
self.search_fieldsen 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]yBasePaginationResponse[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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastapi_basekit-0.1.13.tar.gz.
File metadata
- Download URL: fastapi_basekit-0.1.13.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b3bd7085989602612c9e63bce219c09a74946a784ace2c4a4bacc07f45dcbc0
|
|
| MD5 |
a34554c84c014d9317c6f14f9fae581b
|
|
| BLAKE2b-256 |
40cd15e774caee6602d76f5d06799356189ad86f55bfcbfd9bd622f17d563790
|
Provenance
The following attestation bundles were made for fastapi_basekit-0.1.13.tar.gz:
Publisher:
publish.yml on mundobien2025/fastapi-basekit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fastapi_basekit-0.1.13.tar.gz -
Subject digest:
8b3bd7085989602612c9e63bce219c09a74946a784ace2c4a4bacc07f45dcbc0 - Sigstore transparency entry: 557156725
- Sigstore integration time:
-
Permalink:
mundobien2025/fastapi-basekit@8f70f29c0ab1f18e0ed3371a151a74b002a04901 -
Branch / Tag:
refs/tags/v0.1.13 - Owner: https://github.com/mundobien2025
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8f70f29c0ab1f18e0ed3371a151a74b002a04901 -
Trigger Event:
push
-
Statement type:
File details
Details for the file fastapi_basekit-0.1.13-py3-none-any.whl.
File metadata
- Download URL: fastapi_basekit-0.1.13-py3-none-any.whl
- Upload date:
- Size: 21.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/5.1.1 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a173e4ce8584210c56c7bb16527b1d907b4fa65108429170b2f7dfe524c25db
|
|
| MD5 |
04053933c9297c982458172a78b7616b
|
|
| BLAKE2b-256 |
7780e75ea71b6a7bf45bc3a9c94446fffc6435531ae568e3a069a0c59acea8b8
|
Provenance
The following attestation bundles were made for fastapi_basekit-0.1.13-py3-none-any.whl:
Publisher:
publish.yml on mundobien2025/fastapi-basekit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fastapi_basekit-0.1.13-py3-none-any.whl -
Subject digest:
4a173e4ce8584210c56c7bb16527b1d907b4fa65108429170b2f7dfe524c25db - Sigstore transparency entry: 557156748
- Sigstore integration time:
-
Permalink:
mundobien2025/fastapi-basekit@8f70f29c0ab1f18e0ed3371a151a74b002a04901 -
Branch / Tag:
refs/tags/v0.1.13 - Owner: https://github.com/mundobien2025
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8f70f29c0ab1f18e0ed3371a151a74b002a04901 -
Trigger Event:
push
-
Statement type: