Repository: reagento/dishka
Pull request #138 (2024-04-21 10:24:26)

1.1.0


Patch:
diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml
index c646fab..25312d0 100644
--- a/.github/workflows/setup.yml
+++ b/.github/workflows/setup.yml
@@ -22,9 +22,9 @@ jobs:
           - "3.12"
 
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - name: Set up ${{ matrix.python-version }} on ${{ matrix.os }}
-      uses: actions/setup-python@v3
+      uses: actions/setup-python@v5
       with:
         python-version: ${{ matrix.python-version }}
         
diff --git a/.gitignore b/.gitignore
index 2c479e6..a63468e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,5 @@ __pycache__/
 /.venv
 .mypy_cache/
 .idea/
-.vscode/
\ No newline at end of file
+.vscode/
+.venv/
\ No newline at end of file
diff --git a/README.md b/README.md
index 6c381d9..4b77b72 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ See more in [technical requirements](https://dishka.readthedocs.io/en/latest/req
 pip install dishka
 ```
 
-2. Create `Provider` instance. It is only used co setup all factories providing your objects.
+2. Create `Provider` instance. It is only used to setup all factories providing your objects.
 
 ```python
 from dishka import Provider
@@ -244,7 +244,7 @@ class MyProvider(Provider):
     request = from_context(provides=RequestClass)
 
     @provide
-    async def get_a(self, request: RequestClass, app: App) -> A:
+    def get_a(self, request: RequestClass, app: App) -> A:
         ...
 
 container = make_container(MyProvider(), context={App: app})
@@ -262,7 +262,7 @@ container = make_container(MyProvider(), OtherProvider())
 
 ```python
 class MyProvider(Provider):
-   scope=Scope.APP
+   scope = Scope.APP
 
    @provide
    async def get_a(self) -> A:
diff --git a/docs/advanced/components.rst b/docs/advanced/components.rst
index 8fe0723..54fb46b 100644
--- a/docs/advanced/components.rst
+++ b/docs/advanced/components.rst
@@ -64,7 +64,7 @@ Components are **isolated**: provider cannot implicitly request an object from a
         # default component is used here
 
         @provide(scope=Scope.APP)
-        def foo(self, a:int) -> float:
+        def foo(self, a: int) -> float:
             return a/10
 
 
@@ -95,7 +95,7 @@ Components can **link to each other**: each provider can add a component name wh
     class MainProvider(Provider):
 
         @provide(scope=Scope.APP)
-        def foo(self, a:Annotated[int, FromComponent("X")]) -> float:
+        def foo(self, a: Annotated[int, FromComponent("X")]) -> float:
             return a/10
 
 
diff --git a/docs/advanced/context.rst b/docs/advanced/context.rst
index 2613480..003cad3 100644
--- a/docs/advanced/context.rst
+++ b/docs/advanced/context.rst
@@ -8,7 +8,7 @@ The difference from normal factories is that they are not created inside some ``
 Working with context data consists of three parts:
 
 1. Declaration that object is received from context using :ref:`from-context`. You need to provide the type and scope.
-2. Usage of that object in providers. There is now difference how the object is
+2. Usage of that object in providers.
 3. Passing actual values on scope entrance. It can be container creation for top level scope or container calls for nested ones. Use it in form ``context={Type: value,...}``
 
 .. code-block:: python
@@ -38,4 +38,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.
\ No newline at end of file
+    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.
diff --git a/docs/concepts.rst b/docs/concepts.rst
index a281d44..9fdd0c8 100644
--- a/docs/concepts.rst
+++ b/docs/concepts.rst
@@ -107,11 +107,11 @@ This allows you to have multiple parts of application build separately without n
     class MainProvider(Provider):
 
         @provide(scope=Scope.APP)
-        def foo(self, a:Annotated[int, FromComponent("X")]) -> float:
+        def foo(self, a: Annotated[int, FromComponent("X")]) -> float:
             return a/10
 
         @provide(scope=Scope.APP)
-        def bar(self, a:int) -> complex:
+        def bar(self, a: int) -> complex:
             return a + 0j
 
 
diff --git a/docs/integrations/index.rst b/docs/integrations/index.rst
index b9af24c..810d8d3 100644
--- a/docs/integrations/index.rst
+++ b/docs/integrations/index.rst
@@ -15,6 +15,7 @@ Built-in frameworks integrations:
 * Arq
 * FastStream
 * TaskIq
+* Sanic
 
 Common approach
 =====================
@@ -27,6 +28,11 @@ To use framework integration you mainly need to do 3 things:
 * add ``FromDishka[YourClass]`` on you framework handlers (or view-functions)
 * decorate your handlers with ``@inject`` before registering them in framework. Some integrations do not required it, see :ref:`autoinject`
 
+.. note::
+   ``FromDishka[T]`` is basically a synonym for ``Annotated[T, FromComponent()]`` and is used to get an object from default component. To use other component you can use the same syntax with annotated ``Annotated[T, FromComponent("X")]``.
+
+   For more details on components see :ref:`components`
+
 For FastAPI it will look like:
 
 .. code-block:: python
@@ -77,7 +83,7 @@ With some frameworks we provide an option to inject dependencies in handlers wit
 
     from dishka.integrations.flask import FromDishka, setup_dishka
 
-    @app.get("/"
+    @app.get("/")
     def index(
             *,
             interactor: FromDishka[Interactor],
@@ -91,7 +97,7 @@ With some frameworks we provide an option to inject dependencies in handlers wit
 
 .. code-block:: python
 
-    from dishka.integrations.fastapi import FromDishka, setup_dishka
+    from dishka.integrations.fastapi import FromDishka, DishkaRoute, setup_dishka
 
     router = APIRouter(route_class=DishkaRoute)
 
@@ -105,13 +111,51 @@ With some frameworks we provide an option to inject dependencies in handlers wit
 
     setup_dishka(container, app)
 
+* For **FasStream** (**0.5.0** version and higher) you need to provide ``auto_inject=True`` when calling ``setup_dishka``. It is important here to call it before registering any subscribers or router include:
+
+.. code-block:: python
+
+    from faststream import FastStream
+    from faststream.nats import NatsBroker, NatsMessage
+    from dishka import make_async_container
+    from dishka.integrations.faststream import FastStreamProvider, FromDishka, setup_dishka
+
+    broker = NatsBroker()
+    app = FastStream(broker)
+    setup_dishka(make_async_container(..., FastStreamProvider), app, auto_inject=True)
+
+    @broker.subscriber("/")
+    def index(
+            *,
+            message: FromDishka[NatsMessage],
+    ) -> str:
+        await message.ack()
+        return message.body
+
+* For **Sanic** you need to provide ``auto_inject=True`` when calling ``setup_dishka``. It is important here to call it after registering all views and blueprints. E.g:
+
+.. code-block:: python
+
+    from sanic import Sanic, Request, HTTPResponse
+    from dishka.integrations.sanic import FromDishka, setup_dishka
+
+    app = Sanic(__name__)
+
+    @app.get("/")
+    async def index(
+        request: Request,
+        interactor: FromDishka[Interactor],
+    ) -> HTTPResponse:
+        return HTTPResponse(interactor())
+
+    setup_dishka(container=container, app=app, auto_inject=True)
 
 Context data
 ====================
 
 As ``REQUEST`` scope is entered automatically you cannot pass context data directly, but integrations do it for you:
 
-This objects are passed to context:
+These objects are passed to context:
 
 * aiohttp - ``aiohttp.web_request.Request``
 * Flask - ``flask.Request``
@@ -121,8 +165,9 @@ This objects are passed to context:
 * Aiogram - ``aiogram.types.TelegramObject``
 * pyTelegramBotAPI - actual type of event (like ``Message``) is used.
 * Arq - no objects
-* FastStream - no objects
+* FastStream - ``faststream.broker.message.StreamMessage`` or ``faststream.[broker].[Broker]Message``, ``faststream.utils.ContextRepo`` 
 * TaskIq - no objects
+* Sanic - ``sanic.request.Request``
 
 To use such objects you need to declare them in your provider using :ref:`from-context` and then they will be available as factories params.
 
diff --git a/docs/provider/alias.rst b/docs/provider/alias.rst
index 5dfe3a9..8f1777b 100644
--- a/docs/provider/alias.rst
+++ b/docs/provider/alias.rst
@@ -1,6 +1,6 @@
 .. _alias:
 
-@alias
+alias
 ****************
 
 ``alias`` is used to allow retrieving of the same object by different type hints. E.g. you have configured how to provide ``A`` object and want to use it as AProtocol: ``container.get(A)==container.get(AProtocol)``.
diff --git a/docs/provider/decorate.rst b/docs/provider/decorate.rst
index 858b003..dcde5f4 100644
--- a/docs/provider/decorate.rst
+++ b/docs/provider/decorate.rst
@@ -1,6 +1,6 @@
 .. _decorate:
 
-@decorate
+decorate
 *********************
 
 ``decorate`` is used to modify or wrap an object which is already configured in another ``Provider``.
diff --git a/docs/provider/from_context.rst b/docs/provider/from_context.rst
index 03f7006..486b15c 100644
--- a/docs/provider/from_context.rst
+++ b/docs/provider/from_context.rst
@@ -1,6 +1,6 @@
 .. _from-context:
 
-@from_context
+from_context
 ****************
 
 You can put some data manually when entering scope and rely on it in your provider factories. To make it work you need to mark a dependency as retrieved from context using ``from_context`` and the use it as usual. Later, set ``context=`` argument when you enter corresponding scope.
@@ -17,7 +17,7 @@ You can put some data manually when entering scope and rely on it in your provid
         request = from_context(provides=RequestClass)
 
         @provide
-        async def get_a(self, request: RequestClass, app: App) -> A:
+        def get_a(self, request: RequestClass, app: App) -> A:
             ...
 
     container = make_container(MyProvider(), context={App: app})
diff --git a/docs/provider/index.rst b/docs/provider/index.rst
index 5c13b18..472d472 100644
--- a/docs/provider/index.rst
+++ b/docs/provider/index.rst
@@ -47,12 +47,12 @@ Or using inheritance:
             yield conn
             conn.close()
 
-        gateway = provider.provide(Gateway)
+        gateway = provide(Gateway)
 
     container = make_container(MyProvider(scope=Scope.APP))
 
 
-You class-based provider can have ``__init__`` method and methods access ``self`` as usual. It can be useful for passing configuration:
+Your class-based provider can have ``__init__`` method and methods access ``self`` as usual. It can be useful for passing configuration:
 
 .. code-block:: python
 
@@ -63,7 +63,7 @@ You class-based provider can have ``__init__`` method and methods access ``self`
 
         @provide
         def get_connection(self) -> Iterable[Connection]:
-            conn = connect(self.uri)  #
+            conn = connect(self.uri)  # use passed configuration
             yield conn
             conn.close()
 
@@ -100,6 +100,7 @@ Though it is a normal object, not all attributes are analyzed by ``Container``,
 .. toctree::
 
    provide
+   provide_all
    alias
    from_context
    decorate
diff --git a/docs/provider/provide.rst b/docs/provider/provide.rst
index 7aefdfe..827a820 100644
--- a/docs/provider/provide.rst
+++ b/docs/provider/provide.rst
@@ -1,6 +1,6 @@
 .. _provide:
 
-@provide
+provide
 ******************
 
 ``provide`` function is used to declare a factory providing a dependency. It can be used with some class or as a method decorator (either sync or async). It supports finalization of dependency if you make it a generator.
@@ -36,6 +36,20 @@ By default the result is cached within scope. You can disable it providing ``cac
             yield a
             a.close()
 
+Also, if an error occurs during process handling (inside the ``with`` block), it will be sent to the generator:
+
+.. code-block:: python
+
+    class MyProvider(Provider):
+        @provide(scope=Scope.REQUEST)
+        def get_a(self) -> Iterable[A]:
+            a = A()
+            exc = yield a
+            # exc will be None if an exception has not occurred
+            if exc:
+                print("Some exception while process handling: ", exc)
+            a.close()
+
 * Do not have any specific logic and just want to create class using its ``__init__``? Then add a provider attribute using ``provide`` as function passing that class.
 
 .. code-block:: python
diff --git a/docs/provider/provide_all.rst b/docs/provider/provide_all.rst
new file mode 100644
index 0000000..ce07443
--- /dev/null
+++ b/docs/provider/provide_all.rst
@@ -0,0 +1,31 @@
+.. _provide_all:
+
+provide_all
+******************
+
+``provide_all`` is a helper function which can be used instead of repeating ``provide`` call with just class passed and same scope
+
+These two providers are equal for the container
+
+.. code-block:: python
+
+    from dishka import provide, provide_all, Provider, Scope
+
+    class OneByOne(Provider):
+        scope = Scope.APP
+
+        a = provide(ClassA)
+        b = provide(ClassB)
+
+    class AllAtOnce(Provider):
+        scope = Scope.APP
+
+        ab = provide_all(ClassA, ClassB)
+
+
+It is also available as a method:
+
+.. code-block:: python
+
+    provider = Provider(scope=Scope.APP)
+    provider.provide_all(ClassA, ClassB)
\ No newline at end of file
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 2b127a6..8a1f94f 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -8,7 +8,7 @@ Quickstart
     pip install dishka
 
 
-2. Create Provider instance. It is only used co setup all factories providing your objects.
+2. Create Provider instance. It is only used to setup all factories providing your objects.
 
 .. code-block:: python
 
diff --git a/examples/integrations/faststream_app.py b/examples/integrations/faststream_app.py
index 2a7dade..a893d13 100644
--- a/examples/integrations/faststream_app.py
+++ b/examples/integrations/faststream_app.py
@@ -1,10 +1,8 @@
-from typing import Annotated
-
-from faststream import FastStream
-from faststream.nats import NatsBroker
+from faststream import ContextRepo, FastStream
+from faststream.nats import NatsBroker, NatsMessage
 
 from dishka import Provider, Scope, make_async_container, provide
-from dishka.integrations.faststream import FromDishka, inject, setup_dishka
+from dishka.integrations.faststream import FromDishka, setup_dishka
 
 
 class A:
@@ -32,15 +30,16 @@ container = make_async_container(provider)
 
 broker = NatsBroker()
 app = FastStream(broker)
-setup_dishka(container, app)
+setup_dishka(container, app, auto_inject=True)
 
 
 @broker.subscriber("test")
-@inject
 async def handler(
     msg: str,
     a: FromDishka[A],
     b: FromDishka[B],
+    raw_message: FromDishka[NatsMessage],
+    faststream_context: FromDishka[ContextRepo],
 ):
     print(msg, a, b)
 
diff --git a/examples/integrations/sanic_app.py b/examples/integrations/sanic_app.py
new file mode 100644
index 0000000..cd18bc4
--- /dev/null
+++ b/examples/integrations/sanic_app.py
@@ -0,0 +1,65 @@
+from typing import Protocol
+
+from sanic import Blueprint, HTTPResponse, Request, Sanic
+
+from dishka import Provider, Scope, make_async_container, provide
+from dishka.integrations.sanic import FromDishka, inject, setup_dishka
+
+
+class DbGateway(Protocol):
+    def get(self) -> str:
+        raise NotImplementedError
+
+
+class FakeDbGateway(DbGateway):
+    def get(self) -> str:
+        return "Hello, world!"
+
+
+class Interactor:
+    def __init__(self, gateway: DbGateway) -> None:
+        self.gateway = gateway
+
+    def __call__(self) -> str:
+        return self.gateway.get()
+
+
+class AdaptersProvider(Provider):
+    @provide(scope=Scope.REQUEST)
+    def get_db_gateway(self) -> DbGateway:
+        return FakeDbGateway()
+
+
+class InteractorsProvider(Provider):
+    interactor = provide(Interactor, scope=Scope.REQUEST)
+
+
+bp = Blueprint("example")
+
+
+@bp.get("/")
+@inject
+async def index(
+    _: Request,
+    interactor: FromDishka[Interactor],
+) -> HTTPResponse:
+    return HTTPResponse(interactor())
+
+
+@bp.get("/auto")
+async def auto(
+    _: Request,
+    interactor: FromDishka[Interactor],
+) -> HTTPResponse:
+    return HTTPResponse(interactor())
+
+
+if __name__ == "__main__":
+    app = Sanic(__name__)
+
+    app.blueprint(bp)
+    container = make_async_container(AdaptersProvider(), InteractorsProvider())
+
+    setup_dishka(container, app, auto_inject=True)
+
+    app.run(host="127.0.0.1", port=8002, single_process=True)
diff --git a/pyproject.toml b/pyproject.toml
index 72656d2..9a06ec2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,7 +10,7 @@ where = ["src"]
 
 [project]
 name = "dishka"
-version = "1.0.0"
+version = "0.1"
 readme = "README.md"
 authors = [
     { name = "Andrey Tikhonov", email = "17@itishka.org" },
diff --git a/requirements/faststream-044.txt b/requirements/faststream-044.txt
deleted file mode 100644
index 8e81d03..0000000
--- a/requirements/faststream-044.txt
+++ /dev/null
@@ -1,3 +0,0 @@
--r test.txt
-
-faststream[nats]==0.4.4
\ No newline at end of file
diff --git a/requirements/faststream-047.txt b/requirements/faststream-047.txt
new file mode 100644
index 0000000..3402fb8
--- /dev/null
+++ b/requirements/faststream-047.txt
@@ -0,0 +1,3 @@
+-r test.txt
+
+faststream[nats]==0.4.7
\ No newline at end of file
diff --git a/requirements/faststream-050.txt b/requirements/faststream-050.txt
new file mode 100644
index 0000000..0b7e2c8
--- /dev/null
+++ b/requirements/faststream-050.txt
@@ -0,0 +1,3 @@
+-r test.txt
+
+faststream[nats]==0.5.0rc2
\ No newline at end of file
diff --git a/requirements/sanic-23121.txt b/requirements/sanic-23121.txt
new file mode 100644
index 0000000..fcb0dc9
--- /dev/null
+++ b/requirements/sanic-23121.txt
@@ -0,0 +1,4 @@
+-r asgi.txt
+sanic==23.12.1
+sanic-testing==23.12.0
+setuptools==69.5.1
diff --git a/src/dishka/__init__.py b/src/dishka/__init__.py
index 6e0229d..6d5f887 100644
--- a/src/dishka/__init__.py
+++ b/src/dishka/__init__.py
@@ -4,7 +4,8 @@ __all__ = [
     "DEFAULT_COMPONENT", "Component",
     "make_container", "Container", "FromComponent",
     "Provider",
-    "alias", "decorate", "from_context", "provide", "DependencyKey",
+    "alias", "decorate", "from_context", "provide", "provide_all",
+    "DependencyKey",
     "FromDishka",
     "BaseScope", "Scope", "new_scope",
 ]
@@ -16,6 +17,7 @@ from .dependency_source import (
     decorate,
     from_context,
     provide,
+    provide_all,
 )
 from .entities.component import DEFAULT_COMPONENT, Component
 from .entities.depends_marker import FromDishka
diff --git a/src/dishka/_adaptix/common.py b/src/dishka/_adaptix/common.py
index 023a3fc..bae88ea 100644
--- a/src/dishka/_adaptix/common.py
+++ b/src/dishka/_adaptix/common.py
@@ -1,13 +1,14 @@
 from typing import TYPE_CHECKING, Any, Callable, Tuple, Type, TypeVar, Union
 
-K_contra = TypeVar('K_contra', contravariant=True)
-V_co = TypeVar('V_co', covariant=True)
-T = TypeVar('T')
+K_contra = TypeVar("K_contra", contravariant=True)
+V_co = TypeVar("V_co", covariant=True)
+T = TypeVar("T")
 
 Loader = Callable[[Any], V_co]
 Dumper = Callable[[K_contra], Any]
 Converter = Callable[..., Any]
-Coercer = Callable[[Any], Any]
+Coercer = Callable[[Any, Any], Any]
+OneArgCoercer = Callable[[Any], Any]
 
 TypeHint = Any
 
@@ -17,6 +18,6 @@ Catchable = Union[Type[BaseException], VarTuple[Type[BaseException]]]
 
 # https://github.com/python/typing/issues/684#issuecomment-548203158
 if TYPE_CHECKING:
-    EllipsisType = ellipsis  # pylint: disable=undefined-variable  # noqa: F821
+    EllipsisType = ellipsis  # noqa: F821
 else:
     EllipsisType = type(Ellipsis)
diff --git a/src/dishka/_adaptix/feature_requirement.py b/src/dishka/_adaptix/feature_requirement.py
index bdb1a1d..ea10c9c 100644
--- a/src/dishka/_adaptix/feature_requirement.py
+++ b/src/dishka/_adaptix/feature_requirement.py
@@ -17,7 +17,7 @@ def _false():
 
 
 class Requirement(ABC):
-    __slots__ = ('is_meet', '__bool__', '__dict__')
+    __slots__ = ("is_meet", "__bool__", "__dict__")
 
     def __init__(self):
         self.is_meet = self._evaluate()
@@ -60,7 +60,7 @@ class DistributionRequirement(Requirement):
 
     @property
     def fail_reason(self) -> str:
-        return f'Installed distribution {self.distribution_name!r} is required'
+        return f"Installed distribution {self.distribution_name!r} is required"
 
 
 class DistributionVersionRequirement(DistributionRequirement):
@@ -124,15 +124,15 @@ class DistributionVersionRequirement(DistributionRequirement):
         if match is None:
             raise ValueError
         return (
-            match.group('epoch') or 0,
-            self._remove_trailing_zeros(int(i) for i in match.group('release').split('.')),
-            match.group('pre') is not None,
-            match.group('dev') is not None,
+            match.group("epoch") or 0,
+            self._remove_trailing_zeros(int(i) for i in match.group("release").split(".")),
+            match.group("pre") is not None,
+            match.group("dev") is not None,
         )
 
     @property
     def fail_reason(self) -> str:
-        return f'Installed distribution {self.distribution_name!r} of {self.min_version!r} is required'
+        return f"Installed distribution {self.distribution_name!r} of {self.min_version!r} is required"
 
 
 class PythonImplementationRequirement(Requirement):
@@ -145,7 +145,7 @@ class PythonImplementationRequirement(Requirement):
 
     @property
     def fail_reason(self) -> str:
-        return f'{self.implementation_name} is required'
+        return f"{self.implementation_name} is required"
 
 
 HAS_PY_39 = PythonVersionRequirement((3, 9))
@@ -168,11 +168,14 @@ HAS_UNPACK = HAS_PY_311
 HAS_PY_312 = PythonVersionRequirement((3, 12))
 HAS_TV_SYNTAX = HAS_PY_312
 
-HAS_SUPPORTED_ATTRS_PKG = DistributionVersionRequirement('attrs', '21.3.0')
-HAS_ATTRS_PKG = DistributionRequirement('attrs')
+HAS_SUPPORTED_ATTRS_PKG = DistributionVersionRequirement("attrs", "21.3.0")
+HAS_ATTRS_PKG = DistributionRequirement("attrs")
 
-HAS_SUPPORTED_SQLALCHEMY_PKG = DistributionVersionRequirement('sqlalchemy', '2.0.0')
-HAS_SQLALCHEMY_PKG = DistributionRequirement('sqlalchemy')
+HAS_SUPPORTED_SQLALCHEMY_PKG = DistributionVersionRequirement("sqlalchemy", "2.0.0")
+HAS_SQLALCHEMY_PKG = DistributionRequirement("sqlalchemy")
 
-IS_CPYTHON = PythonImplementationRequirement('cpython')
-IS_PYPY = PythonImplementationRequirement('pypy')
+HAS_SUPPORTED_PYDANTIC_PKG = DistributionVersionRequirement("pydantic", "2.0.0")
+HAS_PYDANTIC_PKG = DistributionRequirement("pydantic")
+
+IS_CPYTHON = PythonImplementationRequirement("cpython")
+IS_PYPY = PythonImplementationRequirement("pypy")
diff --git a/src/dishka/_adaptix/type_tools/__init__.py b/src/dishka/_adaptix/type_tools/__init__.py
index 63df61d..79d25e6 100644
--- a/src/dishka/_adaptix/type_tools/__init__.py
+++ b/src/dishka/_adaptix/type_tools/__init__.py
@@ -1,6 +1,5 @@
 from .basic_utils import (
     create_union,
-    get_all_type_hints,
     is_bare_generic,
     is_generic,
     is_generic_class,
@@ -11,8 +10,8 @@ from .basic_utils import (
     is_subclass_soft,
     is_typed_dict_class,
     is_user_defined_generic,
-    strip_alias,
 )
+from .fundamentals import get_all_type_hints, get_generic_args, get_type_vars, is_pydantic_class, strip_alias
 from .norm_utils import is_class_var, strip_tags
 from .normalize_type import (
     AnyNormTypeVarLike,
diff --git a/src/dishka/_adaptix/type_tools/basic_utils.py b/src/dishka/_adaptix/type_tools/basic_utils.py
index 877e3ef..198b8ac 100644
--- a/src/dishka/_adaptix/type_tools/basic_utils.py
+++ b/src/dishka/_adaptix/type_tools/basic_utils.py
@@ -1,65 +1,45 @@
 import types
 import typing
-from typing import (
-    Any,
-    Dict,
-    ForwardRef,
-    Generic,
-    Iterable,
-    Protocol,
-    TypedDict,
-    TypeVar,
-    Union,
-    get_args,
-    get_origin,
-    get_type_hints,
-)
+from typing import Any, Dict, ForwardRef, Generic, NewType, Protocol, TypedDict, TypeVar, Union
 
 from ..common import TypeHint, VarTuple
 from ..feature_requirement import HAS_ANNOTATED, HAS_PY_39, HAS_PY_312, HAS_STD_CLASSES_GENERICS
 from .constants import BUILTIN_ORIGIN_TO_TYPEVARS
-
-TYPED_DICT_MCS = type(types.new_class("_TypedDictSample", (TypedDict,), {}))
-
-
-def strip_alias(type_hint: TypeHint) -> TypeHint:
-    origin = get_origin(type_hint)
-    return type_hint if origin is None else origin
+from .fundamentals import get_generic_args, get_type_vars, strip_alias
 
 
 def is_subclass_soft(cls, classinfo) -> bool:
-    """Acts like builtin issubclass,
-     but returns False instead of rising TypeError
-    """
+    """Acts like builtin issubclass, but returns False instead of rising TypeError"""
     try:
         return issubclass(cls, classinfo)
     except TypeError:
         return False
 
 
-def has_attrs(obj, attrs: Iterable[str]) -> bool:
-    return all(
-        hasattr(obj, attr_name)
-        for attr_name in attrs
-    )
+_NEW_TYPE_CLS = type(NewType("", None))
 
 
 def is_new_type(tp) -> bool:
-    return has_attrs(tp, ['__supertype__', '__name__'])
+    return isinstance(tp, _NEW_TYPE_CLS)
+
+
+_TYPED_DICT_MCS = type(types.new_class("_TypedDictSample", (TypedDict,), {}))
 
 
 def is_typed_dict_class(tp) -> bool:
-    return isinstance(tp, TYPED_DICT_MCS)
+    return isinstance(tp, _TYPED_DICT_MCS)
 
 
-NAMED_TUPLE_METHODS = ('_fields', '_field_defaults', '_make', '_replace', '_asdict')
+_NAMED_TUPLE_METHODS = ("_fields", "_field_defaults", "_make", "_replace", "_asdict")
 
 
 def is_named_tuple_class(tp) -> bool:
     return (
         is_subclass_soft(tp, tuple)
-        and
-        has_attrs(tp, NAMED_TUPLE_METHODS)
+        and all(
+            hasattr(tp, attr_name)
+            for attr_name in _NAMED_TUPLE_METHODS
+        )
     )
 
 
@@ -74,24 +54,12 @@ def create_union(args: tuple):
     return Union[args]
 
 
-if HAS_ANNOTATED:
-    def get_all_type_hints(obj, globalns=None, localns=None):
-        return get_type_hints(obj, globalns, localns, include_extras=True)
-else:
-    get_all_type_hints = get_type_hints
-
-
 def is_parametrized(tp: TypeHint) -> bool:
-    return bool(get_args(tp))
-
-
-def get_type_vars(tp: TypeHint) -> VarTuple[TypeVar]:
-    return getattr(tp, '__parameters__', ())
+    return bool(get_generic_args(tp))
 
 
 if HAS_PY_312:
     def is_user_defined_generic(tp: TypeHint) -> bool:
-        # pylint: disable=no-member
         return (
             bool(get_type_vars(tp))
             and (
@@ -121,7 +89,8 @@ def is_generic(tp: TypeHint) -> bool:
         )
         or (
             bool(HAS_ANNOTATED)
-            and get_origin(tp) == typing.Annotated
+            and strip_alias(tp) == typing.Annotated
+            and tp != typing.Annotated
             and is_generic(tp.__origin__)
         )
     )
@@ -156,25 +125,21 @@ def is_generic_class(cls: type) -> bool:
 
 
 def get_type_vars_of_parametrized(tp: TypeHint) -> VarTuple[TypeVar]:
-    try:
-        params = tp.__parameters__
-    except AttributeError:
+    params = get_type_vars(tp)
+    if not params:
         return ()
-
     if isinstance(tp, type):
         if HAS_STD_CLASSES_GENERICS and isinstance(tp, types.GenericAlias):
             return params
         return ()
-    if get_origin(tp) is not None and get_args(tp) == ():
+    if strip_alias(tp) != tp and get_generic_args(tp) == ():
         return ()
     return params
 
 
 if HAS_PY_39:
     def eval_forward_ref(namespace: Dict[str, Any], forward_ref: ForwardRef):
-        # pylint: disable=protected-access
         return forward_ref._evaluate(namespace, None, frozenset())
 else:
     def eval_forward_ref(namespace: Dict[str, Any], forward_ref: ForwardRef):
-        # pylint: disable=protected-access
         return forward_ref._evaluate(namespace, None)  # type: ignore[call-arg]
diff --git a/src/dishka/_adaptix/type_tools/constants.py b/src/dishka/_adaptix/type_tools/constants.py
index f05a630..e296399 100644
--- a/src/dishka/_adaptix/type_tools/constants.py
+++ b/src/dishka/_adaptix/type_tools/constants.py
@@ -1,3 +1,4 @@
+# mypy: disable-error-code="dict-item"
 import collections
 import concurrent.futures
 import queue
@@ -7,29 +8,29 @@ from typing import Mapping, TypeVar
 
 from ..common import VarTuple
 
-_AnyStrT = TypeVar('_AnyStrT', str, bytes)
-_T1 = TypeVar('_T1')
-_T2 = TypeVar('_T2')
-_T1_co = TypeVar('_T1_co', covariant=True)
+_AnyStrT = TypeVar("_AnyStrT", str, bytes)
+_T1 = TypeVar("_T1")
+_T2 = TypeVar("_T2")
+_T1_co = TypeVar("_T1_co", covariant=True)
 _AnyStr_co = TypeVar("_AnyStr_co", str, bytes, covariant=True)
 
 BUILTIN_ORIGIN_TO_TYPEVARS: Mapping[type, VarTuple[TypeVar]] = {
-    re.Pattern: (_AnyStrT, ),  # type: ignore[dict-item]
-    re.Match: (_AnyStrT, ),  # type: ignore[dict-item]
-    PathLike: (_AnyStr_co, ),  # type: ignore[dict-item]
-    type: (_T1,),  # type: ignore[dict-item]
-    list: (_T1,),  # type: ignore[dict-item]
-    set: (_T1,),  # type: ignore[dict-item]
-    frozenset: (_T1_co, ),  # type: ignore[dict-item]
-    collections.Counter: (_T1,),  # type: ignore[dict-item]
-    collections.deque: (_T1,),  # type: ignore[dict-item]
-    dict: (_T1, _T2),  # type: ignore[dict-item]
-    collections.defaultdict: (_T1, _T2),  # type: ignore[dict-item]
-    collections.OrderedDict: (_T1, _T2),  # type: ignore[dict-item]
-    collections.ChainMap: (_T1, _T2),  # type: ignore[dict-item]
-    queue.Queue: (_T1, ),  # type: ignore[dict-item]
-    queue.PriorityQueue: (_T1, ),  # type: ignore[dict-item]
-    queue.LifoQueue: (_T1, ),  # type: ignore[dict-item]
-    queue.SimpleQueue: (_T1, ),  # type: ignore[dict-item]
-    concurrent.futures.Future: (_T1, ),  # type: ignore[dict-item]
+    re.Pattern: (_AnyStrT, ),
+    re.Match: (_AnyStrT, ),
+    PathLike: (_AnyStr_co, ),
+    type: (_T1,),
+    list: (_T1,),
+    set: (_T1,),
+    frozenset: (_T1_co, ),
+    collections.Counter: (_T1,),
+    collections.deque: (_T1,),
+    dict: (_T1, _T2),
+    collections.defaultdict: (_T1, _T2),
+    collections.OrderedDict: (_T1, _T2),
+    collections.ChainMap: (_T1, _T2),
+    queue.Queue: (_T1, ),
+    queue.PriorityQueue: (_T1, ),
+    queue.LifoQueue: (_T1, ),
+    queue.SimpleQueue: (_T1, ),
+    concurrent.futures.Future: (_T1, ),
 }
diff --git a/src/dishka/_adaptix/type_tools/fundamentals.py b/src/dishka/_adaptix/type_tools/fundamentals.py
new file mode 100644
index 0000000..f83bfc3
--- /dev/null
+++ b/src/dishka/_adaptix/type_tools/fundamentals.py
@@ -0,0 +1,48 @@
+import types
+from typing import TypeVar, get_args, get_origin, get_type_hints
+
+from ..common import TypeHint, VarTuple
+from ..feature_requirement import HAS_ANNOTATED, HAS_SUPPORTED_PYDANTIC_PKG
+
+__all__ = ("is_pydantic_class", "strip_alias", "get_type_vars", "get_generic_args", "get_all_type_hints")
+
+
+if HAS_SUPPORTED_PYDANTIC_PKG:
+    from pydantic import BaseModel
+
+    _PYDANTIC_MCS = type(types.new_class("_PydanticSample", (BaseModel,), {}))
+
+    def is_pydantic_class(tp) -> bool:
+        return isinstance(tp, _PYDANTIC_MCS) and tp != BaseModel
+else:
+    def is_pydantic_class(tp) -> bool:
+        return False
+
+
+def strip_alias(tp: TypeHint) -> TypeHint:
+    origin = tp.__pydantic_generic_metadata__["origin"] if is_pydantic_class(tp) else get_origin(tp)
+    return tp if origin is None else origin
+
+
+def get_type_vars(tp: TypeHint) -> VarTuple[TypeVar]:
+    if is_pydantic_class(tp):
+        return tp.__pydantic_generic_metadata__["parameters"]
+
+    type_vars = getattr(tp, "__parameters__", ())
+    # UnionType object contains descriptor inside `__parameters__`
+    if not isinstance(type_vars, tuple):
+        return ()
+    return type_vars
+
+
+def get_generic_args(tp: TypeHint) -> VarTuple[TypeHint]:
+    if is_pydantic_class(tp):
+        return tp.__pydantic_generic_metadata__["args"]
+    return get_args(tp)
+
+
+if HAS_ANNOTATED:
+    def get_all_type_hints(obj, globalns=None, localns=None):
+        return get_type_hints(obj, globalns, localns, include_extras=True)
+else:
+    get_all_type_hints = get_type_hints
diff --git a/src/dishka/_adaptix/type_tools/generic_resolver.py b/src/dishka/_adaptix/type_tools/generic_resolver.py
index 0a07eb5..90233a3 100644
--- a/src/dishka/_adaptix/type_tools/generic_resolver.py
+++ b/src/dishka/_adaptix/type_tools/generic_resolver.py
@@ -1,16 +1,17 @@
 import typing
 from dataclasses import dataclass, replace
 from itertools import chain
-from typing import Callable, Collection, Dict, Generic, Hashable, Mapping, TypeVar, get_args
+from typing import Callable, Collection, Dict, Generic, Hashable, Mapping, TypeVar
 
 from ..common import TypeHint
 from ..feature_requirement import HAS_TV_TUPLE, HAS_UNPACK
+from . import get_generic_args
 from .basic_utils import get_type_vars, get_type_vars_of_parametrized, is_generic, is_parametrized, strip_alias
 from .implicit_params import fill_implicit_params
 from .normalize_type import normalize_type
 
-M = TypeVar('M')
-K = TypeVar('K', bound=Hashable)
+M = TypeVar("M")
+K = TypeVar("K", bound=Hashable)
 
 
 @dataclass
@@ -36,14 +37,14 @@ class GenericResolver(Generic[K, M]):
         members_storage = self._get_members_by_parents(origin)
         type_var_to_actual = self._get_type_var_to_actual(
             get_type_vars(origin),
-            self._unpack_args(get_args(parametrized_generic)),
+            self._unpack_args(get_generic_args(parametrized_generic)),
         )
         return replace(
             members_storage,
             members={
                 key: self._parametrize_by_dict(type_var_to_actual, tp)
                 for key, tp in members_storage.members.items()
-            }
+            },
         )
 
     def _unpack_args(self, args):
@@ -81,7 +82,7 @@ class GenericResolver(Generic[K, M]):
             for tp in members_storage.members.values()
         ):
             return members_storage
-        if not hasattr(tp, '__orig_bases__'):
+        if not hasattr(tp, "__orig_bases__"):
             return members_storage
 
         bases_members: Dict[K, TypeHint] = {}
diff --git a/src/dishka/_adaptix/type_tools/implicit_params.py b/src/dishka/_adaptix/type_tools/implicit_params.py
index de133c1..99f4fff 100644
--- a/src/dishka/_adaptix/type_tools/implicit_params.py
+++ b/src/dishka/_adaptix/type_tools/implicit_params.py
@@ -24,7 +24,7 @@ class ImplicitParamsGetter:
                 tuple(
                     self._process_limit_element(type_var, constraint)
                     for constraint in type_var.__constraints__
-                )
+                ),
             )
         if type_var.__bound__ is None:
             return Any
