Dishka
Cute dependency injection (DI) framework for Python with agreeable API and everything you need
Install / Use
/learn @reagento/DishkaREADME
dishka (stands for "cute DI" in Russian)
Cute DI framework with scopes and an agreeable API.
Purpose
This library provides an IoC container that's genuinely useful.
If you're exhausted from endlessly passing objects to create other objects, only to have those objects create even
more — you're not alone, and dishka is a solution.
Not every project requires an IoC container, but take a look at what dishka offers.
Unlike other tools, dishka focuses only
on dependency injection (DI) without trying to solve unrelated
tasks.
It keeps DI in place without cluttering your code with global variables and scattered specifiers.
To see how dishka stands out among other DI tools, check out
the detailed comparison.
Key features:
- Scopes. Any object can have a lifespan for the entire app, a single request, or even more fractionally. Many
frameworks either lack scopes completely or offer only two. With
dishka, you can define as many scopes as needed. - Finalization. Some dependencies, like database connections, need not only to be created but also carefully released. Many frameworks lack this essential feature.
- Modular providers. Instead of creating many separate functions or one large class, you can split factories into smaller classes for easier reuse.
- Clean dependencies. You don't need to add custom markers to dependency code to make it visible for the library.
- Simple API. Only a few objects are needed to start using the library.
- Framework integrations. Popular frameworks are supported out of the box. You can simply extend it for your needs.
- Speed. The library is fast enough that performance is not a concern. In fact, it outperforms many alternatives.
See more in technical requirements.
Quickstart
- Install dishka.
pip install dishka
- Define classes with type hints. Let's have the
Serviceclass (business logic) that has two infrastructure dependencies:APIClientandUserDAO.UserDAOis implemented inSQLiteUserDAOthat has its own dependency -sqlite3.Connection.
We want to create an APIClient instance once during the application's lifetime
and create UserDAO implementation instances on every request (event) our application handles.
from sqlite3 import Connection
from typing import Protocol
class APIClient:
...
class UserDAO(Protocol):
...
class SQLiteUserDAO(UserDAO):
def __init__(self, connection: Connection):
...
class Service:
def __init__(self, client: APIClient, user_dao: UserDAO):
...
- Create providers and specify how to provide dependencies.
Providers are used to set up factories for your objects. To learn more about providers, see Provider.
Use Scope.APP for dependencies that should be created once for the entire application lifetime,
and Scope.REQUEST for those that should be created for each request, event, etc.
To learn more about scopes, see Scope management.
There are multiple options for registering dependencies. We will use:
- class (for
ServiceandAPIClient) - specific interface implementation (for
UserDAO) - custom factory with finalization (for
Connection, as we want to make it releasable)
import sqlite3
from collections.abc import Iterable
from sqlite3 import Connection
from dishka import Provider, Scope, provide
service_provider = Provider(scope=Scope.REQUEST)
service_provider.provide(Service)
service_provider.provide(SQLiteUserDAO, provides=UserDAO)
service_provider.provide(APIClient, scope=Scope.APP) # override provider's scope
class ConnectionProvider(Provider):
@provide(scope=Scope.REQUEST)
def new_connection(self) -> Iterable[Connection]:
connection = sqlite3.connect(":memory:")
yield connection
connection.close()
- Create a container, passing providers. You can combine as many providers as needed.
Containers hold a cache of dependencies and are used to retrieve them. To learn more about containers, see Container.
from dishka import make_container
container = make_container(service_provider, ConnectionProvider())
- Access dependencies using the container.
Use the .get() method to access APP-scoped dependencies.
It is safe to request the same dependency multiple times.
# APIClient is bound to Scope.APP, so it can be accessed here
# or from any scope inside including Scope.REQUEST
client = container.get(APIClient)
client = container.get(APIClient) # the same APIClient instance as above
To access the REQUEST scope (sub-container) and its dependencies, use a context manager.
Higher level scoped dependencies are also available from sub-containers, e.g. APIClient.
# A sub-container to access shorter-living objects
with container() as request_container:
# Service, UserDAO implementation, and Connection are bound to Scope.REQUEST,
# so they are accessible here. APIClient can also be accessed here
service = request_container.get(Service)
service = request_container.get(Service) # the same Service instance as above
# Since we exited the context manager, the sqlite3 connection is now closed
# A new sub-container has a new lifespan for request processing
with container() as request_container:
service = request_container.get(Service) # a new Service instance
- Close the container when done.
container.close()
<details>
<summary>Full example:</summary>
import sqlite3
from collections.abc import Iterable
from sqlite3 import Connection
from typing import Protocol
from dishka import Provider, Scope, make_container, provide
class APIClient:
...
class UserDAO(Protocol):
...
class SQLiteUserDAO(UserDAO):
def __init__(self, connection: Connection):
...
class Service:
def __init__(self, client: APIClient, user_dao: UserDAO):
...
service_provider = Provider(scope=Scope.REQUEST)
service_provider.provide(Service)
service_provider.provide(SQLiteUserDAO, provides=UserDAO)
service_provider.provide(APIClient, scope=Scope.APP) # override provider's scope
class ConnectionProvider(Provider):
@provide(scope=Scope.REQUEST)
def new_connection(self) -> Iterable[Connection]:
connection = sqlite3.connect(":memory:")
yield connection
connection.close()
container = make_container(service_provider, ConnectionProvider())
# APIClient is bound to Scope.APP, so it can be accessed here
# or from any scope inside including Scope.REQUEST
client = container.get(APIClient)
client = container.get(APIClient) # the same APIClient instance as above
# A sub-container to access shorter-living objects
with container() as request_container:
# Service, UserDAO implementation, and Connection are bound to Scope.REQUEST,
# so they are accessible here. APIClient can also be accessed here
service = request_container.get(Service)
service = request_container.get(Service) # the same Service instance as above
# Since we exited the context manager, the sqlite3 connection is now closed
# A new sub-container has a new lifespan for request processing
with container() as request_container:
service = request_container.get(Service) # a new Service instance
container.close()
</details>
- (optional) Integrate with your framework. If you are using a supported framework, add decorators and middleware for it. For more details, see Using with frameworks.
from fastapi import APIRouter, FastAPI
from dishka import make_async_container
from dishka.integrations.fastapi import (
FastapiProvider,
FromDishka,
inject,
setup_dishka,
)
app = FastAPI()
router = APIRouter()
app.include_router(router)
container = make_async_container(
service_provider,
ConnectionProvider(),
FastapiProvider(),
)
setup_dishka(container, app)
@router.get("/")
@inject
async def index(service: FromDishka[Service]) -> str:
...
Concepts
Dependency is what you need for some parts of your code to work. Dependencies are simply objects you don't create directly in place and might want to replace someday, at least for testing purposes. Some of them live for the entire application lifetime, while others are created and destroyed with each request. Dependencies can also rely on other objects, which then become their dependencies.
Scope is the lifespan of a dependency. Standard scopes are (with some skipped):
APP -> REQUEST -> ACTION -> STEP.
You decide when to enter and exit each scope, but this is done one by one. You set a s
Related Skills
node-connect
334.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
claude-opus-4-5-migration
82.2kMigrate prompts and code from Claude Sonnet 4.0, Sonnet 4.5, or Opus 4.1 to Opus 4.5
frontend-design
82.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
model-usage
334.5kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
