https://lifeofsems.com/posts/feed.xml

The Python stack I use for back-end development

2025-05-02

Introduction

Selecting an effective toolset is crucial for building maintainable and performant back-end systems. After extensive evaluation across production projects, I've standardized on a Python stack that combines modern features with practical reliability.

Core Tools

FastAPI with Uvicorn

FastAPI delivers exceptional performance through Starlette's async foundation while providing:

  • Built-in OpenAPI/Swagger documentation
  • Security utilities (OAuth2, JWT, CORS)
  • Clean dependency injection system
  • Seamless Pydantic integration for request/response validation

The development experience is enhanced by Uvicorn's hot-reloading:

uvicorn main:app --reload

Pydantic

Beyond basic validation, Pydantic offers powerful features:

  • BaseSettings for environment configuration
  • Field aliasing (handles camelCase/snake_case conversion)
  • TypeAdapter for flexible validation of arbitrary data
  • Custom validators and serialization control

Example showing multiple features:

class UserResponse(BaseModel):
    user_id: UUID = Field(alias="userId")
    created_at: datetime
    
    class Config:
        from_attributes = True  # Works with ORM models
        json_encoders = {datetime: lambda v: v.isoformat()}

HTTPX

The async HTTP client provides:

  • Connection pooling
  • Timeout configuration
  • Automatic content decoding
  • Mocking support for testing

SQLAlchemy 2.0 + Alembic

SQLAlchemy's latest version offers:

  • Unified sync/async API
  • Improved type annotations
  • Declarative model configuration
  • Relationship loading strategies

Alembic handles schema migrations with:

  • Autogeneration capabilities
  • Data migration support
  • Branching/merging workflows

Caching with Cashews

The caching solution supports:

  • Time-based expiration
  • Cache invalidation patterns
  • Key templating
  • Rate limiting

Basic usage pattern:

@cache(ttl="1h", key="user:{user_id}")
async def get_user(user_id: int):
    ...

@invalidate("user:{user_id}")
async def update_user(user_id: int):
    ...

orjson

While Pydantic uses Rust-based serialization internally, orjson provides:

  • 2-3x faster JSON serialization
  • Direct bytes output (no string conversion)
  • datetime/UUID/numpy support
  • Used for non-Pydantic JSON operations

Structured Logging with structlog

Key benefits:

  • Consistent log format across services
  • Contextual logging (attach key-value pairs)
  • Processor pipeline for enrichment
  • Output to JSON for log aggregation

Testing

  1. pytest - Async test support, fixtures, parameterization
  2. respx - Mock HTTPX with route matching and assertion
  3. Locust - Define user behavior scenarios
  4. mypy - Gradual typing with strict optional checking

Code Quality

Ruff combines:

  • 10-100x faster than flake8
  • Built-in formatting (replaces Black)
  • Import sorting
  • Configurable rule sets

Minimal configuration needed due to sensible defaults.

Conclusion

This carefully selected stack delivers:

  • Performance: Async-first architecture with optimized serialization
  • Reliability: Strong typing and validation throughout
  • Maintainability: Consistent patterns across layers
  • Observability: Structured logging and monitoring support

Each component has proven its value in production environments at various scales, providing a solid foundation for Python back-end development.