@@ -46,4 +46,4 @@ def fill_implicit_params(tp: TypeHint) -> TypeHint:
     params = ImplicitParamsGetter().get_implicit_params(strip_alias(tp))
     if params:
         return tp[params]
-    raise ValueError(f'Can not derive implicit parameters for {tp}')
+    raise ValueError(f"Can not derive implicit parameters for {tp}")
diff --git a/src/dishka/_adaptix/type_tools/norm_utils.py b/src/dishka/_adaptix/type_tools/norm_utils.py
index a23e1b6..4a41443 100644
--- a/src/dishka/_adaptix/type_tools/norm_utils.py
+++ b/src/dishka/_adaptix/type_tools/norm_utils.py
@@ -23,7 +23,7 @@ def strip_tags(norm: BaseNormType) -> BaseNormType:
     return norm
 
 
-N = TypeVar('N', bound=BaseNormType)
+N = TypeVar("N", bound=BaseNormType)
 
 
 def strip_annotated(value: N) -> N:
diff --git a/src/dishka/_adaptix/type_tools/normalize_type.py b/src/dishka/_adaptix/type_tools/normalize_type.py
index cc1f090..34e8b14 100644
--- a/src/dishka/_adaptix/type_tools/normalize_type.py
+++ b/src/dishka/_adaptix/type_tools/normalize_type.py
@@ -1,4 +1,4 @@
-# pylint: disable=inconsistent-return-statements,comparison-with-callable
+# ruff: noqa: RET503
 import dataclasses
 import sys
 import types
