Repository: reagento/dishka
Pull request #310 (2024-11-15 21:34:00)

v1.4.1


Patch:
diff --git a/README.md b/README.md
index 6354936..89bba0c 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-## Dishka (from russian "cute DI")
+## Dishka (stands for "cute DI" in Russian)
 
 [![PyPI version](https://badge.fury.io/py/dishka.svg)](https://pypi.python.org/pypi/dishka)
 [![Supported versions](https://img.shields.io/pypi/pyversions/dishka.svg)](https://pypi.python.org/pypi/dishka)
-[![downloads](https://img.shields.io/pypi/dm/dishka.svg)](https://pypistats.org/packages/dishka)
-[![license](https://img.shields.io/github/license/reagento/dishka)](https://github.com/reagento/dishka/blob/master/LICENSE)
+[![Downloads](https://img.shields.io/pypi/dm/dishka.svg)](https://pypistats.org/packages/dishka)
+[![License](https://img.shields.io/github/license/reagento/dishka)](https://github.com/reagento/dishka/blob/master/LICENSE)
 [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/reagento/dishka/setup.yml)](https://github.com/reagento/dishka/actions)
 [![Doc](https://readthedocs.org/projects/dishka/badge/?version=latest&style=flat)](https://dishka.readthedocs.io)
 [![Telegram](https://img.shields.io/badge/💬-Telegram-blue)](https://t.me/reagento_ru)
@@ -14,164 +14,197 @@ Cute DI framework with scopes and agreeable API.
 
 ### Purpose
 
-This library is targeting to provide only an IoC-container but tries to make it really useful. 
-If you are tired of passing objects manually to create other objects which are only used to create more objects - we have a solution.
-Not all projects require an IoC-container, but check what we have.
+This library provides **IoC container** that's genuinely useful.
+If you're exhausted from endlessly passing objects just to create other objects, only to have those objects create even
+more — you're not alone, and we have a solution.
+Not every project requires IoC container, but take a look at what we offer.
 
-Unlike other instruments we are not trying to solve tasks not related to [dependency injection](https://dishka.readthedocs.io/en/latest/di_intro.html).
-Instead, we want to keep DI in place, not soiling your code with global variables and additional specifiers scattered everywhere.
+Unlike other tools, Dishka focuses **only**
+on [dependency injection](https://dishka.readthedocs.io/en/latest/di_intro.html) without trying to solve unrelated
+tasks.
+It keeps DI in place without cluttering your code with global variables and scattered specifiers.
 
-Main ideas:
-* **Scopes**. Any object can have lifespan of the whole app, single request or even more fractionally. Many frameworks do not have scopes or have only 2 of them. Here you can have as many scopes as you need.
-* **Finalization**. Some dependencies like database connections must not only be created, but carefully released. Many framework lack this essential feature
-* **Modular providers**. Instead of creating lots of separate functions or contrariwise a big single class, you can split your factories into several classes, which makes them simpler to reuse.
-* **Clean dependencies**. You do not need to add custom markers to the code of dependencies just to allow library to see them. All customization is done within providers code and only the borders of scopes have to deal with library API.
-* **Simple API**. You need minimum of objects to start using library. You can easily integrate it with your task framework, examples provided.
-* **Speed**. It is fast enough so you do not have to worry about it. It is even faster than many of the analogs.
+To see how Dishka **stands out** among other dependency injection tools, check out
+the [detailed comparison](https://dishka.readthedocs.io/en/latest/alternatives.html).
 
-See more in [technical requirements](https://dishka.readthedocs.io/en/latest/requirements/technical.html)
+#### 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. Here, 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 just to make it visible to the
+  library. Customization is managed by library providers, so only scope boundaries interact with the library API.
+* **Simple API**. Only a few objects are needed to start using the library. Integration with your framework is
+  straightforward, with examples provided.
+* **Speed**. The library is fast enough that performance is not a concern. In fact, it outperforms many
+  alternatives.
+
+See more in [technical requirements.](https://dishka.readthedocs.io/en/latest/requirements/technical.html)
 
 ### Quickstart
 
-1. Install dishka
+1. **Install Dishka.**
 
 ```shell
 pip install dishka
 ```
 
-2. Write your classes, fill type hints. Imagine, you have two classes: Service (kind of business logic) and DAO (kind of data access) and some external api client:
+2. **Define your classes with type hints.** Imagine you have two classes: `Service` (business logic) and
+   `DAO` (data access), along with an external API client:
 
 ```python
 class DAO(Protocol):
     ...
 
+
 class Service:
     def __init__(self, dao: DAO):
         ...
 
+
 class DAOImpl(DAO):
     def __init__(self, connection: Connection):
         ...
 
+
 class SomeClient:
     ...
 ```
 
-4. Create Provider instance. It is only used to setup all factories providing your objects.
-
-```python
-from dishka import Provider
-
-provider = Provider()
-```
+3. **Create `Provider`** instance and specify how to provide dependencies.
 
+Providers are used only to set up factories providing your objects.
 
-5. Setup how to provide dependencies.
-
-We use `scope=Scope.APP` for dependencies which are created only once in application lifetime,
-and `scope=Scope.REQUEST` for those which should be recreated for each processing request/event/etc.
-To read more about scopes, refer [documentation](https://dishka.readthedocs.io/en/latest/advanced/scopes.html)
+Use `scope=Scope.APP` for dependencies created once for the entire application lifetime,
+and `scope=Scope.REQUEST` for those that need to be recreated for each request, event, etc.
+To learn more about scopes, see [documentation.](https://dishka.readthedocs.io/en/latest/advanced/scopes.html)
 
 ```python
 from dishka import Provider, Scope
 
+
 service_provider = Provider(scope=Scope.REQUEST)
 service_provider.provide(Service)
 service_provider.provide(DAOImpl, provides=DAO)
 service_provider.provide(SomeClient, scope=Scope.APP)  # override provider scope
 ```
 
-To provide connection we might need to write some custom code:
+To provide a connection, you might need some custom code:
 
 ```python
 from dishka import Provider, provide, Scope
 
+
 class ConnectionProvider(Provider):
-    @provide(Scope=Scope.REQUEST)
-    def new_connection(self) -> Connection:
-        conn = sqlite3.connect()
+    @provide(scope=Scope.REQUEST)
+    def new_connection(self) -> Iterable[Connection]:
+        conn = sqlite3.connect(":memory:")
         yield conn
         conn.close()
 ```
 
-6. Create main `Container` instance passing providers, and step into `APP` scope.
+4. **Create main `Container`** instance, passing providers, and enter `APP` scope.
 
 ```python
 from dishka import make_container
 
+
 container = make_container(service_provider, ConnectionProvider())
 ```
 
-7. Container holds dependencies cache and is used to retrieve them. Here, you can use `.get` method to access APP-scoped dependencies:
+5. **Access dependencies using container.** Container holds a cache of dependencies and is used to retrieve them.
+   You can use `.get` method to access `APP`-scoped dependencies:
 
 ```python
 client = container.get(SomeClient)  # `SomeClient` has Scope.APP, so it is accessible here
 client = container.get(SomeClient)  # same instance of `SomeClient`
 ```
 
-
-8. You can enter and exit `REQUEST` scope multiple times after that using context manager:
+6. **Enter and exit `REQUEST` scope repeatedly using a context manager**:
 
 ```python
-# subcontainer to access more short-living objects
+# subcontainer to access shorter-living objects
 with container() as request_container:
     service = request_container.get(Service)
     service = request_container.get(Service)  # same service instance
-# at this point connection will be closed as we exited context manager
+# since we exited the context manager, the connection is now closed
 
 # new subcontainer to have a new lifespan for request processing
 with container() as request_container:
     service = request_container.get(Service)  # new service instance
 ```
 
-
-9. Close container in the end:
+7. **Close container** when done:
 
 ```python
 container.close()
 ```
 
-10. If you are using supported framework add decorators and middleware for it.
-For more details see [integrations doc](https://dishka.readthedocs.io/en/latest/integrations/index.html)
+8. **Integrate with your framework.** If you are using a supported framework, add decorators and middleware for it.
+   For more details, see [integrations doc.](https://dishka.readthedocs.io/en/latest/integrations/index.html)
 
 ```python
 from dishka.integrations.fastapi import (
     FromDishka, inject, setup_dishka,
 )
 
+
 @router.get("/")
 @inject
 async def index(service: FromDishka[Service]) -> str:
     ...
 
+
 ...
 setup_dishka(container, app)
 ```
 
 ### Concepts
 
-**Dependency** is what you need for some part of your code to work. They are just object which you do not create in place and probably want to replace some day. At least for tests.
-Some of them can live while you application is running, others are destroyed and created on each request. Dependencies can depend on other objects, which are their dependencies.
+**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 a lifespan of a dependency. Standard scopes are (without skipped ones):
+**Scope** is the lifespan of a dependency. Standard scopes are (with some skipped):
 
-  `APP` -> `REQUEST` -> `ACTION` -> `STEP`.
+`APP` -> `REQUEST` -> `ACTION` -> `STEP`.
 
-You decide when to enter and exit them, but it is done one by one. You set a scope for your dependency when you configure how to create it. If the same dependency is requested multiple time within one scope without leaving it, then by default the same instance is returned.
+You decide when to enter and exit each scope, but this is done one by one.
+You set a scope for each dependency when you configure how it is created.
+If the same dependency is requested multiple times within a single scope without leaving it, then by default the same
+instance is returned.
 
-If you are developing web application, you would enter `APP` scope on startup, and you would `REQUEST` scope in each HTTP-request.
+For a web application, enter `APP` scope on startup and `REQUEST` scope for each HTTP request.
 
-You can provide your own Scopes class if you are not satisfied with standard flow.
+You can create a custom scope by defining your own `Scope` class if the standard scope flow doesn't fit your needs.
 
-**Container** is what you use to get your dependency. You just call `.get(SomeType)` and it finds a way to get you an instance of that type. It does not create things itself, but manages their lifecycle and caches. It delegates objects creation to providers which are passed during creation.
+**Container** is what you use to get your dependencies.
+You simply call `.get(SomeType)` and it finds a way to provide you with an instance of that type.
+Container itself doesn't create objects but manages their lifecycle and caches.
+It delegates object creation to providers that are passed during creation.
 
-**Provider** is a collection of functions which really provide some objects. 
-Provider itself is a class with some attributes and methods. Each of them is either result of `provide`, `alias` or `decorate`. They can be used as provider methods, functions to assign attributes or method decorators.
+**Provider** is a collection of functions that provide concrete objects.
+`Provider` is a class with attributes and methods, each being the result of `provide`, `alias`, `from_context`, or
+`decorate`.
+They can be used as provider methods, functions to assign attributes, or method decorators.
 
-`@provide` can be used as a decorator for some method. This method will be called when corresponding dependency has to be created. Name of the method is not important: just check that it is different form other `Provider` attributes. Type hints do matter: they show what this method creates and what does it require. All method parameters are treated as other dependencies and created using container.
+`@provide` can be used as a decorator for a method.
+This method will be called when the corresponding dependency has to be created.
+Name doesn't matter: just make sure it's different from other `Provider` attributes.
+Type hints do matter: they indicate what this method creates and what it requires.
+All method parameters are treated as dependencies and are created using the container.
 
-If `provide` is used with some class then that class itself is treated as a factory (`__init__` is analyzed for parameters). But do not forget to assign that call to some attribute otherwise it will be ignored.
+If `provide` is applied to a class, that class itself is treated as a factory (its `__init__` parameters are analyzed).
+Remember to assign this call to an attribute; otherwise, it will be ignored.
 
-**Component** - is an isolated group of providers within the same container identified by a string. When dependency is requested it is searched only within the same component as its dependant, unless it is declared explicitly.
+**Component** is an isolated group of providers within the same container, identified by a unique string.
+When a dependency is requested, it is only searched within the same component as its direct dependant, unless explicitly
+specified otherwise.
 
-This allows you to have multiple parts of application build separately without need to think if the use same types.
+This structure allows you to build different parts of the application separately without worrying about using the same
+types.
diff --git a/docs/advanced/context.rst b/docs/advanced/context.rst
index c754e91..b5e8000 100644
--- a/docs/advanced/context.rst
+++ b/docs/advanced/context.rst
@@ -40,4 +40,4 @@ Working with context data consists of three parts:
 
 .. note::
 
-    If your are using *multiple components*, you need to specify ``from_context`` in them separately though the context is shared. Context data is always stored in default component, so, other components may not use it and have factories instead.
+    If you are using *multiple components*, you need to specify ``from_context`` in them separately though the context is shared. Context data is always stored in default component, so, other components may not use it and have factories instead.
diff --git a/docs/concepts.rst b/docs/concepts.rst
index 6592c56..729a1ab 100644
--- a/docs/concepts.rst
+++ b/docs/concepts.rst
@@ -9,8 +9,10 @@ Let's start with some terms.
 Dependency
 ==================
 
-**Dependency** is what you need for some part of your code to work.
-If you *database gateway* needs *database connection* to execute SQL queries, then the connection is a dependency for a gateway. If your business logic class requires *database gateway* or some *api client* then *api client* and *database gateway* are dependencies for a business logic. Here the ``Client`` is a dependency, while ``Service`` is a dependant.
+**Dependency** is what you need for some parts of your code to work.
+If your *database gateway* needs *database connection* to execute SQL queries, then the connection is a dependency for a gateway.
+If your business logic class requires *database gateway* or some *api client* then *api client* and *database gateway* are dependencies for a business logic.
+Here, the ``Client`` is a dependency, while ``Service`` is a dependant.
 
 .. code-block:: python
 
@@ -18,46 +20,60 @@ If you *database gateway* needs *database connection* to execute SQL queries, th
         def __init__(self, client: Client):
             self.client = client
 
-So, dependency is just object required for some other object. Dependencies can depend on other objects, which are their dependencies.
+So, dependency is just an object required by another object. Dependencies can depend on other objects, which are their dependencies.
 
-To follow dependency injection rule, dependent object should receive their dependencies and not request themselves. The same classes can be instantiated with different values of their dependencies. At least in tests.
+To follow dependency injection rule, dependent objects should receive their dependencies and not request them themselves.
+The same classes can be instantiated with different values of their dependencies.
+At least in tests.
 
 
 Scope
 ===========
 
 **Scope** is a lifespan of a dependency.
-Some dependencies can live while you application is running, others are created and destroyed on each request. In more rare cases you need more short-lived objects. You set a scope for your dependency when you configure how to create it.
+Some dependencies live for the entire application lifetime, while others are created and destroyed with each request.
+In more rare cases, you need more short-lived objects.
+You set a scope for each dependency when you configure how it is created.
 
-Standard scopes are (excluding skipped):
+Standard scopes are (with some skipped):
 
     ``APP`` |rarr| ``REQUEST`` |rarr| ``ACTION`` |rarr| ``STEP``
 
-You decide when to enter and exit them, but it is done one by one. If you entered ``APP`` scope then the next step deeper will enter ``REQUEST`` scope.
+You decide when to enter and exit each scope, but this is done one by one.
+If you enter the ``APP`` scope, then the next step deeper is to enter the ``REQUEST`` scope.
 
 .. note::
-    ``APP`` scope can be used for lazy initialization of singletons, while ``REQUEST`` scope is good for processing events like HTTP-requests or messenger updates. It is unlikely that you will need other scopes
+    ``APP`` scope can be used for lazy initialization of singletons, while ``REQUEST`` scope is good for processing events like HTTP requests or messenger updates. It is unlikely that you will need other scopes
 
 
-In dishka dependencies are lazy - they are created when you first request them. If the same dependency is requested multiple times within one scope then the same instance is returned (you can disable it for each dependency separately). A created dependency is kept until you exit the scope. And in that moment it is not just dropped away, but corresponding finalization steps are done. You can enter same scope multiple times concurrently so to have multiple instances of objects you can work simultaneously.
+In Dishka dependencies are lazy — they are created when you first request them.
+If the same dependency is requested multiple times within a single scope, then the same instance is returned (you can disable it for each dependency separately).
+A created dependency is kept until you exit the scope.
+And at that moment, it is not just dropped away, but the corresponding finalization steps are done.
+You can enter same scope multiple times concurrently so that to have multiple instances of objects you can work with simultaneously.
 
-Each object can depend on other objects from the same or previous scopes. So, if you have ``Config`` with scope of *APP* and ``Connection`` with scope of *REQUEST* you cannot have an *APP*-scoped object which requires a connection, but you can have *REQUEST*-scoped object which requires a ``Connection`` or a ``Config`` (or even both).
+Each object can depend on other objects from the same or previous scopes.
+So, if you have ``Config`` with scope of *APP* and ``Connection`` with scope of *REQUEST*,
+you cannot have *APP*-scoped object which requires a connection,
+but you can have *REQUEST*-scoped object which requires a ``Connection`` or a ``Config`` (or even both).
 
-If you are developing web application, you would enter ``APP`` scope on startup, and you would ``REQUEST`` scope in each HTTP-request.
+For a web application, enter ``APP`` scope on startup and ``REQUEST`` scope for each HTTP request.
 
-You can provide your own Scopes class if you are not satisfied with standard flow.
+You can create a custom scope by defining your own ``Scope`` class if the standard scope flow doesn't fit your needs.
 
-:ref:`Read more about custom and hidden scopes<scopes>`
+:ref:`Read more about custom and skipped scopes<scopes>`
 
 Container
 ==================
 
-**Container** is an object you use to get your dependency.
+**Container** is an object you use to get your dependencies.
 
-You just call ``container.get(SomeType)`` and it finds a way to get you an instance of that type. It does not create things itself, but manages their lifecycle and caches. It delegates objects creation to providers which are passed during creation.
+You simply call ``.get(SomeType)`` and it finds a way to provide you with an instance of that type.
+Container itself doesn't create objects but manages their lifecycle and caches.
+It delegates object creation to providers that are passed during creation.
 
-Each container is assigned to a certain scope. To enter the nested scope you call it and use as a context manager.
-According to scopes order container can be used to get dependencies from its and previous scopes.
+Each container is assigned to a certain scope. To enter a nested scope, you call it and use it as a context manager.
+According to the scope order, container can be used to get dependencies from its own and previous scopes.
 
 .. code-block:: python
 
@@ -69,9 +85,10 @@ According to scopes order container can be used to get dependencies from its and
         connection = request_container.get(Connection)  # REQUEST-scoped object
         config = request_container.get(Config)  # APP-scoped object
 
-Async container is working in the same manner, but you should use async context manager and await the result of get
+Async container works in the same manner, but you should use async context manager and await the result of ``.get``.
 
-Some containers are concurrently safe, others are not: it is configured when you call a context manager. For web applications it is good to have APP-scoped container thread/task-safe, but REQUEST-scoped containers do not it, and it is default behavior.
+Some containers are concurrently safe, others are not: this is configured when you call a context manager.
+For web applications, it is good to have APP-scoped container thread/task-safe, but REQUEST-scoped containers do not require it, and this is the default behavior.
 
 
 :ref:`Read more about container API<container>`
@@ -79,7 +96,9 @@ Some containers are concurrently safe, others are not: it is configured when you
 Provider
 ===============
 
-**Provider** is an object which members are used to construct dependencies. It is a normal object, but some attributes must be marked with special decorators so they will be used by a container. To create your own provider you inherit from ``Provider`` class and instantiate it when creating a container:
+**Provider** is an object which members are used to construct dependencies.
+It is a normal object, but some attributes must be marked with special decorators so they will be used by a container.
+To create your own provider, you inherit from ``Provider`` class and instantiate it when creating a container:
 
 .. code-block:: python
 
@@ -89,18 +108,21 @@ Provider
     container = make_container(MyProvider())
 
 
-There are 3 special functions:
+There are 4 special functions:
 
 * ``@provide`` is used to declare a factory providing a dependency. It can be used with some class or as a method decorator. :ref:`Read more<provide>`
-* ``alias`` is used to allow retrieving of the same object by different type hints. :ref:`Read more<alias>`
+* ``alias`` is used to allow retrieving the same object by different type hints. :ref:`Read more<alias>`
 * ``from_context`` is used to mark a dependency as context data, which will be set manually when entering a scope. :ref:`Read more<from-context>`
 * ``@decorate`` is used to modify or wrap an object which is already configured in another ``Provider``. :ref:`Read more<decorate>`
 
 Component
 ====================
-**Component** - is an isolated group of providers within the same container identified by a string. When dependency is requested it is searched only within the same component as its dependant, unless it is declared explicitly.
+**Component** is an isolated group of providers within the same container, identified by a unique string.
+When a dependency is requested, it is only searched within the same component as its direct dependant, unless explicitly
+specified otherwise.
 
-This allows you to have multiple parts of application build separately without need to think if they use same types.
+This structure allows you to build different parts of the application separately without worrying about using the same
+types.
 
 .. code-block:: python
 
@@ -122,6 +144,7 @@ This allows you to have multiple parts of application build separately without n
         def foo(self) -> int:
             return 1
 
+
     container = make_container(MainProvider(), AdditionalProvider())
     container.get(float)  # returns 0.1
     container.get(complex)  # raises NoFactoryError
diff --git a/docs/container/index.rst b/docs/container/index.rst
index b192635..94bd501 100644
--- a/docs/container/index.rst
+++ b/docs/container/index.rst
@@ -46,7 +46,7 @@ or if you created *async* container:
         pass
 
 Container is needed for retrieving objects. To do it you need to call ``get(DependencyType)`` (and ``await`` it for async container).
-All retrieved dependencies are stored inside container of corresponding scope until you exit that scope. So, you if you call ``get`` multiple times you will receive the same instance. The rule is followed for indirect dependencies as well. Multiple dependencies of the same scope have their own cache.
+All retrieved dependencies are stored inside container of corresponding scope until you exit that scope. So, if you call ``get`` multiple times you will receive the same instance. The rule is followed for indirect dependencies as well. Multiple dependencies of the same scope have their own cache.
 
 .. code-block:: python
 
diff --git a/docs/di_intro.rst b/docs/di_intro.rst
index 3357817..2b350c0 100644
--- a/docs/di_intro.rst
+++ b/docs/di_intro.rst
@@ -18,7 +18,7 @@ Imagine you have a business logic which uses some remote API client
     service = Service()
     service.action()
 
-Here, the ``client`` is a **dependency**. Imagine that you have many methods working with same client and each method knows how to create the client. Than think about these questions?
+Here, the ``client`` is a **dependency**. Imagine that you have many methods working with same client and each method knows how to create the client. Then: think about these questions?
 
 * How do they get the ``token``? Should the every method read it on its own?
 * What if the ``Client`` constructor will require more than one token? Should we copy-paste new parameters to each method?
diff --git a/docs/index.rst b/docs/index.rst
index e56f530..619b7c2 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -3,20 +3,29 @@ dishka
 
 Cute DI framework with scopes and agreeable API.
 
-This library is targeting to provide only an IoC-container but make it really useful.
-If you are tired manually passing objects to create others objects which are only used to create more objects - we have a solution.
+This library provides **IoC container** that's genuinely useful.
+If you're exhausted from endlessly passing objects just to create other objects, only to have those objects create even
+more — you're not alone, and we have a solution.
+Not every project requires IoC container, but take a look at what we offer.
 
-Unlike other instruments we are not trying to solve tasks not related to dependency injection. We want to keep DI in place, not soiling you code with global variables and additional specifiers in all places.
+Unlike other tools, Dishka focuses **only**
+on dependency injection without trying to solve unrelated tasks.
+It keeps DI in place without cluttering your code with global variables and scattered specifiers.
 
-Main ideas:
-
-* **Scopes**. Any object can have lifespan of the whole app, single request or even more fractionally. Many frameworks do not have scopes or have only 2 of them. Here you can have as many scopes as you need.
-* **Finalization**. Some dependencies like database connections must be not only created, but carefully released. Many framework lack this essential feature
-* **Modular providers**. Instead of creating lots of separate functions or contrariwise a big single class, you can split your factories into several classes, which makes them simpler reusable.
-* **Clean dependencies**. You do not need to add custom markers to the code of dependencies so to allow library to see them. All customization is done within providers code and only borders of scopes have to deal with library API.
-* **Simple API**. You need minimum of objects to start using library. You can easily integrate it with your task framework, examples provided.
-* **Speed**. It is fast enough so you not to worry about. It is even faster than many of the analogs.
+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. Here, 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 just to make it visible to the
+  library. Customization is managed by library providers, so only scope boundaries interact with the library API.
+* **Simple API**. Only a few objects are needed to start using the library. Integration with your framework is
+  straightforward, with examples provided.
+* **Speed**. The library is fast enough that performance is not a concern. In fact, it outperforms many
+  alternatives.
 
 .. toctree::
    :hidden:
diff --git a/docs/integrations/aiogram.rst b/docs/integrations/aiogram.rst
index d4715fe..428652a 100644
--- a/docs/integrations/aiogram.rst
+++ b/docs/integrations/aiogram.rst
@@ -66,7 +66,7 @@ How to use
     container = make_async_container(YourProvider(), AiogramProvider())
 
 
-6. Setup dishka integration. ``autoinject=True`` is required unless you explicitly use ``@inject`` decorator
+6. Setup dishka integration. ``auto_inject=True`` is required unless you explicitly use ``@inject`` decorator
 
 .. code-block:: python
 
diff --git a/docs/integrations/aiohttp.rst b/docs/integrations/aiohttp.rst
index 7af32dc..003639c 100644
--- a/docs/integrations/aiohttp.rst
+++ b/docs/integrations/aiohttp.rst
@@ -64,11 +64,11 @@ It is available on ``SESSION`` and ``REQUEST`` scopes.
 
     container = make_async_container(YourProvider(), AiohttpProvider())
 
-5. Setup dishka integration. ``autoinject=True`` is required unless you explicitly use ``@inject`` decorator
+5. Setup dishka integration. ``auto_inject=True`` is required unless you explicitly use ``@inject`` decorator
 
 .. code-block:: python
 
-    setup_dishka(container=container, app=app, autoinject=True)
+    setup_dishka(container=container, app=app, auto_inject=True)
 
 
 6. *(optional)* Close container on app termination
@@ -100,7 +100,7 @@ To achieve REQUEST-scope you can enter in manually:
         websocket = web.WebsocketResponse()
         await websocket.prepare(request)
 
-        async for message in weboscket:
+        async for message in websocket:
             # enter the nested scope, which is Scope.REQUEST
             async with container() as request_container:
                 b = await request_container.get(B)  # object with Scope.REQUEST
diff --git a/docs/integrations/fastapi.rst b/docs/integrations/fastapi.rst
index 263b09d..4c25172 100644
--- a/docs/integrations/fastapi.rst
+++ b/docs/integrations/fastapi.rst
@@ -35,7 +35,7 @@ How to use
         def create_x(self, request: Request) -> X:
              ...
 
-3. *(optional)* Set route class to each of your fastapi routers to enable autoinjection (it works only for HTTP, not for websockets).
+3. *(optional)* Set route class to each of your fastapi routers to enable automatic injection (it works only for HTTP, not for websockets).
 
 .. code-block:: python
 
@@ -81,7 +81,7 @@ How to use
 
     app = FastAPI(lifespan=lifespan)
 
-5. Setup dishka integration. ``autoinject=True`` is required unless you explicitly use ``@inject`` decorator
+5. Setup dishka integration.
 
 .. code-block:: python
 
diff --git a/docs/integrations/faststream.rst b/docs/integrations/faststream.rst
index 8c4af1d..17ce1c1 100644
--- a/docs/integrations/faststream.rst
+++ b/docs/integrations/faststream.rst
@@ -75,7 +75,7 @@ How to use
     container = make_async_container(YourProvider(), FastStreamProvider())
 
 
-5. Setup dishka integration.  ``autoinject=True`` is required unless you explicitly use ``@inject`` decorator.
+5. Setup dishka integration.  ``auto_inject=True`` is required unless you explicitly use ``@inject`` decorator.
 
 .. code-block:: python
 
diff --git a/docs/integrations/flask.rst b/docs/integrations/flask.rst
index f87f7e1..30ed130 100644
--- a/docs/integrations/flask.rst
+++ b/docs/integrations/flask.rst
@@ -63,8 +63,8 @@ It is available on ``REQUEST`` scope.
 
     container = make_container(YourProvider(), FlaskProvider())
 
-5. Setup dishka integration. ``autoinject=True`` is required unless you explicitly use ``@inject`` decorator. It is important here to call it after registering all views and blueprints.
+5. Setup dishka integration. ``auto_inject=True`` is required unless you explicitly use ``@inject`` decorator. It is important here to call it after registering all views and blueprints.
 
 .. code-block:: python
 
-    setup_dishka(container=container, app=app, autoinject=True)
+    setup_dishka(container=container, app=app, auto_inject=True)
diff --git a/docs/integrations/grpcio.rst b/docs/integrations/grpcio.rst
index 8615764..ecc4c5d 100644
--- a/docs/integrations/grpcio.rst
+++ b/docs/integrations/grpcio.rst
@@ -72,7 +72,7 @@ Streaming
 If we receive single request from user like in ``unary_unary`` or ``unary_stream`` methods,
 we operate only 2 scopes: ``APP`` and ``REQUEST``.
 
-``stream_unary`` and ``stream_streeam`` methods are different: for one application you have multiple connections (one per client) and each connection delivers multiple messages. To support this we use additional scope: ``SESSION``:
+``stream_unary`` and ``stream_stream`` methods are different: for one application you have multiple connections (one per client) and each connection delivers multiple messages. To support this we use additional scope: ``SESSION``:
 
     ``APP`` |rarr| ``SESSION`` |rarr| ``REQUEST``
 
diff --git a/docs/integrations/sanic.rst b/docs/integrations/sanic.rst
index c3be3f5..37ae3c4 100644
--- a/docs/integrations/sanic.rst
+++ b/docs/integrations/sanic.rst
@@ -64,7 +64,7 @@ How to use
     container = make_async_container(YourProvider(), SanicProvider())
 
 
-6. Setup dishka integration. ``autoinject=True`` is required unless you explicitly use ``@inject`` decorator
+6. Setup dishka integration. ``auto_inject=True`` is required unless you explicitly use ``@inject`` decorator
 
 .. code-block:: python
 
diff --git a/docs/provider/from_context.rst b/docs/provider/from_context.rst
index f129d05..67f640a 100644
--- a/docs/provider/from_context.rst
+++ b/docs/provider/from_context.rst
@@ -25,7 +25,7 @@ You can put some data manually when entering scope and rely on it in your provid
         pass
 
 
-* Do you want to override the from_context? To do this, specify the parameter ``override=True``. This can be checked when passing proper ``validation_settings`` when creating container.
+* Do you want to override the ``from_context``? To do this, specify the parameter ``override=True``. This can be checked when passing proper ``validation_settings`` when creating container.
 
 .. code-block:: python
 
diff --git a/docs/provider/index.rst b/docs/provider/index.rst
index 472d472..63f8705 100644
--- a/docs/provider/index.rst
+++ b/docs/provider/index.rst
@@ -3,7 +3,7 @@ Provider
 
 **Provider** is an object which members are used to construct dependencies. ``Provider`` contains different factories and other entities and then is used to create a ``Container``. You can have multiple providers in one application and combine them in different ways to make it more modular.
 
-To configure provider you can either inherit and use decorators on you methods or just create an instance and use its methods.
+To configure provider you can either inherit and use decorators on your methods or just create an instance and use its methods.
 
 For example, imagine you have two classes: connection which is retrieved from external library and a gateway which requires such a connection.
 
diff --git a/docs/provider/provide.rst b/docs/provider/provide.rst
index 18d777e..f95f2a9 100644
--- a/docs/provider/provide.rst
+++ b/docs/provider/provide.rst
@@ -139,7 +139,7 @@ It works similar to :ref:`alias`.
 
 WithParents generates only one factory and many aliases and is equivalent to ``AnyOf[AImpl, A]``. The following parents are ignored: ``type``, ``object``, ``Enum``, ``ABC``, ``ABCMeta``, ``Generic``, ``Protocol``, ``Exception``, ``BaseException``
 
-* You object's dependencies (and their dependencies) can be simply created by calling their constructors. You do not need to register them manually. Use ``recursive=True`` to register them automatically
+* Your object's dependencies (and their dependencies) can be simply created by calling their constructors. You do not need to register them manually. Use ``recursive=True`` to register them automatically
 
 .. code-block:: python
 
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index cc99671..85d06b0 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -1,108 +1,64 @@
 Quickstart
 ********************
 
-1. Install dishka
+1. **Install Dishka.**
 
 .. code-block:: shell
 
     pip install dishka
 
-2. Write your classes, fill type hints. Imagine, you have two classes: Service (kind of business logic) and DAO (kind of data access) and some external api client:
+2. **Define your classes with type hints.** Imagine you have two classes: ``Service`` (business logic) and
+   ``DAO`` (data access), along with an external API client:
 
-.. code-block:: python
-
-    class DAO(Protocol):
-        ...
-
-    class Service:
-        def __init__(self, dao: DAO):
-            ...
-
-    class DAOImpl(DAO):
-        def __init__(self, connection: Connection):
-            ...
-
-    class SomeClient:
-        ...
-
-4. Create Provider instance. It is only used to setup all factories providing your objects.
-
-.. code-block:: python
-
-    from dishka import Provider
-
-    provider = Provider()
-
-
-5. Setup how to provide dependencies.
-
-We use ``scope=Scope.APP`` for dependencies which are created only once in application lifetime,
-and ``scope=Scope.REQUEST`` for those which should be recreated for each processing request/event/etc.
-To read more about scopes, refer :ref:`scopes`
-
-.. code-block:: python
+.. literalinclude:: ./quickstart_example.py
+   :language: python
+   :lines: 6-21
 
-    from dishka import Provider, Scope
+3. **Create** ``Provider`` instance and specify how to provide dependencies.
 
-    service_provider = Provider(scope=Scope.REQUEST)
-    service_provider.provide(Service)
-    service_provider.provide(DAOImpl, provides=DAO)
-    service_provider.provide(SomeClient, scope=Scope.APP)  # override provider scope
+Providers are used only to set up factories providing your objects.
 
-To provide connection we might need to write some custom code:
+Use ``scope=Scope.APP`` for dependencies created once for the entire application lifetime,
+and ``scope=Scope.REQUEST`` for those that need to be recreated for each request, event, etc.
+To learn more about scopes, see :ref:`scopes`
 
-.. code-block:: python
+.. literalinclude:: ./quickstart_example.py
+   :language: python
+   :lines: 24-30
 
-    from dishka import Provider, provide, Scope
+To provide a connection, you might need some custom code:
 
-    class ConnectionProvider(Provider):
-        @provide(Scope=Scope.REQUEST)
-        def new_connection(self) -> Connection:
-            conn = sqlite3.connect()
-            yield conn
-            conn.close()
+.. literalinclude:: ./quickstart_example.py
+   :language: python
+   :lines: 33-41
 
+4. **Create main** ``Container`` instance, passing providers, and enter ``APP`` scope.
 
-6. Create main ``Container`` instance passing providers, and step into ``APP`` scope.
+.. literalinclude:: ./quickstart_example.py
+   :language: python
+   :lines: 44-47
 
-.. code-block:: python
+5. **Access dependencies using container.** Container holds a cache of dependencies and is used to retrieve them.
+   You can use ``.get`` method to access ``APP``-scoped dependencies:
 
-    from dishka import make_container
+.. literalinclude:: ./quickstart_example.py
+   :language: python
+   :lines: 49-50
 
-    container = make_container(service_provider, ConnectionProvider())
+6. **Enter and exit** ``REQUEST`` **scope repeatedly using a context manager**:
 
-7. Container holds dependencies cache and is used to retrieve them. Here, you can use ``.get`` method to access APP-scoped dependencies:
+.. literalinclude:: ./quickstart_example.py
+   :language: python
+   :lines: 52-60
 
-.. code-block:: python
+7. **Close container** when done:
 
-    client = container.get(SomeClient)  # `SomeClient` has Scope.APP, so it is accessible here
-    client = container.get(SomeClient)  # same instance of `SomeClient`
+.. literalinclude:: ./quickstart_example.py
+   :language: python
+   :lines: 62
 
-
-8. You can enter and exit ``REQUEST`` scope multiple times after that using context manager:
-
-.. code-block:: python
-
-    # subcontainer to access more short-living objects
-    with container() as request_container:
-        service = request_container.get(Service)
-        service = request_container.get(Service)  # same service instance
-    # at this point connection will be closed as we exited context manager
-
-    # new subcontainer to have a new lifespan for request processing
-    with container() as request_container:
-        service = request_container.get(Service)  # new service instance
-
-
-9. Close container in the end:
-
-.. code-block:: python
-
-   container.close()
-
-
-10. If you are using supported framework add decorators and middleware for it.
-For more details see :ref:`integrations`
+8. **Integrate with your framework.** If you are using a supported framework, add decorators and middleware for it.
+   For more details, see :ref:`integrations`
 
 .. code-block:: python
 
@@ -110,10 +66,12 @@ For more details see :ref:`integrations`
         FromDishka, inject, setup_dishka,
     )
 
+
     @router.get("/")
     @inject
     async def index(service: FromDishka[Service]) -> str:
         ...
 
+
     ...
-    setup_dishka(container, app)
\ No newline at end of file
+    setup_dishka(container, app)
diff --git a/docs/quickstart_example.py b/docs/quickstart_example.py
new file mode 100644
index 0000000..d61acdf
--- /dev/null
+++ b/docs/quickstart_example.py
@@ -0,0 +1,62 @@
+import sqlite3
+
+from typing import Protocol, Iterable
+from sqlite3 import Connection
+
+class DAO(Protocol):
+    ...
+
+
+class Service:
+    def __init__(self, dao: DAO):
+        ...
+
+
+class DAOImpl(DAO):
+    def __init__(self, connection: Connection):
+        ...
+
+
+class SomeClient:
+    ...
+
+
+from dishka import Provider, Scope
+
+
+service_provider = Provider(scope=Scope.REQUEST)
+service_provider.provide(Service)
+service_provider.provide(DAOImpl, provides=DAO)
+service_provider.provide(SomeClient, scope=Scope.APP)  # override provider scope
+
+
+from dishka import Provider, provide, Scope
+
+
+class ConnectionProvider(Provider):
+    @provide(scope=Scope.REQUEST)
+    def new_connection(self) -> Iterable[Connection]:
+        conn = sqlite3.connect(":memory:")
+        yield conn
+        conn.close()
+
+
+from dishka import make_container
+
+
+container = make_container(service_provider, ConnectionProvider())
+
+client = container.get(SomeClient)  # `SomeClient` has Scope.APP, so it is accessible here
+client = container.get(SomeClient)  # same instance of `SomeClient`
+
+# subcontainer to access shorter-living objects
+with container() as request_container:
+    service = request_container.get(Service)
+    service = request_container.get(Service)  # same service instance
+# since we exited the context manager, the connection is now closed
+
+# new subcontainer to have a new lifespan for request processing
+with container() as request_container:
+    service = request_container.get(Service)  # new service instance
+
+container.close()
diff --git a/docs/requirements/technical.rst b/docs/requirements/technical.rst
index 6616eb4..48403e8 100644
--- a/docs/requirements/technical.rst
+++ b/docs/requirements/technical.rst
@@ -28,7 +28,7 @@ Technical requirements
 ========================
 
 1. Usage of container must not require modification of objects we are creating
-2. Container must not require to be global variable.
+2. Container must not require being a global variable.
 3. Container can require code changes on the borders of scopes (e.g. application start, middlewares, request handlers)
 
 4. Lifecycle
diff --git a/examples/real_world/myapp/db.py b/examples/real_world/myapp/db.py
index a0f252b..88d1439 100644
--- a/examples/real_world/myapp/db.py
+++ b/examples/real_world/myapp/db.py
@@ -1,13 +1,13 @@
 import logging
 
-from .use_cases import ProductGateway, UnitOfWork, User, UserGateway, Product
+from .use_cases import ProductGateway, Committer, User, UserGateway, Product
 
 logger = logging.getLogger(__name__)
 
 
-class FakeDbConnection(UnitOfWork):
+class FakeCommitter(Committer):
     def __init__(self):
-        logger.info("init FakeDbConnection as %s", self)
+        logger.info("init FakeCommitter as %s", self)
 
     def commit(self) -> None:
         logger.info("commit as %s", self)
@@ -17,9 +17,9 @@ class FakeDbConnection(UnitOfWork):
 
 
 class FakeUserGateway(UserGateway):
-    def __init__(self, unit_of_work: FakeDbConnection):
-        self.unit_of_work = unit_of_work
-        logger.info("init FakeUserGateway with %s", unit_of_work)
+    def __init__(self, committer: FakeCommitter):
+        self.committer = committer
+        logger.info("init FakeUserGateway with %s", committer)
 
     def get_user(self, user_id: int) -> User:
         logger.info("get_user %s as %s", user_id, self)
@@ -27,9 +27,9 @@ class FakeUserGateway(UserGateway):
 
 
 class FakeProductGateway(ProductGateway):
-    def __init__(self, unit_of_work: FakeDbConnection):
-        self.unit_of_work = unit_of_work
-        logger.info("init FakeProductGateway with %s", unit_of_work)
+    def __init__(self, committer: FakeCommitter):
+        self.committer = committer
+        logger.info("init FakeProductGateway with %s", committer)
 
     def add_product(self, product: Product) -> None:
         logger.info(
diff --git a/examples/real_world/myapp/ioc.py b/examples/real_world/myapp/ioc.py
index 21308ea..cb94f63 100644
--- a/examples/real_world/myapp/ioc.py
+++ b/examples/real_world/myapp/ioc.py
@@ -7,11 +7,11 @@ from dishka import (
     provide,
 )
 from .api_client import FakeWarehouseClient
-from .db import FakeDbConnection, FakeProductGateway, FakeUserGateway
+from .db import FakeCommitter, FakeProductGateway, FakeUserGateway
 from .use_cases import (
     AddProductsInteractor,
     ProductGateway,
-    UnitOfWork,
+    Committer,
     UserGateway,
     WarehouseClient,
 )
@@ -25,12 +25,12 @@ class AdaptersProvider(Provider):
     products = provide(FakeProductGateway, provides=ProductGateway)
 
     @provide
-    def connection(self) -> Iterable[FakeDbConnection]:
-        uow = FakeDbConnection()
-        yield uow
-        uow.close()
+    def connection(self) -> Iterable[FakeCommitter]:
+        committer = FakeCommitter()
+        yield committer
+        committer.close()
 
-    uow = alias(source=FakeDbConnection, provides=UnitOfWork)
+    committer = alias(source=FakeCommitter, provides=Committer)
 
     @provide(scope=Scope.APP)
     def warehouse(self) -> WarehouseClient:
diff --git a/examples/real_world/myapp/use_cases.py b/examples/real_world/myapp/use_cases.py
index fdd500a..cd5ca5d 100644
--- a/examples/real_world/myapp/use_cases.py
+++ b/examples/real_world/myapp/use_cases.py
@@ -29,7 +29,7 @@ class ProductGateway(Protocol):
         raise NotImplementedError
 
 
-class UnitOfWork(Protocol):
+class Committer(Protocol):
     @abstractmethod
     def commit(self) -> None:
         raise NotImplementedError
@@ -46,12 +46,12 @@ class AddProductsInteractor:
             self,
             user_gateway: UserGateway,
             product_gateway: ProductGateway,
-            unit_of_work: UnitOfWork,
+            committer: Committer,
             warehouse_client: WarehouseClient,
     ) -> None:
         self.user_gateway = user_gateway
         self.product_gateway = product_gateway
-        self.unit_of_work = unit_of_work
+        self.committer = committer
         self.warehouse_client = warehouse_client
 
     def __call__(self, user_id: int):
@@ -63,4 +63,4 @@ class AddProductsInteractor:
         self.product_gateway.add_product(product)
         product2 = Product(user_id, self.warehouse_client.next_product())
         self.product_gateway.add_product(product2)
-        self.unit_of_work.commit()
+        self.committer.commit()
diff --git a/pyproject.toml b/pyproject.toml
index 4e75dee..aac556b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,19 +10,18 @@ where = ["src"]
 
 [project]
 name = "dishka"
-version = "1.4.0"
+version = "0.1"
 readme = "README.md"
 authors = [
     { name = "Andrey Tikhonov", email = "17@itishka.org" },
 ]
 license = { text = "Apache-2.0" }
-description = "Cute DI framework with scopes and agreeable API"
+description = "Minimal DI framework"
 requires-python = ">=3.10"
 classifiers = [
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
-    "Programming Language :: Python :: 3.13",
     "Topic :: Software Development :: Libraries",
     "Typing :: Typed",
     "Intended Audience :: Developers",
diff --git a/src/dishka/async_container.py b/src/dishka/async_container.py
index f0b0da6..1993ffb 100644
--- a/src/dishka/async_container.py
+++ b/src/dishka/async_container.py
@@ -51,7 +51,7 @@ class AsyncContainer:
     ):
         self.registry = registry
         self.child_registries = child_registries
-        self._context = {DependencyKey(type(self), DEFAULT_COMPONENT): self}
+        self._context = {CONTAINER_KEY: self}
         if context:
             for key, value in context.items():
                 if not isinstance(key, DependencyKey):
@@ -227,7 +227,7 @@ def make_async_container(
 ) -> AsyncContainer:
     registries = RegistryBuilder(
         scopes=scopes,
-        container_type=AsyncContainer,
+        container_key=CONTAINER_KEY,
         providers=providers,
         skip_validation=skip_validation,
         validation_settings=validation_settings,
@@ -257,3 +257,6 @@ def make_async_container(
                 close_parent=True,
             )
     return container
+
+
+CONTAINER_KEY = DependencyKey(AsyncContainer, DEFAULT_COMPONENT)
diff --git a/src/dishka/container.py b/src/dishka/container.py
index a7a4acc..45d0a3d 100644
--- a/src/dishka/container.py
+++ b/src/dishka/container.py
@@ -51,7 +51,7 @@ class Container:
     ):
         self.registry = registry
         self.child_registries = child_registries
-        self._context = {DependencyKey(type(self), DEFAULT_COMPONENT): self}
+        self._context = {CONTAINER_KEY: self}
         if context:
             for key, value in context.items():
                 if not isinstance(key, DependencyKey):
@@ -223,7 +223,7 @@ def make_container(
 ) -> Container:
     registries = RegistryBuilder(
         scopes=scopes,
-        container_type=Container,
+        container_key=CONTAINER_KEY,
         providers=providers,
         skip_validation=skip_validation,
         validation_settings=validation_settings,
@@ -252,3 +252,6 @@ def make_container(
                 close_parent=True,
             )
     return container
+
+
+CONTAINER_KEY = DependencyKey(Container, DEFAULT_COMPONENT)
diff --git a/src/dishka/dependency_source/make_factory.py b/src/dishka/dependency_source/make_factory.py
index e3fa0aa..deb7280 100644
--- a/src/dishka/dependency_source/make_factory.py
+++ b/src/dishka/dependency_source/make_factory.py
@@ -103,10 +103,10 @@ def _type_repr(hint: Any) -> str:
 
 
 def _async_generator_result(hint: Any) -> Any:
-    if isinstance(hint, ProvideMultiple):
-        return ProvideMultiple([
-            _async_generator_result(x) for x in hint.items
-        ])
+    if get_origin(hint) is ProvideMultiple:
+        return ProvideMultiple[tuple(  # type: ignore[misc]
+            _async_generator_result(x) for x in get_args(hint)
+        )]
     origin = get_origin(hint)
     if origin is AsyncIterable:
         return get_args(hint)[0]
@@ -136,10 +136,10 @@ def _async_generator_result(hint: Any) -> Any:
 
 
 def _generator_result(hint: Any) -> Any:
-    if isinstance(hint, ProvideMultiple):
-        return ProvideMultiple([
-            _generator_result(x) for x in hint.items
-        ])
+    if get_origin(hint) is ProvideMultiple:
+        return ProvideMultiple[tuple(  # type: ignore[misc]
+            _generator_result(x) for x in get_args(hint)
+        )]
     origin = get_origin(hint)
     if origin is Iterable:
         return get_args(hint)[0]
@@ -433,6 +433,11 @@ def make_factory(
         is_in_class: bool,
         override: bool,
 ) -> Factory:
+    if get_origin(source) is ProvideMultiple:
+        if provides is None:
+            provides = source
+        source = get_args(source)[0]
+
     if is_bare_generic(source):
         source = source[get_type_vars(source)]  # type: ignore[index]
 
diff --git a/src/dishka/dependency_source/unpack_provides.py b/src/dishka/dependency_source/unpack_provides.py
index 173f51e..ed1b62b 100644
--- a/src/dishka/dependency_source/unpack_provides.py
+++ b/src/dishka/dependency_source/unpack_provides.py
@@ -1,4 +1,5 @@
 from collections.abc import Sequence
+from typing import get_args, get_origin
 
 from dishka.entities.key import DependencyKey
 from dishka.entities.provides_marker import ProvideMultiple
@@ -9,10 +10,10 @@ from .factory import Factory
 
 
 def unpack_factory(factory: Factory) -> Sequence[DependencySource]:
-    if not isinstance(factory.provides.type_hint, ProvideMultiple):
+    if get_origin(factory.provides.type_hint) is not ProvideMultiple:
         return [factory]
 
-    provides_first, *provides_others = factory.provides.type_hint.items
+    provides_first, *provides_others = get_args(factory.provides.type_hint)
 
     res: list[DependencySource] = [
         Alias(
@@ -43,7 +44,7 @@ def unpack_factory(factory: Factory) -> Sequence[DependencySource]:
 
 
 def unpack_decorator(decorator: Decorator) -> Sequence[DependencySource]:
-    if not isinstance(decorator.provides.type_hint, ProvideMultiple):
+    if get_origin(decorator.provides.type_hint) is not ProvideMultiple:
         return [decorator]
 
     return [
@@ -51,12 +52,12 @@ def unpack_decorator(decorator: Decorator) -> Sequence[DependencySource]:
             factory=decorator.factory,
             provides=DependencyKey(provides, decorator.provides.component),
         )
-        for provides in decorator.provides.type_hint.items
+        for provides in get_args(decorator.provides.type_hint)
     ]
 
 
 def unpack_alias(alias: Alias) -> Sequence[DependencySource]:
-    if not isinstance(alias.provides.type_hint, ProvideMultiple):
+    if get_origin(alias.provides.type_hint) is not ProvideMultiple:
         return [alias]
 
     return [
@@ -66,5 +67,5 @@ def unpack_alias(alias: Alias) -> Sequence[DependencySource]:
             cache=alias.cache,
             override=alias.override,
         )
-        for provides in alias.provides.type_hint.items
+        for provides in get_args(alias.provides.type_hint)
     ]
diff --git a/src/dishka/entities/provides_marker.py b/src/dishka/entities/provides_marker.py
index d786426..f6b9cf2 100644
--- a/src/dishka/entities/provides_marker.py
+++ b/src/dishka/entities/provides_marker.py
@@ -1,20 +1,32 @@
 from __future__ import annotations
 
-from collections.abc import Sequence
-from typing import TYPE_CHECKING, Any
+import sys
+import threading
+from typing import TYPE_CHECKING, Any, Generic, TypeVar
 
 __all__ = ["AnyOf", "ProvideMultiple"]
 
-if TYPE_CHECKING:
-    from typing import Union as AnyOf
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+
+    Variants = TypeVarTuple("Variants")
+    class ProvideMultiple(Generic[Unpack[Variants]]):
+        pass
 else:
-    class AnyOf:
-        def __class_getitem__(cls, item: Any) -> Any:
-            if isinstance(item, tuple):
-                return ProvideMultiple(item)
-            return item
+    # in this case we simulate generics with variadic typevars
+    # by suppressing builtin checks of parameters length
+    Variants = TypeVar("Variants")
+    provides_lock = threading.Lock()
 
+    class ProvideMultiple(Generic[Variants]):
+        def __class_getitem__(cls, item: tuple[Any]):
+            with provides_lock:
+                cls.__parameters__ = [Variants]*len(item)  # type: ignore[attr-defined, misc]
+                return super().__class_getitem__(item)  # type: ignore[misc]
 
-class ProvideMultiple:
-    def __init__(self, items: Sequence[Any]) -> None:
-        self.items = items
+
+if TYPE_CHECKING:
+    from typing import Union as AnyOf
+else:
+    AnyOf = ProvideMultiple
diff --git a/src/dishka/entities/with_parents.py b/src/dishka/entities/with_parents.py
index 1d1b7f1..3452037 100644
--- a/src/dishka/entities/with_parents.py
+++ b/src/dishka/entities/with_parents.py
@@ -174,5 +174,5 @@ else:
         def __class_getitem__(cls, item: TypeHint) -> TypeHint:
             parents = ParentsResolver().get_parents(item)
             if len(parents) > 1:
-                return ProvideMultiple(parents)
+                return ProvideMultiple[tuple(parents)]
             return parents[0]
diff --git a/src/dishka/integrations/aiogram_dialog.py b/src/dishka/integrations/aiogram_dialog.py
index 4d9a49d..05539d5 100644
--- a/src/dishka/integrations/aiogram_dialog.py
+++ b/src/dishka/integrations/aiogram_dialog.py
@@ -1,13 +1,12 @@
 __all__ = ["inject"]
 
 from collections.abc import Callable
-from typing import Any, Final, ParamSpec, TypeVar, cast
+from typing import Any, Final, TypeVar, cast
 
 from dishka import AsyncContainer
 from dishka.integrations.base import wrap_injection
 
 T = TypeVar("T")
-P = ParamSpec("P")
 TWO: Final = 2
 CONTAINER_NAME: Final = "dishka_container"
 
@@ -26,7 +25,7 @@ def _container_getter(
     return cast(AsyncContainer, container)
 
 
-def inject(func: Callable[P, T]) -> Callable[P, T]:
+def inject(func: Callable[..., T]) -> Callable[..., T]:
     return wrap_injection(
         func=func,
         is_async=True,
diff --git a/src/dishka/registry.py b/src/dishka/registry.py
index c7d67b5..fd58d3c 100644
--- a/src/dishka/registry.py
+++ b/src/dishka/registry.py
@@ -14,13 +14,16 @@ from .factory_compiler import compile_factory
 
 
 class Registry:
-    __slots__ = ("scope", "factories", "compiled", "compiled_async")
+    __slots__ = (
+        "scope", "factories", "compiled", "compiled_async", "has_fallback",
+    )
 
-    def __init__(self, scope: BaseScope):
+    def __init__(self, scope: BaseScope, *, has_fallback: bool) -> None:
         self.scope = scope
         self.factories: dict[DependencyKey, Factory] = {}
         self.compiled: dict[DependencyKey, Callable[..., Any]] = {}
         self.compiled_async: dict[DependencyKey, Callable[..., Any]] = {}
+        self.has_fallback = has_fallback
 
     def add_factory(
             self,
@@ -64,7 +67,7 @@ class Registry:
             origin = get_origin(dependency.type_hint)
             if not origin:
                 return None
-            if origin is type:
+            if origin is type and self.has_fallback:
                 return self._get_type_var_factory(dependency)
 
             origin_key = DependencyKey(origin, dependency.component)
@@ -112,10 +115,10 @@ class Registry:
             hint = source_dependency.type_hint
             if isinstance(hint, TypeVar):
                 hint = params_replacement[hint]
-            elif get_origin(hint):
+            elif get_origin(hint) and (type_vars:=get_type_vars(hint)):
                 hint = hint[tuple(
                     params_replacement[param]
-                    for param in get_type_vars(hint)
+                    for param in type_vars
                 )]
             new_dependencies.append(DependencyKey(
                 hint, source_dependency.component,
@@ -125,10 +128,10 @@ class Registry:
             hint = source_dependency.type_hint
             if isinstance(hint, TypeVar):
                 hint = params_replacement[hint]
-            elif get_origin(hint):
+            elif get_origin(hint) and (type_vars:=get_type_vars(hint)):
                 hint = hint[tuple(
                     params_replacement[param]
-                    for param in get_type_vars(hint)
+                    for param in type_vars
                 )]
             new_kw_dependencies[name] = DependencyKey(
                 hint, source_dependency.component,
diff --git a/src/dishka/registry_builder.py b/src/dishka/registry_builder.py
index ccc7526..8f654cf 100644
--- a/src/dishka/registry_builder.py
+++ b/src/dishka/registry_builder.py
@@ -120,7 +120,7 @@ class RegistryBuilder:
             *,
             scopes: type[BaseScope],
             providers: Sequence[BaseProvider],
-            container_type: type,
+            container_key: DependencyKey,
             skip_validation: bool,
             validation_settings: ValidationSettings,
     ) -> None:
@@ -131,7 +131,7 @@ class RegistryBuilder:
         self.components: set[Component] = {DEFAULT_COMPONENT}
         self.alias_sources: dict[DependencyKey, Any] = {}
         self.aliases: dict[DependencyKey, Alias] = {}
-        self.container_type = container_type
+        self.container_key = container_key
         self.decorator_depth: dict[DependencyKey, int] = defaultdict(int)
         self.skip_validation = skip_validation
         self.validation_settings = validation_settings
@@ -170,16 +170,18 @@ class RegistryBuilder:
                 self.aliases[provides] = alias
 
     def _init_registries(self) -> None:
+        has_fallback = True
         for scope in self.scopes:
-            registry = Registry(scope)
+            registry = Registry(scope, has_fallback=has_fallback)
             context_var = ContextVariable(
-                provides=DependencyKey(self.container_type, DEFAULT_COMPONENT),
+                provides=self.container_key,
                 scope=scope,
                 override=False,
             )
             for component in self.components:
                 registry.add_factory(context_var.as_factory(component))
             self.registries[scope] = registry
+            has_fallback = False
 
     def _process_factory(
             self, provider: BaseProvider, factory: Factory,


Test patch:
diff --git a/docs/integrations/litestar.rst b/docs/integrations/litestar.rst
index 25cdf69..fe555d4 100644
--- a/docs/integrations/litestar.rst
+++ b/docs/integrations/litestar.rst
@@ -66,7 +66,7 @@ How to use
     app = Litestar([endpoint], lifespan=[lifespan])
 
 
-6. Setup dishka integration. ``autoinject=True`` is required unless you explicitly use ``@inject`` decorator
+6. Setup dishka integration.
 
 .. code-block:: python
 
diff --git a/examples/real_world/tests/test_add_products.py b/examples/real_world/tests/test_add_products.py
index 2c6b516..e602cf9 100644
--- a/examples/real_world/tests/test_add_products.py
+++ b/examples/real_world/tests/test_add_products.py
@@ -9,8 +9,8 @@ import pytest
 from myapp.ioc import InteractorProvider
 from myapp.use_cases import (
     AddProductsInteractor,
+    Committer,
     ProductGateway,
-    UnitOfWork,
     User,
     UserGateway,
     WarehouseClient,
@@ -26,7 +26,7 @@ from dishka import (
 
 # app dependency logic
 class AdaptersProvider(Provider):
-    scope=Scope.APP
+    scope = Scope.APP
 
     @provide
     def users(self) -> UserGateway:
@@ -41,10 +41,10 @@ class AdaptersProvider(Provider):
         return gateway
 
     @provide
-    def uow(self) -> UnitOfWork:
-        uow = Mock()
-        uow.commit = Mock()
-        return uow
+    def committer(self) -> Committer:
+        committer = Mock()
+        committer.commit = Mock()
+        return committer
 
     @provide
     def warehouse(self) -> WarehouseClient:
@@ -63,4 +63,4 @@ def container():
 def test_interactor(container):
     interactor = container.get(AddProductsInteractor)
     interactor(1)
-    container.get(UnitOfWork).commit.assert_called_once_with()
+    container.get(Committer).commit.assert_called_once_with()
diff --git a/tests/unit/container/test_alias.py b/tests/unit/container/test_alias.py
index c079c44..1fdb615 100644
--- a/tests/unit/container/test_alias.py
+++ b/tests/unit/container/test_alias.py
@@ -67,6 +67,24 @@ def test_implicit():
     mock.assert_called_once()
 
 
+
+def test_implicit_no_source():
+    provider = Provider(scope=Scope.APP)
+    provider.provide_all(AnyOf[float, str])
+    container = make_container(provider)
+    assert container.get(float) == 0.0
+    assert container.get(str) == 0.0
+
+
+
+def test_implicit_all():
+    provider = Provider(scope=Scope.APP)
+    provider.provide_all(AnyOf[float, str])
+    container = make_container(provider)
+    assert container.get(float) == 0.0
+    assert container.get(str) == 0.0
+
+
 def test_implicit_generator():
     class MyProvider(Provider):
         value = 0
@@ -100,7 +118,7 @@ async def test_implicit_async_generator():
         value = 0
 
         @provide(scope=Scope.APP)
-        async def foo(self) -> AsyncIterable[AnyOf[float, int]]:
+        async def foo(self) -> AsyncIterable[AnyOf[float, "int"]]:
             self.value += 1
             yield self.value
 
diff --git a/tests/unit/container/test_generic.py b/tests/unit/container/test_generic.py
index 79b307f..4b00e59 100644
--- a/tests/unit/container/test_generic.py
+++ b/tests/unit/container/test_generic.py
@@ -207,3 +207,18 @@ def test_passing_type_var_decorator():
     provider.decorate(type_var_decorator)
     container = make_container(provider)
     assert container.get(int) == (int, 1, A(42, y=42))
+
+
+
+def func_with_type(param: type[T], param2: type[int]) -> A[T]:
+    return param, param2
+
+
+def test_provide_type_non_generic():
+    provider = Provider(scope=Scope.APP)
+    provider.provide(func_with_type, scope=Scope.REQUEST)
+    provider.provide(lambda: bool, provides=type[int])
+    container = make_container(provider)
+    with container() as c:
+        assert c.get(A[str]) == (str, bool)
+        assert c.get(A[int]) == (bool, bool)
diff --git a/tests/unit/test_quickstart_example.py b/tests/unit/test_quickstart_example.py
new file mode 100644
index 0000000..21152e1
--- /dev/null
+++ b/tests/unit/test_quickstart_example.py
@@ -0,0 +1,8 @@
+import runpy
+from pathlib import Path
+
+ROOT = Path(__file__).parent.parent.parent.resolve()
+QUICKSTART_EXAMPLE_PATH = ROOT / "docs/quickstart_example.py"
+
+def test_readme_example():
+    runpy.run_path(QUICKSTART_EXAMPLE_PATH)