π½οΈ Serve your FastAPI dependencies with style! A delightful integration between FastAPI and Dishka that makes dependency injection feel like a five-star dining experience.
fastapi-dishka bridges the gap between FastAPI and Dishka, bringing you:
- π Auto-registration - Routers and middleware register themselves like magic
- π― Provider-first design - Your providers are first-class citizens
- π§© Seamless integration - Works with existing FastAPI and Dishka code
- π Zero boilerplate - Less setup, more building awesome stuff
- π Type-safe - Full type hints and mypy support
- β‘ High performance - Built on FastAPI and Dishka's solid foundations
Get started in seconds:
pip install fastapi-dishkaOr if you're feeling fancy with poetry:
poetry add fastapi-dishkaHere's how easy it is to get rolling:
from dishka import Scope, provide, FromDishka from fastapi_dishka import App, APIRouter, provide_router, Provider # π¦ Create your service class GreetingService: def greet(self, name: str) -> str: return f"Hello, {name}! π" # π£οΈ Create your router router = APIRouter(prefix="/api") @router.get("/greet/{name}") async def greet_endpoint(name: str, service: FromDishka[GreetingService]) -> dict: return {"message": service.greet(name)} # π Create your provider class AppProvider(Provider): scope = Scope.APP # π― Auto-register the router greeting_router = provide_router(router) # π Provide your services greeting_service = provide(GreetingService, scope=Scope.APP) # π Launch your app app = App("My Awesome API", "1.0.0", AppProvider()) if __name__ == "__main__": app.start_sync() # π₯ Your API is now running!That's it! Your API is running with auto-registered routes and dependency injection. π
Say goodbye to manually registering every router:
from fastapi_dishka import provide_router, Provider class MyProvider(Provider): # β¨ These routers register themselves automatically users_router = provide_router(users_router) posts_router = provide_router(posts_router) comments_router = provide_router(comments_router)Create powerful middleware that can inject dependencies:
from fastapi_dishka import Middleware, provide_middleware, Provider class AuthMiddleware(Middleware): async def dispatch(self, request, call_next): # π Inject services right into your middleware! auth_service = await self.get_dependency(request, AuthService) if not auth_service.is_authenticated(request): return JSONResponse({"error": "Unauthorized"}, status_code=401) return await call_next(request) class SecurityProvider(Provider): scope = Scope.APP auth_service = provide(AuthService, scope=Scope.APP) # π‘οΈ Auto-register middleware with DI support auth_middleware = provide_middleware(AuthMiddleware)Organize your code with multiple providers:
from fastapi_dishka import Provider # π€ User-related stuff class UserProvider(Provider): scope = Scope.APP user_router = provide_router(user_router) user_service = provide(UserService, scope=Scope.APP) # π Post-related stuff class PostProvider(Provider): scope = Scope.APP post_router = provide_router(post_router) post_service = provide(PostService, scope=Scope.APP) # π Combine them all app = App("Blog API", "2.0.0", UserProvider(), PostProvider())Full control over your server lifecycle:
# π₯ Blocking mode (great for production) app.start_sync(host="0.0.0.0", port=8080) # π§΅ Non-blocking mode (perfect for testing) app.start_sync(blocking=False, port=8081) # ... do other stuff ... app.stop() # π Graceful shutdown # β‘ Async mode await app.start(host="127.0.0.1", port=8082)fastapi-dishka follows a provider-first design:
π¦ Your App βββ π Providers (define what you have) β βββ π£οΈ Router providers (auto-register routes) β βββ π‘οΈ Middleware providers (auto-register middleware) β βββ π Service providers (your business logic) βββ π Auto-registration (happens magically) βββ π FastAPI App (ready to serve) fastapi-dishka gives you flexibility in how you define your providers. You have two options:
from fastapi_dishka import Provider class MyProvider(Provider): scope = Scope.APP # Your provider methods here...This is the recommended approach as it's specifically designed for fastapi-dishka integration.
from dishka import Provider from fastapi_dishka import FastAPIDishkaProviderMeta class MyProvider(Provider, metaclass=FastAPIDishkaProviderMeta): scope = Scope.APP # Your provider methods here...This approach allows you to use dishka's Provider directly while still getting fastapi-dishka's auto-registration features through the metaclass.
Both approaches provide the same functionality - choose the one that fits your project's needs! π―
Testing is a breeze with multiple patterns and full async support! Let's start with the classic hello world test:
import pytest from fastapi.testclient import TestClient from dishka import Scope, provide, FromDishka from fastapi_dishka import App, APIRouter, provide_router, start_test, stop_test, test, Provider class GreetingService: def greet(self, name: str) -> str: return f"Hello, {name}! π" hello_router = APIRouter() @hello_router.get("/hello/{name}") async def hello_endpoint(name: str, service: FromDishka[GreetingService]) -> dict: return {"message": service.greet(name)} class HelloProvider(Provider): scope = Scope.APP greeting_router = provide_router(hello_router) greeting_service = provide(GreetingService, scope=Scope.APP)The cleanest and most convenient way to test:
@pytest.mark.asyncio async def test_hello_world_with_context_manager(): """The cleanest way to test - using the context manager! π―""" app = App("Hello World API", "1.0.0", HelloProvider()) # π― Ultra-clean testing with context manager async with test(app) as test_app: client = TestClient(test_app.app) response = client.get("/hello/World") assert response.status_code == 200 data = response.json() assert data["message"] == "Hello, World! π" # π§Ή Cleanup happens automatically!For more control over the server lifecycle:
@pytest.mark.asyncio async def test_hello_world(): """Manual server management with start_test/stop_test.""" app = App("Hello World API", "1.0.0", HelloProvider()) try: # π Use start_test() for clean async server startup await start_test(app, port=9999) client = TestClient(app.app) response = client.get("/hello/World") assert response.status_code == 200 data = response.json() assert data["message"] == "Hello, World! π" finally: # π§Ή Use stop_test() for clean async server shutdown await stop_test(app)- π― Context Manager: Perfect for most tests, cleanest syntax, automatic cleanup
- π§ Start/Stop: Use when you need custom server lifecycle management or multiple test phases
Both patterns handle provider reuse correctly, so you can use the same providers across multiple tests! π
We love contributions! Here's how to get started:
# π₯ Clone the repo git clone https://github.com/NSXBet/fastapi-dishka.git cd fastapi-dishka # π Create virtual environment python -m venv .venv source .venv/bin/activate # On Windows: .venv\Scripts\activate # π¦ Install dependencies pip install -e ".[dev]"We maintain 90%+ test coverage (we're a bit obsessed π ):
# πββοΈ Run all tests make test # π Check coverage make coverage # π Lint your code make lint # β¨ Format your code make format- β Type Safety: We love type hints and use mypy
- π§ͺ Test Coverage: Keep it above 90%
- π Documentation: Update docs for new features
- π¨ Code Style: We use ruff and flake8
- π Provider-First: Make providers first-class citizens
- π Additional integrations (SQLAlchemy, Redis, etc.)
- π More examples and tutorials
- π Bug fixes and performance improvements
- π Documentation improvements
- π§ͺ More test coverage (can we hit 99%? π)
Found a bug? Have a question? Want to suggest a feature?
- π Report bugs
- π‘ Request features
- β Ask questions
This project is licensed under the MIT License - see the LICENSE file for details.
- π FastAPI - For making APIs fun again
- π½οΈ Dishka - For elegant dependency injection
- β€οΈ All our contributors and users
If you like this project, please consider giving it a star! It helps others discover fastapi-dishka and motivates us to keep improving it.
Made with β€οΈ and lots of β
Happy coding! π