@@ -29,7 +29,6 @@ from typing import (
     Type,
     TypeVar,
     Union,
-    get_args,
     overload,
 )
 
@@ -49,6 +48,7 @@ from ..feature_requirement import (
     HAS_UNPACK,
 )
 from .basic_utils import create_union, eval_forward_ref, is_new_type, is_subclass_soft, strip_alias
+from .fundamentals import get_generic_args
 from .implicit_params import ImplicitParamsGetter
 
 
@@ -69,11 +69,11 @@ class BaseNormType(Hashable, ABC):
         ...
 
 
-T = TypeVar('T')
+T = TypeVar("T")
 
 
 class _BasicNormType(BaseNormType, ABC):
-    __slots__ = ('_source', '_args')
+    __slots__ = ("_source", "_args")
 
     def __init__(self, args: VarTuple[Any], *, source: TypeHint):
         self._source = source
@@ -103,11 +103,11 @@ class _BasicNormType(BaseNormType, ABC):
 
     def __repr__(self):
         args_str = f" {list(self.args)}," if self.args else ""
-        return f'<{type(self).__name__}({self.origin},{args_str} source={self._source})>'
+        return f"<{type(self).__name__}({self.origin},{args_str} source={self._source})>"
 
 
 class _NormType(_BasicNormType):
