A Python architecture sample that demonstrates Uncle Bob's Clean Architecture through a Pokémon and Trainer API built with FastAPI. The focus is on showing how layers are separated, how dependencies flow inward, and how infrastructure can be swapped at the structural level.
This is a learning-oriented project, not a production template. For background and common questions, see the FAQ.
This project applies Clean Architecture with the following practices:
- Framework Independence: FastAPI is confined to the controllers layer — use cases and models have no framework dependency.
- Testability: Business rules in use cases can be tested without HTTP or database.
- UI Independence: REST and GraphQL are interchangeable outer-layer interfaces.
- Database Independence: Multiple databases implement the same repository contracts.
- External Independence: Domain logic does not depend on specific APIs, ORMs, or drivers.
*source: yoan-thirion.gitbook.io
./
├── ...
├── src/
│ ├── di/ - Dependency injection configurations for managing dependencies.
│ │ ├── dependency_injection.py
│ │ └── unit_of_work.py
│ │
│ ├── controllers/ - External interfaces like REST & GraphQL endpoints.
│ │ ├── graphql/ - GraphQL components for a flexible API.
│ │ └── rest/ - RESTful API routes and controllers.
│ │ ('Frameworks and Drivers' and part of 'Interface Adapters' in Clean Architecture)
│ │
│ ├── usecases/ - Contains application-specific business rules and implementations.
│ │ ('Use Cases' in Clean Architecture)
│ │
│ ├── repositories/ - Data access abstraction layer, responsible for persistence and retrieval of domain data.
│ │ ├── relational_db/ - Operations for relational databases (e.g., SQLite, MySQL, PostgreSQL).
│ │ ├── document_db/ - Operations for document-oriented databases (e.g., MongoDB, CouchDB).
│ │ └── key_value_db/ - Operations for key-value databases (e.g., Redis, Memcached).
│ │ ('Interface Adapters' in Clean Architecture)
│ │
│ ├── models/ - Domain entities representing the business data.
│ │ ('Entities' in Clean Architecture)
│ │
│ ├── common/ - Shared code and utilities.
│ ├── settings/
│ │ └── db/ - Database configurations.
│ │ ('Frameworks and Drivers' in Clean Architecture)
│ │
│ └── main.py - Main file to launch the application.
│
└── tests/
├── api_db_test.bats - Runs the full pytest suite against each supported database.
├── functional/ - Functional tests for testing the overall functionality and behavior of the application.
├── integration/ - Integration tests for testing module interactions.
└── unit/ - Unit tests for testing individual components in isolation.REST and GraphQL share the same use cases layer — the API protocol is an outer-layer detail. 5 databases (SQLite, MySQL, PostgreSQL, MongoDB, Redis) sit behind the same repository contracts — storage is also an outer-layer detail.
Two entities illustrate different complexity levels:
- Pokemon — basic CRUD
- Trainer — business rules like team size limits, ownership checks, and atomic trades
Enabled by several key design decisions:
- Repository Pattern 1 — decouples the model layer from data storage
- Unit of Work Pattern 2 — ensures transactional consistency
- Dependency Injection 3 — reduces coupling between modules
- Asynchronous SQLAlchemy 2.0 4 — async database operations
The flow diagrams visualize the layers and how they interact:
For a detailed explanation of the ASCII flow, refer to ascii-flow.md.
*source: yoan-thirion.gitbook.io
*source: https://stackoverflow.com/a/73788685
Start the application with a single command — no local Python or dependency installation required:
$ docker compose up appUses in-memory SQLite by default. Access the API documentation at http://localhost:8000/docs and try the interactive endpoints.
Set DATABASE_URI to switch databases:
| Database | URI format |
|---|---|
| SQLite (file) | sqlite+aiosqlite:///<dbname>.db |
| SQLite (memory) | sqlite+aiosqlite:///:memory: (default) |
| MySQL | mysql+asyncmy://<user>:<pass>@<host>:<port>/<dbname> |
| PostgreSQL | postgresql+asyncpg://<user>:<pass>@<host>:<port>/<dbname> |
| MongoDB | mongodb://<user>:<pass>@<host>:<port>/<dbname> |
| Redis | redis://<user>:<pass>@<host>:<port>/<dbname> |
📌 Note: If you encounter database initialization issues, append
reinitialize=trueto theDATABASE_URI, e.g.,sqlite+aiosqlite:///sqlite.db?reinitialize=true.
$ docker compose down --remove-orphans -v
$ docker compose up dockerizeFor local development with hot-reload:
-
Install prerequisites: Python 3.11+ and uv
-
Install dependencies: 5
$ uv sync
-
Launch the application:
# With default SQLite database $ make up # With specific database $ DATABASE_URI=<database-uri> make up
-
Access the application: http://localhost:8000
# Test against a specific database
$ DATABASE_URI=<database-uri> uv run pytest
# Test against all databases (requires bats)
$ make testFor supported database URIs, see Database Options
📌 Note: Use a different
dbnamewith "_test" suffix for testing (e.g., "mydatabase_test") to avoid interfering with your main application data.
See the full version history in CHANGELOG.md.
If this project helped you, a ⭐ would be greatly appreciated!
Have questions or ideas? Feel free to open an issue or submit a PR.
Footnotes
-
https://www.cosmicpython.com/book/chapter_02_repository.html ↩
-
https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html ↩
-
The
uv synccommand installs all required packages for running and developing the application. However, it does not includecspell. If you needcspellfor spell checking, please refer to the official installation guide at cspell installation guide ↩