Python & FastAPI Technical Debt Cleanup
Your Python backend is showing its age. Untyped endpoints, sync bottlenecks, and missing docs. We fix all of it.
At Variant Systems, we pair the right technology with the right approach to ship products that work.
Why this combination
- Legacy Flask and Django REST APIs often lack type hints and auto-generated docs
- Mixing sync and async code creates subtle performance bottlenecks
- Python's loose typing lets debt accumulate silently until production breaks
- Dependency management in Python projects drifts without strict pinning and CI checks
Untyped Endpoints, Sync-in-Async Bottlenecks, and Stale Notion Docs
The most common Python API debt: untyped endpoints. Request bodies are parsed from raw dictionaries. Response shapes vary depending on which developer wrote the endpoint. Documentation is a Notion page that was last updated six months ago.
Then there’s the sync-in-async problem. Your FastAPI app is async, but somewhere a library makes a blocking call - a database query through a sync ORM, a file system operation, a requests.get() call. One blocking endpoint can starve the entire async event loop, turning your fast API into a slow one under load.
Pydantic Models on Every Boundary and Event Loop Profiling for Blocking Calls
We start by adding Pydantic models to every endpoint boundary. This is the highest-impact change: immediate request validation, accurate auto-generated docs, and type-safe response serialization. We do this endpoint by endpoint so the API stays stable throughout.
For async issues, we profile the event loop to find blocking calls. Each one gets migrated: sync database calls move to SQLAlchemy 2.0’s async engine, HTTP calls switch to httpx, and file operations use aiofiles. We add asyncio instrumentation so future blocking calls get caught in CI, not production.
Auto-Generated Docs That Stay Accurate and 10x Concurrent Request Capacity
Your API gets interactive documentation that’s always accurate. Frontend developers and API consumers stop asking questions that the docs now answer. Integration bugs drop because Pydantic catches malformed requests before they hit your business logic.
Performance under concurrent load improves dramatically. An API that choked at 100 concurrent requests handles 1,000 because the event loop isn’t being blocked by sync calls. P95 latency drops from seconds to milliseconds for I/O-bound endpoints.
SQLAlchemy 2.0 Async Migration and Alembic-Guarded Schema Evolution
Python API debt rarely stops at the endpoint layer. The database interaction code often carries its own accumulated problems. We see raw SQL strings interpolated with f-strings (a security liability), SQLAlchemy 1.x sessions managed inconsistently across request lifecycles, and ORM models that have drifted from the actual database schema because migrations were applied manually and never committed to version control.
We migrate the data layer to SQLAlchemy 2.0 with its native async support via asyncpg. Models get explicit type annotations that mypy can validate against your Pydantic response schemas, catching mismatches between what the database returns and what the API promises. Alembic manages migrations with autogeneration from model changes, and we add a CI check that verifies the migration chain is complete — no unapplied model changes can merge into the main branch.
Connection pooling is configured properly for your concurrency profile. We set pool sizes based on measured concurrent request volume rather than arbitrary defaults, configure statement-level timeouts to prevent runaway queries from holding connections, and add health checks that recycle stale connections before they cause intermittent failures under load.
Strict mypy, Ruff Enforcement, and Async Lint Rules in CI
We configure mypy in strict mode and integrate it into CI. Untyped functions fail the build. Ruff replaces flake8, black, and isort with a single, fast linter that enforces consistent code style.
Dependency management moves to Poetry or uv with locked dependency files. Renovate or Dependabot creates PRs for updates automatically. We add async linting rules that flag blocking calls in async functions. The result: your Python codebase stays clean because the tooling enforces it, not just willpower.
What you get
Ideal for
- Python APIs with untyped endpoints and outdated documentation
- FastAPI apps with sync-in-async performance issues
- Teams migrating from Flask or Django REST to FastAPI
- Companies scaling Python services that need production hardening