-    __slots__ = _BasicNormType.__slots__ + ('_source',)
+    __slots__ = (*_BasicNormType.__slots__, "_source")
 
     def __init__(self, origin: TypeHint, args: VarTuple[Any], *, source: TypeHint):
         self._origin = origin
@@ -177,7 +177,7 @@ class _AnnotatedNormType(_BasicNormType):
     def origin(self) -> Any:
         return typing.Annotated
 
-    __slots__ = _BasicNormType.__slots__ + ('_hash',)
+    __slots__ = (*_BasicNormType.__slots__, "_hash")
 
     def __init__(self, args: VarTuple[Hashable], *, source: TypeHint):
         super().__init__(args, source=source)
@@ -206,7 +206,7 @@ class Variance(Enum):
     INFERRED = 3
 
     def __repr__(self):
-        return f'{type(self).__name__}.{self.name}'
+        return f"{type(self).__name__}.{self.name}"
 
 
 @dataclass
@@ -219,11 +219,11 @@ class Constraints:
     value: VarTuple[BaseNormType]
 
 
-TypeVarLimit = Union[Bound, Constraints]  # pylint: disable=invalid-name
+TypeVarLimit = Union[Bound, Constraints]
 
 
 class NormTV(BaseNormType):
-    __slots__ = ('_var', '_limit', '_variance', '_source')
+    __slots__ = ("_var", "_limit", "_variance", "_source")
 
     def __init__(self, var: Any, limit: TypeVarLimit, *, source: TypeHint):
         self._var = var
