Skip to main content

FastAPI + Dishka with providers being first-class citizens

Project description

๐Ÿš€ fastapi-dishka

PyPI - Version PyPI - Wheel PyPI - Status PyPI - License PyPI - Downloads PyPI - Format codecov Python 3.13+ FastAPI

๐Ÿฝ๏ธ Serve your FastAPI dependencies with style! A delightful integration between FastAPI and Dishka that makes dependency injection feel like a five-star dining experience.

โœจ What is this magic?

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

๐Ÿ› ๏ธ Installation

Get started in seconds:

pip install fastapi-dishka

Or if you're feeling fancy with poetry:

poetry add fastapi-dishka

๐ŸŽฌ Quick Start

Here'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. ๐ŸŽ‰

๐ŸŽญ Features & Examples

๐Ÿ”„ Auto-Router Registration

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)

๐Ÿ›ก๏ธ Middleware with Dependency Injection

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)

๐Ÿ—๏ธ Multiple Providers

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())

๐ŸŒ Server Management

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)

๐Ÿ—๏ธ Architecture

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)

๐ŸŽ›๏ธ Provider Options

fastapi-dishka gives you flexibility in how you define your providers. You have two options:

Option 1: Use fastapi-dishka Provider (Recommended)

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.

Option 2: Use dishka Provider with fastapi-dishka metaclass

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

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)

๐ŸŽฏ Pattern 1: Context Manager (Recommended!)

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!

๐Ÿ”ง Pattern 2: Manual Start/Stop

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)

๐ŸŽญ Which Pattern to Choose?

  • ๐ŸŽฏ 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! ๐ŸŽ‰

๐Ÿค Contributing

We love contributions! Here's how to get started:

๐Ÿš€ Quick Setup

# ๐Ÿ“ฅ 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]"

๐Ÿงช Running Tests

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

๐ŸŽฏ Development Standards

  • โœ… 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

๐Ÿ’ก Ideas for Contributions

  • ๐Ÿ”Œ Additional integrations (SQLAlchemy, Redis, etc.)
  • ๐Ÿ“š More examples and tutorials
  • ๐Ÿ› Bug fixes and performance improvements
  • ๐Ÿ“– Documentation improvements
  • ๐Ÿงช More test coverage (can we hit 99%? ๐Ÿ˜)

๐Ÿ› Issues & Questions

Found a bug? Have a question? Want to suggest a feature?

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ™ Acknowledgments

  • ๐Ÿš€ FastAPI - For making APIs fun again
  • ๐Ÿฝ๏ธ Dishka - For elegant dependency injection
  • โค๏ธ All our contributors and users

โญ Show Your Support

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! ๐Ÿš€

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

fastapi_dishka-0.2.3.tar.gz (23.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_dishka-0.2.3-py3-none-any.whl (13.4 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_dishka-0.2.3.tar.gz.

File metadata

  • Download URL: fastapi_dishka-0.2.3.tar.gz
  • Upload date:
  • Size: 23.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for fastapi_dishka-0.2.3.tar.gz
Algorithm Hash digest
SHA256 0ccb4606eebeb69f496e950a1856d5dbd483fd1b8ba9dfcbf3260c23e6eba2aa
MD5 ea6fe2807893dbd8c52ecdf0a1a3f71e
BLAKE2b-256 22881acd4b5ed8bc5eac9820e0f41681f01742454a6247f2a1e6a887ed1e3958

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_dishka-0.2.3.tar.gz:

Publisher: pypi.yaml on NSXBet/fastapi-dishka

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fastapi_dishka-0.2.3-py3-none-any.whl.

File metadata

  • Download URL: fastapi_dishka-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 13.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for fastapi_dishka-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 8480b08ece9e16ef289336cfe3de5ce71ce5064fa33b17dc6b3f688a0094b243
MD5 95b7ef6d6542e4eddbefce1c49d826eb
BLAKE2b-256 76c07321fa87ff09780e29346a63c714cd792f027f64104f1be018a9f5f83fb8

See more details on using hashes here.

Provenance

The following attestation bundles were made for fastapi_dishka-0.2.3-py3-none-any.whl:

Publisher: pypi.yaml on NSXBet/fastapi-dishka

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page