Skip to content

Key Concepts

Dishka is a modern, performance-oriented Dependency Injection (DI) framework for Python emphasizing lifecycle management through scopes, modularity via components, and clean integration with popular frameworks.

Providers: The Source of Truth

  • Configuration: Dependencies are configured within Provider classes.
  • Factories: Factories are declared using the @provide decorator on methods or by applying provide() to a class which inspects its __init__.
  • Finalization: Resource cleanup (e.g., closing database connections) is handled elegantly by defining a provider as a generator. The code after the yield statement is executed upon scope exit.
  • Async Support: Asynchronous factories (async def) are fully supported via make_async_container and await container.get().

Scopes: Managing Lifecycles

  • Hierarchical Lifespans: Dishka’s core feature is its strict, hierarchical scope system that defines the lifespan of dependencies. The default flow is APPREQUEST.
  • Caching: Dependencies are lazy-loaded and cached within their scope. Requesting the same dependency multiple times in the same scope instance returns the same object.
  • Scope Dependency Rules: A dependency can only rely on objects from its own scope or a parent scope. For instance, a REQUEST-scoped object can depend on an APP-scoped one, but the reverse is not allowed.
  • Entering Scopes: You enter a new, nested scope by using the container as a context manager: with app_container() as request_container:.

Container: The Access Point

  • Creation: The Container is the object used to retrieve dependencies and is created by make_container or make_async_container, into which you pass your providers.
  • Retrieval: The primary method for accessing a dependency is container.get(MyType).
  • Concurrency: The top-level (APP) container is thread/task-safe by default to prevent race conditions during the creation of singletons.

Components: True Isolation for Modularity

  • Preventing Collisions: To avoid type collisions in large or modular applications, providers can be grouped into named Components.
  • Isolation: By default, providers within one component are isolated and cannot see dependencies from another.
  • Inter-Component Communication: To explicitly request a dependency from another component, use the Annotated type hint with FromComponent: my_param: Annotated[int, FromComponent("X")]. This enables the creation of well-defined boundaries between different parts of an application.

Context Data & Framework Integration

  • Runtime Data Injection: Data that is only available at runtime (like an incoming HTTP request object) can be injected into the dependency graph. This is achieved by marking a dependency with from_context in a provider and then passing the actual value when entering a scope, for example with container(context={Request: request_obj}):.
  • Seamless Integration: Dishka provides integrations for popular frameworks like FastAPI, aiohttp, and others, which automate scope management and context injection. The typical pattern involves:
    • Calling setup_dishka(container, app) during application startup.
    • Using an @inject decorator on handlers or views.
    • Type-hinting dependencies in handlers with FromDishka[MyService], which is a convenient alias for requesting a dependency from the default component.

A Practical Guide

This guide provides the core syntax and patterns you’ll use to integrate Dishka into a project.

1. The Core Workflow: A Complete Example

This is the fundamental pattern for using Dishka in a standalone script.

import os
from collections.abc import Iterable
from dishka import Provider, Scope, make_container, provide

# 1. Your application classes (no changes needed for Dishka)
class ApiClient:
    def __init__(self, api_key: str):
        print(f"ApiClient created with key: ...{api_key[-4:]}")
        self.api_key = api_key
    def fetch(self): return "data"

class Database:
    def __init__(self): print("Database connection opened.")
    def query(self): return "db_result"
    def close(self): print("Database connection closed.")

class Service:
    def __init__(self, db: Database, client: ApiClient):
        self._db = db
        self._client = client
    def process(self):
        return f"{self._db.query()} and {self._client.fetch()}"

# 2. Create a Provider to declare how objects are created
class MyProvider(Provider):
    # APP-scoped: created once and shared across the application
    scope = Scope.APP

    # provide a simple value by returning it
    @provide
    def get_api_key(self) -> str:
        return os.getenv("API_KEY", "fake_key_1234")

    # provide a class by instantiating it with its dependencies
    # Dishka automatically injects `api_key` from the provider above
    api_client = provide(ApiClient)

    # REQUEST-scoped: Use a generator for dependencies that need cleanup
    @provide(scope=Scope.REQUEST)
    def get_db(self) -> Iterable[Database]:
        db = Database()  # Setup code
        yield db         # The object is used within the scope
        db.close()       # Teardown code runs after scope exit

    # Dishka will automatically resolve Database and ApiClient dependencies
    service = provide(Service, scope=Scope.REQUEST)