@@ -234,7 +234,7 @@ class NormTV(BaseNormType):
             self._variance = Variance.COVARIANT
         if var.__contravariant__:
             self._variance = Variance.CONTRAVARIANT
-        if getattr(var, '__infer_variance__', False):
+        if getattr(var, "__infer_variance__", False):
             self._variance = Variance.INFERRED
         self._variance = Variance.INVARIANT
 
@@ -263,7 +263,7 @@ class NormTV(BaseNormType):
         return self._limit
 
     def __repr__(self):
-        return f'<{type(self).__name__}({self._var})>'
+        return f"<{type(self).__name__}({self._var})>"
 
     def __hash__(self):
         return hash(self._var)
@@ -277,7 +277,7 @@ class NormTV(BaseNormType):
 
 
 class NormTVTuple(BaseNormType):
-    __slots__ = ('_var', '_source')
+    __slots__ = ("_var", "_source")
 
     def __init__(self, var: Any, *, source: TypeHint):
         self._var = var
@@ -300,7 +300,7 @@ class NormTVTuple(BaseNormType):
         return self._var.__name__
 
     def __repr__(self):
-        return f'<{type(self).__name__}({self._var})>'
+        return f"<{type(self).__name__}({self._var})>"
 
     def __hash__(self):
         return hash(self._var)
@@ -314,7 +314,7 @@ class NormTVTuple(BaseNormType):
 
 
 class NormParamSpecMarker(BaseNormType, ABC):
-    __slots__ = ('_param_spec', '_source')
+    __slots__ = ("_param_spec", "_source")
 
     def __init__(self, param_spec: Any, *, source: TypeHint):
         self._param_spec = param_spec
@@ -359,7 +359,7 @@ AnyNormTypeVarLike = Union[NormTV, NormTVTuple]
 
 
 class NormTypeAlias(BaseNormType):
-    __slots__ = ('_type_alias', '_args', '_norm_type_vars')
+    __slots__ = ("_type_alias", "_args", "_norm_type_vars")
 
     def __init__(self, type_alias, args: VarTuple[BaseNormType], type_vars: VarTuple[AnyNormTypeVarLike]):
         self._type_alias = type_alias
@@ -404,11 +404,11 @@ class NormTypeAlias(BaseNormType):
 _PARAM_SPEC_MARKER_TYPES = (typing.ParamSpecArgs, typing.ParamSpecKwargs) if HAS_PARAM_SPEC else ()
 
 
-def make_norm_type(  # noqa: CCR001
+def make_norm_type(
     origin: TypeHint,
     args: VarTuple[Hashable],
     *,
-    source: TypeHint
+    source: TypeHint,
 ) -> BaseNormType:
     if origin == Union:
         if not all(isinstance(arg, BaseNormType) for arg in args):
@@ -468,7 +468,7 @@ def _replace_source_with_union(norm: BaseNormType, sources: list) -> BaseNormTyp
     )
 
 
-NormAspect = Callable[['TypeNormalizer', Any, Any, tuple], Optional[BaseNormType]]
+NormAspect = Callable[["TypeNormalizer", Any, Any, tuple], Optional[BaseNormType]]
 
 
 class AspectStorage(List[str]):
@@ -488,7 +488,7 @@ class AspectStorage(List[str]):
             self.append(func.__name__)
         return func
 
-    def copy(self) -> 'AspectStorage':
+    def copy(self) -> "AspectStorage":
         return type(self)(super().copy())
 
 
@@ -496,8 +496,8 @@ class NotSubscribedError(ValueError):
     pass
 
 
-N = TypeVar('N', bound=BaseNormType)
-TN = TypeVar('TN', bound='TypeNormalizer')
+N = TypeVar("N", bound=BaseNormType)
+TN = TypeVar("TN", bound="TypeNormalizer")
 
 
 class TypeNormalizer:
@@ -506,7 +506,6 @@ class TypeNormalizer:
         self._namespace: Optional[Dict[str, Any]] = None
 
     def _with_namespace(self: TN, namespace: Dict[str, Any]) -> TN:
-        # pylint: disable=protected-access
         self_copy = copy(self)
         self_copy._namespace = namespace
         return self_copy
@@ -528,7 +527,7 @@ class TypeNormalizer:
 
     def normalize(self, tp: TypeHint) -> BaseNormType:
         origin = strip_alias(tp)
-        args = get_args(tp)
+        args = get_generic_args(tp)
 
         result = self._norm_forward_ref(tp)
         if result is not None:
@@ -551,10 +550,10 @@ class TypeNormalizer:
             if isinstance(tp, ForwardRef):
                 return _replace_source(
                     self.normalize(eval_forward_ref(self._namespace, tp)),
-                    source=tp
+                    source=tp,
                 )
         elif isinstance(tp, (str, ForwardRef)):
-            raise ValueError(f'Can not normalize value {tp!r}, there are no namespace to evaluate types')
+            raise ValueError(f"Can not normalize value {tp!r}, there are no namespace to evaluate types")
 
     _aspect_storage = AspectStorage()
 
@@ -578,7 +577,7 @@ class TypeNormalizer:
             raise NotSubscribedError(f"{tp} must be subscribed")
 
         if tp in (NewType, TypeVar):
-            raise ValueError(f'{origin} must be instantiating')
+            raise ValueError(f"{origin} must be instantiated")
 
     @_aspect_storage.add
     def _norm_none(self, tp, origin, args):
@@ -590,7 +589,7 @@ class TypeNormalizer:
         if origin == typing.Annotated:
             return _AnnotatedNormType(
                 (self.normalize(args[0]), *args[1:]),
-                source=tp
+                source=tp,
             )
 
     def _get_bound(self, type_var) -> Bound:
@@ -602,16 +601,15 @@ class TypeNormalizer:
 
     @_aspect_storage.add
     def _norm_type_var(self, tp, origin, args):
-        # pylint: disable=protected-access
         if isinstance(origin, TypeVar):
             namespaced = self._with_module_namespace(origin.__module__)
             limit = (
                 Constraints(
                     tuple(
                         namespaced._dedup_union_args(
-                            namespaced._norm_iter(origin.__constraints__)
-                        )
-                    )
+                            namespaced._norm_iter(origin.__constraints__),
+                        ),
+                    ),
                 )
                 if origin.__constraints__ else
                 namespaced._get_bound(origin)
@@ -635,13 +633,13 @@ class TypeNormalizer:
             namespaced = self._with_module_namespace(origin.__module__)
             return NormTV(
                 var=origin,
-                limit=namespaced._get_bound(origin),  # pylint: disable=protected-access
+                limit=namespaced._get_bound(origin),
                 source=tp,
             )
 
     @_aspect_storage.add(condition=HAS_TV_SYNTAX)
     def _norm_type_alias_type(self, tp, origin, args):
-        if isinstance(origin, typing.TypeAliasType):  # pylint: disable=no-member
+        if isinstance(origin, typing.TypeAliasType):
             return NormTypeAlias(
                 origin,
                 self._norm_iter(args),
@@ -655,7 +653,7 @@ class TypeNormalizer:
             return _NormType(
                 InitVar,
                 (self.normalize(origin.type),),
-                source=tp
+                source=tp,
             )
 
     @_aspect_storage.add
@@ -670,7 +668,7 @@ class TypeNormalizer:
                 return _NormType(
                     tuple,
                     (ANY_NT, ...),
-                    source=tp
+                    source=tp,
                 )
 
             # >>> Tuple[()].__args__ == ((),)
@@ -715,7 +713,7 @@ class TypeNormalizer:
         if origin == c_abc.Callable:
             if not args:
                 return _NormType(
-                    c_abc.Callable, (..., ANY_NT), source=tp
+                    c_abc.Callable, (..., ANY_NT), source=tp,
                 )
 
             if args[0] is Ellipsis:
@@ -727,7 +725,7 @@ class TypeNormalizer:
             else:
                 call_args = normalize_type(args[0])
             return _NormType(
-                c_abc.Callable, (call_args, self.normalize(args[-1])), source=tp
+                c_abc.Callable, (call_args, self.normalize(args[-1])), source=tp,
             )
 
     @_aspect_storage.add
@@ -745,7 +743,7 @@ class TypeNormalizer:
                         _NormType(None, (), source=Literal[None]),
                         _create_norm_literal(args_without_none),
                     ),
-                    source=tp
+                    source=tp,
                 )
 
             return _LiteralNormType(args, source=tp)
@@ -813,7 +811,7 @@ class TypeNormalizer:
                         _NormType(type, (arg,), source=Type[arg.source])
                         for arg in norm.args
                     ),
-                    source=tp
+                    source=tp,
                 )
 
     ALLOWED_ZERO_PARAMS_ORIGINS = {Any, NoReturn}
@@ -854,7 +852,7 @@ class TypeNormalizer:
             or isinstance(origin, type)
             or origin in self.ALLOWED_ZERO_PARAMS_ORIGINS
         ):
