dishka, a dependency injection container where scopes are the main idea
Cute dependency injection (DI) framework for Python with agreeable API and everything you need
At a glance
- What is it?
- dishka is a Python IoC container that focuses on dependency injection and nothing else, and its distinguishing feature is that scopes are yours to define rather than fixed at one or two levels. Any object can have a lifespan for the whole app, for a single request, or something more fractional, and providers can be split into small reusable pieces.
- Who is it for?
- Adopt dishka if your Python application has objects with genuinely different lifetimes, especially resources that must be released rather than merely created, and skip it if your project is small enough that passing objects directly is still readable, because a container is infrastructure you have to learn and maintain.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A container that only does dependency injection
The positioning is unusually clear about what the library does not do. It focuses only on dependency injection without trying to solve unrelated tasks, and it keeps DI in place without cluttering your code with global variables and scattered specifiers. The problem it names is the one every Python developer recognises: if you are exhausted from endlessly passing objects to create other objects, only to have those objects create even more, this is an answer. The project is not shy about the fact that not every project requires an IoC container, and it points at a detailed comparison page to show how it stands out among other DI tools. The name is a joke with a translation, dishka stands for cute DI in Russian, and the tone of the documentation matches, which is a small signal about who the library is for.
pip install dishkaScopes, and the reason the library exists
Scopes are the first feature listed and the reason the library is worth switching to. Any object can have a lifespan for the entire app, a single request, or even more fractionally, and the documentation notes that many frameworks either lack scopes completely or offer only two. With dishka you can define as many scopes as needed. That is the architectural claim, and it is easy to underestimate. A container with a single lifetime cannot express a request-scoped database session next to an application-scoped connection pool, and a container with exactly two fixed levels forces everything else into one of them. The second feature listed is finalization, which is described as missing from many frameworks: some dependencies, like database connections, need not only to be created but also to be carefully released. The third is modular providers, which let you split factories into smaller classes for easier reuse instead of one large class or many loose functions.
Type hints do the work, with no markers to add
The dependency declarations are ordinary type hints, and the library reads them. The quickstart defines a Service class taking an APIClient and a UserDAO, where UserDAO is a Protocol and SQLiteUserDAO is the concrete implementation taking a sqlite3 Connection. Nothing here is annotated for the container's benefit. The clean dependencies feature says it plainly: you do not need to add custom markers to dependency code to make it visible for the library. That is a real difference from container designs where a class has to be declared as a provider or decorated to be injectable, and it means the same class can be constructed by hand in a test without the container's cooperation. The project also claims a simple API where only a few objects are needed to start, and a typing story, with the package metadata carrying a Typing :: Typed classifier and a strict version range.
class Service:
def __init__(self, client: APIClient, user_dao: UserDAO):
...Providers, and the three ways to register a dependency
Providers set up the factories for your objects, and the quickstart shows three registration styles rather than one. You can provide a class directly, which is what happens for Service and APIClient. You can provide a specific interface implementation, which is what happens for UserDAO, where SQLiteUserDAO is registered as providing the UserDAO protocol. Or you can write a custom factory, which is what happens for the Connection, because the project wants it to be releasable. The scope is set per dependency as well as per provider, so a provider created with Scope.REQUEST can have one of its dependencies overridden to Scope.APP, and the quickstart does exactly that with the APIClient. The declaration reads as a small amount of configuration that states the lifetime of each thing you build, and that configuration is the main artefact you will maintain.
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 scopeFinalization, expressed as a generator
The Connection provider is the piece of the example that shows how finalization works, and it uses a generator rather than a context manager decorator. The factory is annotated with the provide decorator and its scope, it returns an Iterable of Connection, and the body opens an in-memory SQLite connection, yields it, and closes it after the yield. The container then knows to close that connection when the scope ends, because the factory is a generator whose teardown half runs at scope exit. This is a design decision with a real consequence: the object you receive is a normal Connection with no wrapper, no proxy and no context manager held open, and the release is driven by the container rather than by the caller. For a database session or a connection pool, that is the difference between a leak and not, and it is why finalization is listed as a headline feature rather than an afterthought.
class ConnectionProvider(Provider):
@provide(scope=Scope.REQUEST)
def new_connection(self) -> Iterable[Connection]:
connection = sqlite3.connect(":memory:")
yield connection
connection.close()The container, and the two rules that keep it honest
The container holds a cache of dependencies and is used to retrieve them, and you build one by passing providers to make_container, combining as many as you need. Access is through a get method, and for APP-scoped dependencies it is safe to request the same dependency multiple times, because the container returns the cached instance rather than building a second one. The scope discipline matters here, since a REQUEST-scoped dependency requested twice inside one request should also be the same object, and a dependency requested outside its scope should be an error rather than a silent new instance. The project positions the container as genuinely useful rather than a formal exercise, and it makes the usual argument that a container keeps DI in place without global variables and scattered specifiers, which is a fair description of what a well-used container does and a criticism of the alternative of wiring things by hand at every layer.
from dishka import make_container
container = make_container(service_provider, ConnectionProvider())Integrations, speed, and where the limits are
Framework integrations are supported out of the box, and the documentation says you can simply extend them for your needs, so the expectation is that a supported framework gets you most of the way and the extension point handles the rest. On performance the claim is modest and specific: the library is fast enough that performance is not a concern, and in fact it outperforms many alternatives, with the technical requirements page holding the detail. The honest limits are the usual ones for this kind of library. A container earns its keep when object graphs get deep and lifecycles get varied, and it costs you an indirection, a learning curve and a configuration file that has to be kept in step with the code. dishka is also young: it is at version 1.10.1, released 2026-04-25, with 1.10.0 from 2026-04-18 and 1.9.1 from 2026-03-08, and the default branch is develop rather than main, so the released line and the head of the branch can differ.
The repository, and the tooling around it
The project ships the things you would expect from a library that takes its own typing seriously. The package metadata names a single runtime dependency, exceptiongroup for Python versions below 3.11, which is a signal that the library itself is a very small piece of code. The development tooling is pinned hard, with ruff, mypy, nox and uv in the CI group, and separate groups for typos, zizmor and ast-grep, which is an unusual and slightly paranoid selection. The repository has a .mypy.ini, a .ruff.toml, a .typos.toml, a noxfile, a .coveragerc and a linter-rules/ directory, and a sgconfig.yml for ast-grep, which is the tool used to enforce project-specific lint rules beyond what ruff can express. The examples directory is organized by lesson, with sync_simple and async_simple at the top level and integrations/ and real_world/ subdirectories, and each example carries its own ruff.toml, so the examples are held to a stricter standard than the library.
src/
tests/
examples/
docs/
requirements/Editorial conclusion
Adopt dishka if your Python application has objects with genuinely different lifetimes, especially resources that must be released rather than merely created, and skip it if your project is small enough that passing objects directly is still readable, because a container is infrastructure you have to learn and maintain. Verify three things before you start: that your Python is 3.10 or newer, which the package metadata requires; which scope each of your dependencies actually needs, since the whole design turns on that and a wrong scope choice is the usual source of bugs in DI code; and that the framework integration you use is one of the supported ones, or that you are willing to write the small adapter the library expects.
Frequently asked questions
What is dishka and what does it focus on?
It is an IoC container for Python that focuses only on dependency injection, without trying to solve unrelated tasks, and it keeps DI in place without cluttering your code with global variables and scattered specifiers. Only a few objects are needed to start using it.
How are scopes handled in dishka?
Any object can have a lifespan for the entire app, a single request, or something more fractional, and you can define as many scopes as you need, where many frameworks offer only two. Scope is set per provider and can be overridden per dependency.
How does dishka release resources like database connections?
Through finalization. A provider can define a custom factory that returns an Iterable, so it is written as a generator: the value is yielded and the teardown after the yield runs when the scope ends. For a Connection, the example opens it, yields it, and closes it after the yield.
Do I need to mark my classes for dishka to use them?
No. The clean dependencies feature means you do not need to add custom markers to dependency code to make it visible for the library. Dependencies are declared with ordinary type hints, and a class can be constructed by hand in a test without the container's help.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/reagento-dishka)Was this useful?