# 3. Create and manage the container
# The app_container is for APP-scoped objects
app_container = make_container(MyProvider())

print("--- First Request ---")
# Enter a new scope (REQUEST) using a context manager
with app_container() as request_container:
    service_instance = request_container.get(Service)
    print(service_instance.process())
    # request_container.get(Service) again here would return the SAME instance

# The `with` block has exited, so the Database connection is closed.

print("\n--- Second Request ---")
with app_container() as request_container2:
    service_instance_2 = request_container2.get(Service) # A NEW instance
    print(service_instance_2.process())

# 4. Close the main container on application shutdown
app_container.close()

2. Key Provider Syntax

Your Provider is where you define all dependency creation logic.

  • Simple Instantiation: For classes with dependencies that Dishka already knows how to create, just pass the class to provide.
    python # Automatically resolves ApiClient's `settings` dependency api_client = provide(ApiClient, scope=Scope.APP)

  • Interface Binding: To provide a concrete implementation for a protocol or abstract class, use the provides argument.
    ```python
    from typing import Protocol

    class UserRepo(Protocol): …
    class UserRepoImpl(UserRepo): …

    When UserRepo is requested, an instance of UserRepoImpl will be provided

    user_repo = provide(UserRepoImpl, provides=UserRepo, scope=Scope.REQUEST)
    ```

  • Asynchronous Factories: For async applications, use async def and make_async_container.
    ```python
    from dishka import make_async_container

    class MyAsyncProvider(Provider):
    @provide(scope=Scope.APP)
    async def get_async_db(self) -> AsyncDatabase:
    return await connect_async(…)

    Use the async container

    container = make_async_container(MyAsyncProvider())
    db = await container.get(AsyncDatabase)
    await container.close()
    ```

3. Framework Integration (FastAPI Example)

Dishka automates scope and context management within frameworks.

  1. Add the Framework Provider: Include the specific provider for your framework to get access to framework objects like Request.
  2. Setup Dishka: Call setup_dishka to add the middleware to your app.
  3. Inject into Handlers: Use the @inject decorator and the FromDishka type hint.
from fastapi import FastAPI
from dishka import make_async_container
from dishka.integrations.fastapi import (
    setup_dishka,
    inject,
    FromDishka,
    FastapiProvider, # 1. Import framework provider
)

# Assume MyProvider and Service classes from the first example

# Create an async container and include the FastapiProvider
container = make_async_container(MyProvider(), FastapiProvider())

app = FastAPI()

@app.get("/")
@inject # 3. Use the inject decorator
async def read_root(
    # Type hint your dependency with FromDishka
    service: FromDishka[Service],
) -> dict:
    result = service.process()
    return {"result": result}

# 2. Setup dishka on the app instance
setup_dishka(container, app)

# FastAPI's lifespan can be used to close the container
@app.on_event("shutdown")
async def shutdown():
    await container.close()

When a request hits /, Dishka’s middleware automatically:
1. Enters the REQUEST scope.
2. Creates a new Service and Database instance.
3. Provides the Service instance to the read_root handler.
4. Exits the REQUEST scope after the response is sent, closing the Database.

4. Components for Isolation

When multiple parts of your app provide the same type (e.g., two different Database connections), use components to avoid conflicts.

  • Assigning a Component: Add a component attribute to a provider.

    ```python
    class UsersProvider(Provider):
    component = “users” # Assign this provider to the “users” component
    scope = Scope.APP
    db = provide(UsersDatabase, provides=Database)

    class OrdersProvider(Provider):
    component = “orders” # Assign this provider to the “orders” component
    scope = Scope.APP
    db = provide(OrdersDatabase, provides=Database)
    `` * **Requesting from a Component**: When injecting, useAnnotatedwithFromComponent` to specify the source.

    ```python
    from typing import Annotated
    from dishka import FromComponent

    @inject
    async def get_users(
    # Request the Database provided by the “users” component
    db: Annotated[Database, FromComponent(“users”)],
    ):

    `` TheFromDishka[T]hint is a shortcut forAnnotated[T, FromComponent()]`, which targets the default (unnamed) component.

Page last modified: 2025-10-04 11:51:12