-            raise ValueError(f'Can not normalize value {tp!r}')
+            raise ValueError(f"Can not normalize value {tp!r}")
 
         return _NormType(
             origin,
diff --git a/src/dishka/async_container.py b/src/dishka/async_container.py
index 9e2af07..c832ba1 100644
--- a/src/dishka/async_container.py
+++ b/src/dishka/async_container.py
@@ -126,14 +126,14 @@ class AsyncContainer:
             e.add_path(self.registry.get_factory(key))
             raise
 
-    async def close(self):
+    async def close(self, exception: Exception | None = None):
         errors = []
         for exit_generator in self._exits[::-1]:
             try:
                 if exit_generator.type is FactoryType.ASYNC_GENERATOR:
-                    await anext(exit_generator.callable)
+                    await exit_generator.callable.asend(exception)
                 elif exit_generator.type is FactoryType.GENERATOR:
-                    next(exit_generator.callable)
+                    exit_generator.callable.send(exception)
             except StopIteration:  # noqa: PERF203
                 pass
             except StopAsyncIteration:
@@ -142,7 +142,7 @@ class AsyncContainer:
                 errors.append(err)
         if self.close_parent:
             try:
-                await self.parent_container.close()
+                await self.parent_container.close(exception)
             except Exception as err:  # noqa: BLE001
                 errors.append(err)
         if errors:
@@ -157,7 +157,7 @@ class AsyncContextWrapper:
         return self.container
 
     async def __aexit__(self, exc_type, exc_val, exc_tb):
-        await self.container.close()
+        await self.container.close(exception=exc_val)
 
 
 def make_async_container(
diff --git a/src/dishka/container.py b/src/dishka/container.py
index 2952673..000c1fa 100644
--- a/src/dishka/container.py
+++ b/src/dishka/container.py
@@ -123,19 +123,19 @@ class Container:
             e.add_path(self.registry.get_factory(key))
             raise
 
-    def close(self) -> None:
+    def close(self, exception: Exception | None = None) -> None:
         errors = []
         for exit_generator in self._exits[::-1]:
             try:
                 if exit_generator.type is FactoryType.GENERATOR:
-                    next(exit_generator.callable)
+                    exit_generator.callable.send(exception)
             except StopIteration:  # noqa: PERF203
                 pass
             except Exception as err:  # noqa: BLE001
                 errors.append(err)
         if self.close_parent:
             try:
-                self.parent_container.close()
+                self.parent_container.close(exception)
             except Exception as err:  # noqa: BLE001
                 errors.append(err)
 
@@ -153,7 +153,7 @@ class ContextWrapper:
         return self.container
 
     def __exit__(self, exc_type, exc_val, exc_tb):
-        self.container.close()
+        self.container.close(exception=exc_val)
 
 
 def make_container(
diff --git a/src/dishka/dependency_source/__init__.py b/src/dishka/dependency_source/__init__.py
index 020913c..7dad04c 100644
--- a/src/dishka/dependency_source/__init__.py
+++ b/src/dishka/dependency_source/__init__.py
@@ -3,7 +3,8 @@ __all__ = [
     "alias", "Alias",
     "context_var", "ContextVariable", "from_context",
     "decorate", "Decorator",
-    "provide", "Factory", "FactoryType",
+    "provide", "provide_all",
+    "Factory", "FactoryType",
 ]
 
 from .alias import Alias
@@ -14,4 +15,4 @@ from .factory import Factory, FactoryType
 from .make_alias import alias
 from .make_context_var import from_context
 from .make_decorator import decorate
-from .make_factory import provide
+from .make_factory import provide, provide_all
diff --git a/src/dishka/dependency_source/make_factory.py b/src/dishka/dependency_source/make_factory.py
index 9e1537b..91efb63 100644
--- a/src/dishka/dependency_source/make_factory.py
+++ b/src/dishka/dependency_source/make_factory.py
@@ -19,6 +19,7 @@ from inspect import (
     unwrap,
 )
 from typing import (
+    Annotated,
     Any,
     Protocol,
     get_args,
@@ -28,16 +29,21 @@ from typing import (
 )
 
 from dishka._adaptix.type_tools.basic_utils import (
-    get_all_type_hints,
     get_type_vars,
     is_bare_generic,
     strip_alias,
 )
+from dishka._adaptix.type_tools.fundamentals import (
+    get_all_type_hints,
+)
 from dishka._adaptix.type_tools.generic_resolver import (
     GenericResolver,
     MembersStorage,
 )
-from dishka.entities.key import DependencyKey, hints_to_dependency_keys
+from dishka.entities.key import (
+    hint_to_dependency_key,
+    hints_to_dependency_keys,
+)
 from dishka.entities.scope import BaseScope
 from .composite import CompositeDependencySource, ensure_composite
 from .factory import Factory, FactoryType
@@ -177,6 +183,11 @@ def _make_factory_by_class(
         source: Callable,
         cache: bool,
 ) -> Factory:
+    if not provides:
+        provides = source
+
+    if get_origin(source) is Annotated:
+        source = get_args(source)[0]
     init = strip_alias(source).__init__
     if missing_hints := _params_without_hints(init, skip_self=True):
         name = f"{source.__module__}.{source.__qualname__}.__init__"
@@ -200,17 +211,16 @@ def _make_factory_by_class(
             f"Or, create a separate factory with all types imported.",
             name=e.name,
         ) from e
+
     hints.pop("return", _empty)
     dependencies = list(hints.values())
-    if not provides:
-        provides = source
 
     return Factory(
         dependencies=hints_to_dependency_keys(dependencies),
         type_=FactoryType.FACTORY,
         source=source,
         scope=scope,
-        provides=DependencyKey(provides, None),
+        provides=hint_to_dependency_key(provides),
         is_to_bind=False,
         cache=cache,
     )
@@ -272,7 +282,7 @@ def _make_factory_by_method(
         type_=factory_type,
         source=source,
         scope=scope,
-        provides=DependencyKey(provides, None),
+        provides=hint_to_dependency_key(provides),
         is_to_bind=is_in_class,
         cache=cache,
     )
@@ -322,7 +332,7 @@ def _make_factory_by_static_method(
         type_=factory_type,
         source=source,
         scope=scope,
-        provides=DependencyKey(provides, None),
+        provides=hint_to_dependency_key(provides),
         is_to_bind=False,
         cache=cache,
     )
@@ -487,5 +497,41 @@ def provide(
     return scoped
 
 
-def _context_stub():
-    raise NotImplementedError
+def _provide_all(
+        *,
+        provides: Sequence[Any],
+        scope: BaseScope | None,
+        cache: bool,
+        is_in_class: bool,
+) -> CompositeDependencySource:
+    composite = CompositeDependencySource(None)
+    for single_provides in provides:
+        factory = make_factory(
+            provides=single_provides, scope=scope,
+            source=single_provides, cache=cache,
+            is_in_class=is_in_class,
+        )
+        composite.dependency_sources.extend(unpack_factory(factory))
+    return composite
+
+
+def provide_all(
+        *provides: Any,
+        scope: BaseScope | None = None,
+        cache: bool = True,
+) -> CompositeDependencySource:
+    return _provide_all(
+        provides=provides, scope=scope,
+        cache=cache, is_in_class=True,
+    )
+
+
+def provide_all_on_instance(
+        *provides: Any,
+        scope: BaseScope | None = None,
+        cache: bool = True,
+) -> CompositeDependencySource:
+    return _provide_all(
+        provides=provides, scope=scope,
+        cache=cache, is_in_class=False,
+    )
diff --git a/src/dishka/factory_compiler.py b/src/dishka/factory_compiler.py
index 996b357..a7c3638 100644
--- a/src/dishka/factory_compiler.py
+++ b/src/dishka/factory_compiler.py
@@ -101,7 +101,7 @@ def compile_factory(*, factory: Factory, is_async: bool) -> CompiledFactory:
     names = {f"arg{i}": dep for i, dep in enumerate(factory.dependencies)}
     if is_async:
         async_ = "async "
-        await_ = "await"
+        await_ = "await "
         body_template = ASYNC_BODIES.get(factory.type, INVALID)
     else:
         async_ = ""
diff --git a/src/dishka/integrations/faststream.py b/src/dishka/integrations/faststream.py
index 1d352b6..02cfd84 100644
--- a/src/dishka/integrations/faststream.py
+++ b/src/dishka/integrations/faststream.py
@@ -1,49 +1,76 @@
 __all__ = (
     "FromDishka",
     "inject",
+    "FastStreamProvider",
     "setup_dishka",
 )
 
+import warnings
 from collections.abc import AsyncIterator, Awaitable, Callable
 from contextlib import asynccontextmanager
-from typing import Any, TypeVar
+from typing import Any
 
 from faststream import BaseMiddleware, FastStream, context
+from faststream.__about__ import __version__
+from faststream.broker.message import StreamMessage
 from faststream.types import DecodedMessage
+from faststream.utils.context import ContextRepo
 
-from dishka import AsyncContainer, FromDishka
+from dishka import AsyncContainer, FromDishka, Provider, Scope, from_context
 from dishka.integrations.base import wrap_injection
 
-T = TypeVar("T")
 
+class FastStreamProvider(Provider):
+    context = from_context(provides=ContextRepo, scope=Scope.REQUEST)
+    message = from_context(provides=StreamMessage, scope=Scope.REQUEST)
 
-def inject(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
-    return wrap_injection(
-        func=func,
-        container_getter=lambda *_: context.get_local("dishka"),
-        is_async=True,
-        remove_depends=True,
-    )
 
+FASTSTREAM_OLD_MIDDLEWARES = __version__ < "0.5"
 
-class DishkaMiddleware(BaseMiddleware):
+
+class _DishkaBaseMiddleware(BaseMiddleware):
     def __init__(self, container: AsyncContainer) -> None:
         self.container = container
 
-    def __call__(self, msg: Any | None = None) -> "DishkaMiddleware":
+    def __call__(self, msg: Any | None = None) -> "_DishkaBaseMiddleware":
         self.msg = msg
         return self
 
-    @asynccontextmanager
-    async def consume_scope(
-            self,
-            *args: Any,
-            **kwargs: Any,
-    ) -> AsyncIterator[DecodedMessage]:
-        async with self.container() as request_container:
-            with context.scope("dishka", request_container):
-                async with super().consume_scope(*args, **kwargs) as result:
-                    yield result
+
+if FASTSTREAM_OLD_MIDDLEWARES:
+
+    class DishkaMiddleware(_DishkaBaseMiddleware):
+        @asynccontextmanager
+        async def consume_scope(
+                self,
+                *args: Any,
+                **kwargs: Any,
+        ) -> AsyncIterator[DecodedMessage]:
+            async with self.container() as request_container:
+                with context.scope("dishka", request_container):
+                    async with super().consume_scope(
+                        *args,
+                        **kwargs,
+                    ) as result:
+                        yield result
+
+else:
+
+    class DishkaMiddleware(_DishkaBaseMiddleware):
+        async def consume_scope(
+                self,
+                call_next: Callable[[Any], Awaitable[Any]],
+                msg: StreamMessage[any],
+        ) -> AsyncIterator[DecodedMessage]:
+            async with self.container(
+                {
+                    StreamMessage: msg,
+                    type(msg): msg,
+                    ContextRepo: context,
+                },
+            ) as request_container:
+                with context.scope("dishka", request_container):
+                    return await call_next(msg)
 
 
 def setup_dishka(
@@ -51,13 +78,47 @@ def setup_dishka(
         app: FastStream,
         *,
         finalize_container: bool = True,
+        auto_inject: bool = False,
 ) -> None:
     assert app.broker, "You can't patch FastStream application without broker"  # noqa: S101
 
     if finalize_container:
         app.after_shutdown(container.close)
 
-    app.broker.middlewares = (
-        *app.broker.middlewares,
-        DishkaMiddleware(container),
+    if FASTSTREAM_OLD_MIDDLEWARES:
+        app.broker.middlewares = (
+            DishkaMiddleware(container),
+            *app.broker.middlewares,
+        )
+
+        if auto_inject:
+            warnings.warn(
+                """
+Auto injection is not supported for FastStream version less than 0.5.0
+Please, update your FastStream installation
+or use @inject at each subscriber manually.
+            """,
+                category=RuntimeWarning,
+                stacklevel=1,
+            )
+
+    else:
+        app.broker._middlewares = (  # noqa: SLF001
+            DishkaMiddleware(container),
+            *app.broker._middlewares,  # noqa: SLF001
+        )
+
+        if auto_inject:
+            app.broker._call_decorators = (  # noqa: SLF001
+                inject,
+                *app.broker._call_decorators,  # noqa: SLF001
+            )
+
+
+def inject(func):
+    return wrap_injection(
+        func=func,
+        container_getter=lambda *_: context.get_local("dishka"),
+        is_async=True,
+        remove_depends=True,
     )
diff --git a/src/dishka/integrations/sanic.py b/src/dishka/integrations/sanic.py
new file mode 100644
index 0000000..4caa77b
--- /dev/null
+++ b/src/dishka/integrations/sanic.py
@@ -0,0 +1,56 @@
+__all__ = [
+    "FromDishka",
+    "inject",
+    "setup_dishka",
+]
+
+from collections.abc import Awaitable, Callable, Iterable
+
+from sanic import HTTPResponse, Request, Sanic
+from sanic_routing import Route
+
+from dishka import AsyncContainer, FromDishka
+from dishka.integrations.base import is_dishka_injected, wrap_injection
+
+
+def inject(func: Callable) -> Awaitable:
+    return wrap_injection(
+        func=func,
+        remove_depends=True,
+        container_getter=lambda args, _: args[0].ctx.dishka_container,
+        is_async=True,
+    )
+
+
+class ContainerMiddleware:
+    def __init__(self, container: AsyncContainer) -> None:
+        self.container = container
+
+    async def on_request(self, request: Request) -> None:
+        request.ctx.container_wrapper = self.container({Request: request})
+        request.ctx.dishka_container = await request.ctx.container_wrapper.__aenter__()  # noqa: E501
+
+    async def on_response(self, request: Request, _: HTTPResponse) -> None:
+        await request.ctx.dishka_container.close()
+
+
+def _inject_routes(routes: Iterable[Route]) -> None:
+    for route in routes:
+        if not is_dishka_injected(route.handler):
+            route.handler = inject(route.handler)
+
+
+def setup_dishka(
+    container: AsyncContainer,
+    app: Sanic,
+    *,
+    auto_inject: bool = False,
+) -> None:
+    middleware = ContainerMiddleware(container)
+    app.on_request(middleware.on_request)
+    app.on_response(middleware.on_response)
+
+    if auto_inject:
+        _inject_routes(app.router.routes)
+        for blueprint in app.blueprints.values():
+            _inject_routes(blueprint.routes)
diff --git a/src/dishka/provider.py b/src/dishka/provider.py
index 79355ef..c3375b7 100644
--- a/src/dishka/provider.py
+++ b/src/dishka/provider.py
@@ -15,7 +15,10 @@ from .dependency_source import (
 )
 from .dependency_source.composite import CompositeDependencySource
 from .dependency_source.make_decorator import decorate_on_instance
-from .dependency_source.make_factory import provide_on_instance
+from .dependency_source.make_factory import (
+    provide_all_on_instance,
+    provide_on_instance,
+)
 
 
 def is_dependency_source(attribute: Any) -> bool:
@@ -141,6 +144,20 @@ class Provider(BaseProvider):
         self._add_dependency_sources(str(source), composite.dependency_sources)
         return composite
 
+    def provide_all(
+            self,
+            *provides: Any,
+            scope: BaseScope | None = None,
+            cache: bool = True,
+    ) -> CompositeDependencySource:
+        if scope is None:
+            scope = self.scope
+        composite = provide_all_on_instance(
+            *provides, scope=scope, cache=cache,
+        )
+        self._add_dependency_sources("?", composite.dependency_sources)
+        return composite
+
     def alias(
             self,
             *,
@@ -174,7 +191,7 @@ class Provider(BaseProvider):
 
     def from_context(
             self, *, provides: Any, scope: BaseScope,
-    ) -> ContextVariable:
+    ) -> CompositeDependencySource:
         composite = from_context(provides=provides, scope=scope)
         self._add_dependency_sources(str(provides),
                                      composite.dependency_sources)
diff --git a/src/dishka/registry.py b/src/dishka/registry.py
index 29c75bc..a072052 100644
--- a/src/dishka/registry.py
+++ b/src/dishka/registry.py
@@ -9,6 +9,7 @@ from .dependency_source import (
     ContextVariable,
     Decorator,
     Factory,
+    FactoryType,
 )
 from .entities.component import DEFAULT_COMPONENT, Component
 from .entities.key import DependencyKey
@@ -16,6 +17,7 @@ from .entities.scope import BaseScope, InvalidScopes
 from .exceptions import (
     CycleDependenciesError,
     GraphMissingFactoryError,
+    InvalidGraphError,
     NoFactoryError,
     UnknownScopeError,
 )
@@ -294,6 +296,10 @@ class RegistryBuilder:
         )
         self.decorator_depth[provides] += 1
         old_factory = registry.get_factory(provides)
+        if old_factory.type is FactoryType.CONTEXT:
+            raise InvalidGraphError(
+                f"Cannot apply decorator to context data {provides}",
+            )
         old_factory.provides = DependencyKey(
             undecorated_type, old_factory.provides.component,
         )
diff --git a/tox.ini b/tox.ini
index 1d94152..7168c20 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,9 +11,11 @@ env_list =
     aiogram-330,
     telebot-415,
     starlette-0270,
-    faststream-044,
+    faststream-047,
+    faststream-050,
     arq-0250
     taskiq-0110
+    sanic-23121
 
 [pytest]
 addopts = --cov=dishka --cov-append --cov-report term-missing -v
@@ -36,11 +38,14 @@ deps =
     starlette-latest: -r requirements/starlette-latest.txt
     starlette-0270: -r requirements/starlette-0270.txt
     faststream-latest: -r requirements/faststream-latest.txt
-    faststream-044: -r requirements/faststream-044.txt
+    faststream-047: -r requirements/faststream-047.txt
+    faststream-050: -r requirements/faststream-050.txt
     arq-latest: -r requirements/arq-latest.txt
     arq-0250: -r requirements/arq-0250.txt
     taskiq-latest: -r requirements/taskiq-latest.txt
     taskiq-0110: -r requirements/taskiq-0110.txt
+    sanic-latest: -r requirements/sanic-latest.txt
+    sanic-23121: -r requirements/sanic-23121.txt
 
 commands =
     aiohttp: pytest tests/integrations/aiohttp
@@ -53,6 +58,7 @@ commands =
     faststream: pytest tests/integrations/faststream
     arq: pytest tests/integrations/arq
     taskiq: pytest tests/integrations/taskiq
+    sanic: pytest tests/integrations/sanic
 
 package = editable
 


Test patch:
diff --git a/docs/advanced/testing/index.rst b/docs/advanced/testing/index.rst
index 9151b80..8a75415 100644
--- a/docs/advanced/testing/index.rst
+++ b/docs/advanced/testing/index.rst
@@ -24,7 +24,7 @@ And a container:
 
 .. literalinclude:: ./container_before.py
 
-First of all - split your application factory and and container setup.
+First of all - split your application factory and container setup.
 
 .. literalinclude:: ./app_factory.py
 
diff --git a/requirements/sanic-latest.txt b/requirements/sanic-latest.txt
new file mode 100644
index 0000000..463c3f1
--- /dev/null
+++ b/requirements/sanic-latest.txt
@@ -0,0 +1,4 @@
+-r asgi.txt
+sanic
+sanic-testing
+setuptools>=69.5.1
diff --git a/tests/integrations/faststream/__init__.py b/tests/integrations/faststream/__init__.py
index e69de29..d68342b 100644
--- a/tests/integrations/faststream/__init__.py
+++ b/tests/integrations/faststream/__init__.py
@@ -0,0 +1,3 @@
+import pytest
+
+pytest.importorskip("faststream")
diff --git a/tests/integrations/sanic/__init__.py b/tests/integrations/sanic/__init__.py
new file mode 100644
index 0000000..d6b6779
--- /dev/null
+++ b/tests/integrations/sanic/__init__.py
@@ -0,0 +1,3 @@
+import pytest
+
+pytest.importorskip("sanic")
diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py
new file mode 100644
index 0000000..c011727
--- /dev/null
+++ b/tests/integrations/sanic/test_sanic.py
@@ -0,0 +1,80 @@
+from contextlib import asynccontextmanager
+from typing import Annotated
+from unittest.mock import Mock
+
+import pytest
+from sanic import HTTPResponse, Request, Sanic
+
+from dishka import make_async_container
+from dishka.integrations.sanic import FromDishka, inject, setup_dishka
+from ..common import (
+    APP_DEP_VALUE,
+    REQUEST_DEP_VALUE,
+    AppDep,
+    AppProvider,
+    RequestDep,
+)
+
+
+@asynccontextmanager
+async def dishka_app(view, provider):
+    app = Sanic("test")
+    app.get("/")(inject(view))
+    container = make_async_container(provider)
+    setup_dishka(container, app)
+    yield app
+    await container.close()
+
+
+@asynccontextmanager
+async def dishka_auto_app(view, provider):
+    app = Sanic("test1")
+    app.get("/")(view)
+    container = make_async_container(provider)
+    setup_dishka(container, app, auto_inject=True)
+    yield app
+    await container.close()
+
+
+async def get_with_app(
+    _: Request,
+    a: FromDishka[AppDep],
+    mock: FromDishka[Mock],
+) -> None:
+    mock(a)
+    return HTTPResponse(status=200)
+
+
+@pytest.mark.parametrize(
+    "app_factory",
+    [
+        dishka_app,
+        dishka_auto_app,
+    ],
+)
+@pytest.mark.asyncio
+async def test_app_dependency(app_provider: AppProvider, app_factory):
+    async with app_factory(get_with_app, app_provider) as app:
+        _, response = await app.asgi_client.get("/")
+        assert response.status_code == 200
+        app_provider.mock.assert_called_with(APP_DEP_VALUE)
+        app_provider.app_released.assert_not_called()
+    app_provider.app_released.assert_called()
+
+
+async def get_compat(
+    _: Request,
+    a: Annotated[RequestDep, FromDishka()],
+    mock: Annotated[Mock, FromDishka()],
+) -> HTTPResponse:
+    mock(a)
+    return HTTPResponse(status=200)
+
+
+@pytest.mark.asyncio
+async def test_compat(app_provider: AppProvider):
+    async with dishka_app(get_compat, app_provider) as app:
+        _, response = await app.asgi_client.get("/")
+        assert response.status_code == 200
+        app_provider.mock.assert_called_with(REQUEST_DEP_VALUE)
+        app_provider.request_released.assert_called_once()
diff --git a/tests/unit/container/test_components.py b/tests/unit/container/test_components.py
index 492c7a0..25022d3 100644
--- a/tests/unit/container/test_components.py
+++ b/tests/unit/container/test_components.py
@@ -214,3 +214,24 @@ def test_decorator():
     container = make_container(provider, x_provider, dec_provider)
     assert container.get(int, component="X") == 2
     assert container.get(int) == 100
+
+
+class ResultProvider(Provider):
+    scope = Scope.APP
+    component = "X"
+
+    @provide
+    def foo(self) -> int:
+        return 42
+
+    @provide
+    def bar(self, x: int) -> Annotated[float, FromComponent()]:
+        return x
+
+
+def test_result_component():
+    container = make_container(ResultProvider())
+    assert container.get(float) == 42
+
+    container = make_container(ResultProvider().to_component("YYY"))
+    assert container.get(float) == 42
diff --git a/tests/unit/container/test_context_vars.py b/tests/unit/container/test_context_vars.py
index 664fb12..0311398 100644
--- a/tests/unit/container/test_context_vars.py
+++ b/tests/unit/container/test_context_vars.py
@@ -3,12 +3,13 @@ import pytest
 from dishka import (
     Provider,
     Scope,
+    decorate,
     make_async_container,
     make_container,
     provide,
 )
 from dishka.dependency_source import from_context
-from dishka.exceptions import NoContextValueError
+from dishka.exceptions import InvalidGraphError, NoContextValueError
 
 
 def test_simple():
@@ -61,10 +62,6 @@ async def test_2components():
 
 @pytest.mark.asyncio
 async def test_2components_factory():
-    class DefaultProvider(Provider):
-        scope = Scope.APP
-        a = from_context(provides=int)
-
     class MyProvider(Provider):
         scope = Scope.APP
         component = "XXX"
@@ -79,3 +76,17 @@ async def test_2components_factory():
 
     container = make_async_container(MyProvider(), context={int: 1})
     assert await container.get(float, component="XXX") == 100
+
+
+def test_decorate():
+    class MyProvider(Provider):
+        scope = Scope.APP
+
+        i = from_context(provides=int)
+
+        @decorate
+        def ii(self, i: int) -> int:
+            return i + 1
+
+    with pytest.raises(InvalidGraphError):
+        make_container(MyProvider(), context={int: 1})
diff --git a/tests/unit/container/test_enter_exit.py b/tests/unit/container/test_enter_exit.py
index ce78b28..b28d1ab 100644
--- a/tests/unit/container/test_enter_exit.py
+++ b/tests/unit/container/test_enter_exit.py
@@ -1,3 +1,6 @@
+from collections.abc import AsyncGenerator, Generator
+from unittest.mock import Mock
+
 import pytest
 
 from dishka import (
@@ -9,6 +12,7 @@ from dishka import (
 )
 from ..sample_providers import (
     ClassA,
+    async_gen_a,
     sync_gen_a,
 )
 
@@ -90,6 +94,45 @@ def test_sync_enter(provide_scope, start_scope, expected_scope):
     assert a.closed
 
 
+@pytest.mark.parametrize(
+    ("provide_scope", "start_scope", "expected_scope"),
+    [
+        (Scope.SESSION, Scope.REQUEST, Scope.REQUEST),
+        (Scope.REQUEST, Scope.REQUEST, Scope.REQUEST),
+        (Scope.REQUEST, Scope.STEP, Scope.STEP),
+        (Scope.REQUEST, None, Scope.REQUEST),
+    ],
+)
+def test_error_in_sync_enter(provide_scope, start_scope, expected_scope):
+    exception = ValueError("Some exception")
+    mock = Mock()
+
+    class MyProvider(Provider):
+        a = provide(sync_gen_a, scope=provide_scope)
+
+        @provide(scope=provide_scope)
+        def get_int(self) -> Generator[None, int, None]:
+            caught_exc = yield 100
+            mock(caught_exc)
+
+    base_container = make_container(MyProvider())
+
+    try:
+        with base_container(scope=start_scope) as container:
+            assert container.registry.scope is expected_scope
+            a = container.get(ClassA)
+            assert a
+            assert a.dep == 100
+
+            raise exception
+    except ValueError as exc:
+        if exc != exception:
+            raise
+
+    mock.assert_called_with(exception)
+    assert a.closed
+
+
 @pytest.mark.parametrize(
     ("provide_scope", "start_scope", "expected_scope"),
     [
@@ -117,6 +160,94 @@ async def test_async_enter(provide_scope, start_scope, expected_scope):
     assert a.closed
 
 
+@pytest.mark.parametrize(
+    ("provide_scope", "start_scope", "expected_scope"),
+    [
+        (Scope.SESSION, Scope.REQUEST, Scope.REQUEST),
+        (Scope.REQUEST, Scope.REQUEST, Scope.REQUEST),
+        (Scope.REQUEST, Scope.STEP, Scope.STEP),
+        (Scope.REQUEST, None, Scope.REQUEST),
+    ],
+)
+@pytest.mark.asyncio
+async def test_error_in_async_enter(
+    provide_scope,
+    start_scope,
+    expected_scope,
+):
+    exception = ValueError("Some exception")
+    mock = Mock()
+
+    class MyProvider(Provider):
+        a = provide(sync_gen_a, scope=provide_scope)
+
+        @provide(scope=provide_scope)
+        def get_int(self) -> Generator[None, int, None]:
+            caught_exc = yield 100
+            mock(caught_exc)
+
+    base_container = make_async_container(MyProvider())
+
+    try:
+        async with base_container(scope=start_scope) as container:
+            assert container.registry.scope is expected_scope
+            a = await container.get(ClassA)
+            assert a
+            assert a.dep == 100
+
+            raise exception
+    except ValueError as exc:
+        if exc != exception:
+            raise
+
+    mock.assert_called_with(exception)
+    assert a.closed
+
+
+@pytest.mark.parametrize(
+    ("provide_scope", "start_scope", "expected_scope"),
+    [
+        (Scope.SESSION, Scope.REQUEST, Scope.REQUEST),
+        (Scope.REQUEST, Scope.REQUEST, Scope.REQUEST),
+        (Scope.REQUEST, Scope.STEP, Scope.STEP),
+        (Scope.REQUEST, None, Scope.REQUEST),
+    ],
+)
+@pytest.mark.asyncio
+async def test_error_in_async_enter_with_async_gen(
+    provide_scope,
+    start_scope,
+    expected_scope,
+):
+    exception = ValueError("Some exception")
+    mock = Mock()
+
+    class MyProvider(Provider):
+        a = provide(async_gen_a, scope=provide_scope)
+
+        @provide(scope=provide_scope)
+        async def get_int(self) -> AsyncGenerator[int, None]:
+            caught_exc = yield 100
+            mock(caught_exc)
+
+    base_container = make_async_container(MyProvider())
+
+    try:
+        async with base_container(scope=start_scope) as container:
+            assert container.registry.scope is expected_scope
+            a = await container.get(ClassA)
+            assert a
+            assert a.dep == 100
+
+            raise exception
+    except ValueError as exc:
+        if exc != exception:
+            raise
+
+    mock.assert_called_with(exception)
+    assert a.closed
+
+
 def test_sync_exit_one():
     class MyProvider(Provider):
         a = provide(sync_gen_a, scope=Scope.RUNTIME)
diff --git a/tests/unit/test_provider.py b/tests/unit/test_provider.py
index d698670..f788642 100644
--- a/tests/unit/test_provider.py
+++ b/tests/unit/test_provider.py
@@ -4,7 +4,7 @@ import pytest
 
 from dishka import Provider, Scope, alias, provide
 from dishka.dependency_source import FactoryType
-from dishka.dependency_source.make_factory import make_factory
+from dishka.dependency_source.make_factory import make_factory, provide_all
 from dishka.entities.key import (
     hint_to_dependency_key,
     hints_to_dependency_keys,
@@ -247,3 +247,29 @@ def test_provide_protocol_impl():
     factory = impl.dependency_sources[0]
     assert factory.provides == hint_to_dependency_key(MyImpl)
     assert factory.dependencies == []
+
+
+class A:
+    pass
+
+
+class B:
+    pass
+
+
+def test_provide_all_cls():
+    class MyProvider(Provider):
+        x = provide_all(A, B)
+
+    provider = MyProvider(scope=Scope.APP)
+    assert len(provider.factories) == 2
+    provides = [f.provides.type_hint for f in provider.factories]
+    assert provides == [A, B]
+
+
+def test_provide_all_instance():
+    provider = Provider(scope=Scope.APP)
+    provider.provide_all(A, B)
+    assert len(provider.factories) == 2
+    provides = [f.provides.type_hint for f in provider.factories]
+    assert provides == [A, B]