A modern Python package for building robust web applications with FastAPI and SQLAlchemy. It focuses on providing the missing glue required to build standard CRUD applications with an opinionated view on best practices.
Project description
Crudites
A modern Python package for building robust web applications with FastAPI and SQLAlchemy. It focuses on providing the missing glue required to build standard CRUD applications. It also offers a more opinionated view on how things should be done.
Features
- FastAPI Integration: Built-in CORS configuration and middleware support
- SQLAlchemy Integration: Database configuration and connection pool management
- Sentry Integration: Error tracking and monitoring support
- Type Safety: Full type annotations and Pydantic models for configuration
- Resource Management: Automatic resource lifecycle management for FastAPI and CLI applications
Installation
pip install crudites
Quick Start
FastAPI Setup
import logging
from collections.abc import AsyncGenerator, Sequence
from contextlib import asynccontextmanager
from typing import Any
from brotli_asgi import BrotliMiddleware
from crudites.globals import AppGlobals
from crudites.integrations.fastapi import CorsConfig
from crudites.integrations.logging import LoggingConfig, init_logging
from crudites.integrations.sentry import SentryConfig, init_sentry
from crudites.integrations.sqlalchemy import DatabaseConfig
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic_settings import BaseSettings, SettingsConfigDict
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
logger = logging.getLogger(__name__)
class Config(BaseSettings):
# Environment variables are prefixed with MYAPP_ and the separator is __
# - MYAPP__LOGGING__FORMAT_AS_JSON=1
# - MYAPP__DATABASE__HOST=localhost
model_config = SettingsConfigDict(
env_nested_delimiter="__",
env_prefix="MYAPP__",
env_file=".env",
env_file_encoding="utf-8",
)
cors: CorsConfig = CorsConfig()
database: DatabaseConfig
logging: LoggingConfig = LoggingConfig()
sentry: SentryConfig = SentryConfig()
class MyAppGlobals(AppGlobals[Config]):
def __init__(self, config: Config) -> None:
super().__init__(config)
self.db_engine: AsyncEngine
@property
def resources(self) -> Sequence[tuple[str, AsyncGenerator[Any, None]]]:
return [("db_engine", self._db_engine_manager)]
async def _db_engine_manager(self) -> AsyncGenerator[AsyncEngine, None]:
logger.info("Setting up database engine...")
try:
engine = create_async_engine(self.config.database.sqlalchemy_url)
yield engine
finally:
logger.info("Cleaning up database engine...")
await engine.dispose()
@asynccontextmanager
async def lifespan(app: FastAPI):
config: Config = app.state.CONFIG
async with MyAppGlobals(config) as app_globals:
app.state.APP_GLOBALS = app_globals
yield
config = Config()
init_logging(config.logging)
init_sentry(config.sentry)
# Create the app
app = FastAPI(
lifespan=lifespan,
title="Hera Registration System API",
version="1.0.0",
description="Hera Registration System API",
)
app.state.CONFIG = config # allow lifespan function to access config
# Add middleware
app.add_middleware(BrotliMiddleware) # compress responses
app.add_middleware(CORSMiddleware, **config.cors.model_dump()) # setup CORS
# Add routes
CLI Setup
import logging
from collections.abc import AsyncGenerator, Sequence
from typing import Any
from crudites.cli import crudites_command
from crudites.globals import AppGlobals
from crudites.integrations.logging import LoggingConfig, init_logging
from crudites.integrations.sentry import SentryConfig, init_sentry
from crudites.integrations.sqlalchemy import DatabaseConfig
from pydantic_settings import BaseSettings, SettingsConfigDict
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
logger = logging.getLogger(__name__)
class Config(BaseSettings):
# Environment variables are prefixed with MYAPP_ and the separator is __
# - MYAPP__LOGGING__FORMAT_AS_JSON=1
# - MYAPP__DATABASE__HOST=localhost
model_config = SettingsConfigDict(
env_nested_delimiter="__",
env_prefix="MYAPP__",
env_file=".env",
env_file_encoding="utf-8",
)
database: DatabaseConfig
logging: LoggingConfig = LoggingConfig()
sentry: SentryConfig = SentryConfig()
class MyAppGlobals(AppGlobals[Config]):
def __init__(self, config: Config) -> None:
super().__init__(config)
self.db_engine: AsyncEngine
@property
def resources(self) -> Sequence[tuple[str, AsyncGenerator[Any, None]]]:
return [("db_engine", self._db_engine_manager)]
async def _db_engine_manager(self) -> AsyncGenerator[AsyncEngine, None]:
logger.info("Setting up database engine...")
try:
engine = create_async_engine(self.config.database.sqlalchemy_url)
yield engine
finally:
logger.info("Cleaning up database engine...")
await engine.dispose()
@crudites_command(MyAppGlobals, Config)
async def cli(app_globals: MyAppGlobals):
config = app_globals.config
init_logging(config.logging)
init_sentry(config.sentry)
# Use the database engine from app_globals
async with app_globals.db_engine.connect() as conn:
# Do something with the connection
pass
Resource Management
Crudites provides a robust resource management system through AppGlobals and CLI command helpers. This system ensures proper initialization and cleanup of application resources like database connections, external services, and other global state.
AppGlobals
AppGlobals is an abstract base class that manages the lifecycle of application resources. It implements the async context manager protocol for proper resource cleanup.
from crudites.globals import AppGlobals
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
class MyAppGlobals(AppGlobals[MyConfig]):
def __init__(self, config: MyConfig) -> None:
super().__init__(config)
self.db_engine: AsyncEngine
@property
def resources(self) -> Sequence[tuple[str, AsyncGenerator[Any, None]]]:
return [("db_engine", self._db_engine_manager)]
async def _db_engine_manager(self) -> AsyncGenerator[AsyncEngine, None]:
logger.info("Setting up database engine...")
try:
engine = create_async_engine(self.config.sqlalchemy_url)
yield engine
finally:
logger.info("Cleaning up database engine...")
await engine.dispose()
FastAPI Integration
Use AppGlobals with FastAPI's lifespan:
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
config: MyConfig = app.state.CONFIG
async with MyAppGlobals(config) as app_globals:
app.state.GLOBALS = app_globals
yield
app = FastAPI(lifespan=lifespan)
CLI Commands
The crudites_command decorator simplifies CLI command creation with proper resource management:
from crudites.cli import crudites_command
@crudites_command(MyAppGlobals, MyConfig)
async def main(app_globals: MyAppGlobals):
# Use app_globals.db_engine here
async with app_globals.db_engine.connect() as conn:
# Do something with the connection
pass
Key features of the resource management system:
- Automatic resource initialization and cleanup
- Type-safe resource access
- Support for both FastAPI and CLI applications
- Async context manager support for clean resource lifecycle
Development
Running all checks and test
poe all
Formatting code (ruff)
poe format
Linting code (mypy, ruff, bandit)
poe lint
To fix any auto-fixable errors
poe fix
Running tests
poe test
To debug / target a single failing test and break in the debugger on exception
poe debug_test tests/test_globals.py::test_app_globals_abstract_method
License
MIT License
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
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
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 crudites-0.0.1.tar.gz.
File metadata
- Download URL: crudites-0.0.1.tar.gz
- Upload date:
- Size: 8.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
122d24b1621eeac9421824d59d20bafc238448d8eda9b95e12a6d2cacfedcea4
|
|
| MD5 |
900c168e300cd0bd66478c60f8d5759b
|
|
| BLAKE2b-256 |
c8e5c65c57fd51ff84cd94a0c946d472725478ad6952be1b26b33c127c70c2a7
|
File details
Details for the file crudites-0.0.1-py3-none-any.whl.
File metadata
- Download URL: crudites-0.0.1-py3-none-any.whl
- Upload date:
- Size: 4.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
566ecdfd51a35632005d1d81fd76baf7db9772068ddd4c863caf01b4d7704879
|
|
| MD5 |
a77c8ff2a4b456069804edc1a832cb2d
|
|
| BLAKE2b-256 |
ca68e17decbcc2c6113e32970f59eee31b748408774a7d6da1311eed8daaaf26
|