From a5b959f72437d3795ace8b8830a5cd3324d6534e Mon Sep 17 00:00:00 2001 From: euri10 Date: Wed, 16 Apr 2025 09:47:04 +0200 Subject: [PATCH 1/8] add litestar plugin to integration --- src/dishka/integrations/litestar.py | 48 +++++++++++++ tests/integrations/litestar/test_litestar.py | 71 ++++++++++++++------ 2 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/dishka/integrations/litestar.py b/src/dishka/integrations/litestar.py index c533efd2f..802a37b74 100644 --- a/src/dishka/integrations/litestar.py +++ b/src/dishka/integrations/litestar.py @@ -1,4 +1,6 @@ __all__ = [ + "DishkaMiddleware", + "DishkaPlugin", "FromDishka", "LitestarProvider", "inject", @@ -11,7 +13,10 @@ from typing import ParamSpec, TypeVar, get_type_hints from litestar import Litestar, Request, WebSocket +from litestar.config.app import AppConfig from litestar.enums import ScopeType +from litestar.middleware import ASGIMiddleware +from litestar.plugins import InitPlugin from litestar.types import ASGIApp, Receive, Scope, Send from dishka import AsyncContainer, FromDishka, Provider, from_context @@ -94,3 +99,46 @@ def setup_dishka(container: AsyncContainer, app: Litestar) -> None: app.asgi_handler, ) app.state.dishka_container = container + + +class DishkaMiddleware(ASGIMiddleware): + scopes = (ScopeType.HTTP, ScopeType.WEBSOCKET) + async def handle( + self, scope: Scope, receive: Receive, + send: Send, next_app: ASGIApp, + ) -> None: + if scope.get("type") not in (ScopeType.HTTP, ScopeType.WEBSOCKET): + await next_app(scope, receive, send) + return + + if scope.get("type") == ScopeType.HTTP: + request: Request = Request(scope) + context = {Request: request} + di_scope = DIScope.REQUEST + async with request.app.state.dishka_container( + context, + scope=di_scope, + ) as request_container: + request.state.dishka_container = request_container + await next_app(scope, receive, send) + + elif scope.get("type") == ScopeType.WEBSOCKET: + websocket: WebSocket = WebSocket(scope) + context = {WebSocket: websocket} + di_scope = DIScope.SESSION + async with websocket.app.state.dishka_container( + context, + scope=di_scope, + ) as request_container: + websocket.state.dishka_container = request_container + await next_app(scope, receive, send) + + +class DishkaPlugin(InitPlugin): + def __init__(self, container: AsyncContainer): + self.container = container + + def on_app_init(self, app_config: AppConfig) -> AppConfig: + app_config.state.dishka_container = self.container + app_config.middleware.append(DishkaMiddleware()) + return app_config diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index a1fcb0b22..b300cf4b9 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -1,15 +1,16 @@ +from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from unittest.mock import Mock import litestar import pytest -from asgi_lifespan import LifespanManager from litestar import Request, get, websocket_listener from litestar.contrib.htmx.request import HTMXRequest -from litestar.testing import TestClient +from litestar.testing import AsyncTestClient from dishka import make_async_container from dishka.integrations.litestar import ( + DishkaPlugin, FromDishka, inject, setup_dishka, @@ -24,21 +25,37 @@ @asynccontextmanager -async def dishka_app( +async def dishka_app_via_setup( view, provider, request_class: type[Request] = Request, -) -> TestClient: - app = litestar.Litestar(request_class=request_class) +) -> AsyncGenerator[AsyncTestClient]: + app = litestar.Litestar(request_class=request_class, debug=True) app.register(get("/")(inject(view))) app.register(websocket_listener("/ws")(websocket_handler)) container = make_async_container(provider) setup_dishka(container, app) - async with LifespanManager(app): - yield litestar.testing.TestClient(app) + async with AsyncTestClient(app) as client: + yield client + await container.close() + +@asynccontextmanager +async def dishka_app_via_plugin( + view, + provider, + request_class: type[Request] = Request, +) -> AsyncGenerator[AsyncTestClient]: + container = make_async_container(provider) + app = litestar.Litestar(request_class=request_class, + plugins=[DishkaPlugin(container=container)]) + app.register(get("/")(inject(view))) + app.register(websocket_listener("/ws")(websocket_handler)) + async with AsyncTestClient(app) as client: + yield client await container.close() + async def websocket_handler(data: str): pass @@ -65,53 +82,67 @@ async def handler( return handler + +@pytest.mark.parametrize("client_setup", [dishka_app_via_setup, + dishka_app_via_plugin]) @pytest.mark.parametrize("request_class", [Request, HTMXRequest]) @pytest.mark.asyncio -async def test_app_dependency(request_class, app_provider: AppProvider): - async with dishka_app(get_with_app(request_class), app_provider) as client: - client.get("/") +async def test_app_dependency(request_class, app_provider: AppProvider, + client_setup: AsyncTestClient) -> None: + async with client_setup(get_with_app(request_class), + app_provider) as client: + await client.get("/") app_provider.mock.assert_called_with(APP_DEP_VALUE) app_provider.app_released.assert_not_called() app_provider.app_released.assert_called() +@pytest.mark.parametrize("client_setup", [dishka_app_via_setup, + dishka_app_via_plugin]) @pytest.mark.parametrize("request_class", [Request, HTMXRequest]) @pytest.mark.asyncio -async def test_request_dependency(request_class, app_provider: AppProvider): - async with dishka_app( +async def test_request_dependency(request_class, app_provider: AppProvider, + client_setup: AsyncTestClient) -> None: + async with client_setup( get_with_request(request_class), app_provider, request_class, ) as client: - client.get("/") + await client.get("/") app_provider.mock.assert_called_with(REQUEST_DEP_VALUE) app_provider.request_released.assert_called_once() +@pytest.mark.parametrize("client_setup", [dishka_app_via_setup, + dishka_app_via_plugin]) @pytest.mark.parametrize("request_class", [Request, HTMXRequest]) @pytest.mark.asyncio -async def test_request_dependency2(request_class, app_provider: AppProvider): - async with dishka_app( +async def test_request_dependency2(request_class, app_provider: AppProvider, + client_setup: AsyncTestClient) -> None: + async with client_setup( get_with_request(request_class), app_provider, request_class, ) as client: - client.get("/") + await client.get("/") app_provider.mock.assert_called_with(REQUEST_DEP_VALUE) app_provider.mock.reset_mock() app_provider.request_released.assert_called_once() app_provider.request_released.reset_mock() - client.get("/") + await client.get("/") app_provider.mock.assert_called_with(REQUEST_DEP_VALUE) app_provider.request_released.assert_called_once() +@pytest.mark.parametrize("client_setup", [dishka_app_via_setup, + dishka_app_via_plugin]) @pytest.mark.asyncio -async def test_request_middleware(app_provider: AppProvider): - async with dishka_app( +async def test_request_middleware(app_provider: AppProvider, + client_setup: AsyncTestClient) -> None: + async with client_setup( get_with_request(Request), app_provider, Request, ) as client: - with client.websocket_connect("/ws") as websocket: + with await client.websocket_connect("/ws") as websocket: websocket.send("test") From 4481d34ba50b78e3a25f87c829dc70b83b22a2b8 Mon Sep 17 00:00:00 2001 From: euri10 Date: Thu, 24 Apr 2025 16:38:56 +0200 Subject: [PATCH 2/8] ws --- .../litestar/test_litestar_websockets.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/integrations/litestar/test_litestar_websockets.py b/tests/integrations/litestar/test_litestar_websockets.py index 197aa1d51..6bbdd7cfc 100644 --- a/tests/integrations/litestar/test_litestar_websockets.py +++ b/tests/integrations/litestar/test_litestar_websockets.py @@ -3,7 +3,6 @@ from unittest.mock import Mock import pytest -from asgi_lifespan import LifespanManager from litestar import Litestar, websocket_listener from litestar.handlers import WebsocketListener from litestar.testing import TestClient @@ -12,7 +11,7 @@ from dishka.integrations.litestar import ( FromDishka, inject_websocket, - setup_dishka, + setup_dishka, DishkaPlugin, ) from ..common import ( APP_DEP_VALUE, @@ -26,14 +25,21 @@ @asynccontextmanager -async def dishka_app(view, provider) -> AsyncGenerator[TestClient, None]: +async def dishka_app_via_setup(view, provider) -> AsyncGenerator[TestClient, None]: app = Litestar([view], debug=True) container = make_async_container(provider) setup_dishka(container, app) - async with LifespanManager(app): - yield TestClient(app) + with TestClient(app) as client: + yield client await container.close() +@asynccontextmanager +async def dishka_app_via_plugin(view, provider) -> AsyncGenerator[TestClient, None]: + container = make_async_container(provider) + app = Litestar([view], debug=True, plugins=[DishkaPlugin(container=container)]) + with TestClient(app) as client: + yield client + await container.close() @websocket_listener("/") @inject_websocket @@ -61,9 +67,10 @@ async def on_receive( @pytest.mark.asyncio +@pytest.mark.parametrize("setup_client", [dishka_app_via_setup, dishka_app_via_plugin]) @pytest.mark.parametrize("view", [get_with_app, GetWithApp]) -async def test_app_dependency(view, ws_app_provider: WebSocketAppProvider): - async with dishka_app(view, ws_app_provider) as client: +async def test_app_dependency(view, ws_app_provider: WebSocketAppProvider, setup_client: TestClient) -> None: + async with setup_client(view, ws_app_provider) as client: with client.websocket_connect("/") as connection: connection.send_text("...") assert connection.receive_text() == "passed" @@ -104,7 +111,7 @@ async def test_request_dependency( view, ws_app_provider: WebSocketAppProvider, ): - async with dishka_app(view, ws_app_provider) as client: + async with dishka_app_via_setup(view, ws_app_provider) as client: with client.websocket_connect("/") as connection: connection.send_text("...") assert connection.receive_text() == "passed" @@ -118,7 +125,7 @@ async def test_request_dependency2( view, ws_app_provider: WebSocketAppProvider, ): - async with dishka_app(view, ws_app_provider) as client: + async with dishka_app_via_setup(view, ws_app_provider) as client: with client.websocket_connect("/") as connection: connection.send_text("...") assert connection.receive_text() == "passed" @@ -165,7 +172,7 @@ async def test_websocket_dependency( view, ws_app_provider: WebSocketAppProvider, ): - async with dishka_app(view, ws_app_provider) as client: + async with dishka_app_via_setup(view, ws_app_provider) as client: with client.websocket_connect("/") as connection: connection.send_text("...") assert connection.receive_text() == "passed" From 0fe50132536bf6383108132cd85f1c8df5a0d5b1 Mon Sep 17 00:00:00 2001 From: euri10 Date: Tue, 6 May 2025 19:11:10 +0200 Subject: [PATCH 3/8] corrected typing of ws and request contexts --- src/dishka/integrations/litestar.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dishka/integrations/litestar.py b/src/dishka/integrations/litestar.py index 802a37b74..dcc85c050 100644 --- a/src/dishka/integrations/litestar.py +++ b/src/dishka/integrations/litestar.py @@ -113,10 +113,10 @@ async def handle( if scope.get("type") == ScopeType.HTTP: request: Request = Request(scope) - context = {Request: request} + r_context = {Request: request} di_scope = DIScope.REQUEST async with request.app.state.dishka_container( - context, + r_context, scope=di_scope, ) as request_container: request.state.dishka_container = request_container @@ -124,10 +124,10 @@ async def handle( elif scope.get("type") == ScopeType.WEBSOCKET: websocket: WebSocket = WebSocket(scope) - context = {WebSocket: websocket} + w_context = {WebSocket: websocket} di_scope = DIScope.SESSION async with websocket.app.state.dishka_container( - context, + w_context, scope=di_scope, ) as request_container: websocket.state.dishka_container = request_container From 51db172ceb777a556f8ffaac8164b257c238213d Mon Sep 17 00:00:00 2001 From: euri10 Date: Tue, 13 May 2025 16:17:40 +0200 Subject: [PATCH 4/8] revert test to develop state, ***ed up the merge --- tests/integrations/litestar/test_litestar.py | 34 ++++++------------- .../litestar/test_litestar_websockets.py | 16 +++------ 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 959cecc49..abc9a2816 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -1,16 +1,15 @@ -from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from unittest.mock import Mock import litestar import pytest +from asgi_lifespan import LifespanManager from litestar import Request, get, websocket_listener from litestar.contrib.htmx.request import HTMXRequest -from litestar.testing import AsyncTestClient +from litestar.testing import TestClient from dishka import make_async_container from dishka.integrations.litestar import ( - DishkaPlugin, DishkaRouter, FromDishka, inject, @@ -26,12 +25,12 @@ @asynccontextmanager -async def dishka_app_via_setup( +async def dishka_app( view, provider, request_class: type[Request] = Request, -) -> AsyncGenerator[AsyncTestClient]: - app = litestar.Litestar(request_class=request_class, debug=True) +) -> TestClient: + app = litestar.Litestar(request_class=request_class) app.register(get("/")(inject(view))) app.register(websocket_listener("/ws")(websocket_handler)) container = make_async_container(provider) @@ -58,7 +57,6 @@ async def dishka_auto_app( await container.close() - async def websocket_handler(data: str): pass @@ -85,9 +83,6 @@ async def handler( return handler - -@pytest.mark.parametrize("client_setup", [dishka_app_via_setup, - dishka_app_via_plugin]) @pytest.mark.parametrize( ("request_class", "app_factory"), [ @@ -114,8 +109,6 @@ async def test_app_dependency( app_provider.app_released.assert_called() -@pytest.mark.parametrize("client_setup", [dishka_app_via_setup, - dishka_app_via_plugin]) @pytest.mark.parametrize( ("request_class", "app_factory"), [ @@ -136,13 +129,11 @@ async def test_request_dependency( app_provider, request_class, ) as client: - await client.get("/") + client.get("/") app_provider.mock.assert_called_with(REQUEST_DEP_VALUE) app_provider.request_released.assert_called_once() -@pytest.mark.parametrize("client_setup", [dishka_app_via_setup, - dishka_app_via_plugin]) @pytest.mark.parametrize( ("request_class", "app_factory"), [ @@ -163,22 +154,19 @@ async def test_request_dependency2( app_provider, request_class, ) as client: - await client.get("/") + client.get("/") app_provider.mock.assert_called_with(REQUEST_DEP_VALUE) app_provider.mock.reset_mock() app_provider.request_released.assert_called_once() app_provider.request_released.reset_mock() - await client.get("/") + client.get("/") app_provider.mock.assert_called_with(REQUEST_DEP_VALUE) app_provider.request_released.assert_called_once() -@pytest.mark.parametrize("client_setup", [dishka_app_via_setup, - dishka_app_via_plugin]) @pytest.mark.asyncio -async def test_request_middleware(app_provider: AppProvider, - client_setup: AsyncTestClient) -> None: - async with client_setup( +async def test_request_middleware(app_provider: AppProvider): + async with dishka_app( get_with_request(Request), app_provider, Request, @@ -194,5 +182,5 @@ async def test_auto_request_middleware(app_provider: AppProvider): app_provider, Request, ) as client: - with await client.websocket_connect("/ws") as websocket: + with client.websocket_connect("/ws") as websocket: websocket.send("test") diff --git a/tests/integrations/litestar/test_litestar_websockets.py b/tests/integrations/litestar/test_litestar_websockets.py index e26e7e453..f7c398a72 100644 --- a/tests/integrations/litestar/test_litestar_websockets.py +++ b/tests/integrations/litestar/test_litestar_websockets.py @@ -3,6 +3,7 @@ from unittest.mock import Mock import pytest +from asgi_lifespan import LifespanManager from litestar import Litestar, websocket_listener from litestar.handlers import WebsocketListener from litestar.testing import TestClient @@ -12,7 +13,7 @@ DishkaRouter, FromDishka, inject_websocket, - setup_dishka, DishkaPlugin, + setup_dishka, ) from ..common import ( APP_DEP_VALUE, @@ -26,21 +27,14 @@ @asynccontextmanager -async def dishka_app_via_setup(view, provider) -> AsyncGenerator[TestClient, None]: +async def dishka_app(view, provider) -> AsyncGenerator[TestClient, None]: app = Litestar([view], debug=True) container = make_async_container(provider) setup_dishka(container, app) - with TestClient(app) as client: - yield client + async with LifespanManager(app): + yield TestClient(app) await container.close() -@asynccontextmanager -async def dishka_app_via_plugin(view, provider) -> AsyncGenerator[TestClient, None]: - container = make_async_container(provider) - app = Litestar([view], debug=True, plugins=[DishkaPlugin(container=container)]) - with TestClient(app) as client: - yield client - await container.close() @asynccontextmanager async def dishka_auto_app(view, provider) -> AsyncGenerator[TestClient, None]: From 5dcb29f3574101b4795a47c51ffa986c433335cb Mon Sep 17 00:00:00 2001 From: euri10 Date: Wed, 14 May 2025 08:28:04 +0200 Subject: [PATCH 5/8] add tests --- src/dishka/integrations/litestar.py | 5 ++--- tests/integrations/litestar/test_litestar.py | 21 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/dishka/integrations/litestar.py b/src/dishka/integrations/litestar.py index e02703e72..09a127a27 100644 --- a/src/dishka/integrations/litestar.py +++ b/src/dishka/integrations/litestar.py @@ -16,9 +16,6 @@ from litestar import Controller, Litestar, Request, Router, WebSocket from litestar.config.app import AppConfig from litestar.enums import ScopeType -from litestar.middleware import ASGIMiddleware -from litestar.plugins import InitPlugin -from litestar.types import ASGIApp, Receive, Scope, Send from litestar.handlers import ( BaseRouteHandler, HTTPRouteHandler, @@ -26,6 +23,8 @@ ) from litestar.handlers.websocket_handlers import WebsocketListenerRouteHandler from litestar.handlers.websocket_handlers._utils import ListenerHandler +from litestar.middleware import ASGIMiddleware +from litestar.plugins import InitPlugin from litestar.routes import BaseRoute from litestar.types import ( ASGIApp, diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index abc9a2816..ab5e5bcc5 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -10,6 +10,7 @@ from dishka import make_async_container from dishka.integrations.litestar import ( + DishkaPlugin, DishkaRouter, FromDishka, inject, @@ -39,6 +40,20 @@ async def dishka_app( yield TestClient(app) await container.close() +@asynccontextmanager +async def dishka_plugin_app( + view, + provider, + request_class: type[Request] = Request, +) -> TestClient: + container = make_async_container(provider) + app = litestar.Litestar(request_class=request_class, + plugins=[DishkaPlugin(container)]) + app.register(get("/")(inject(view))) + app.register(websocket_listener("/ws")(websocket_handler)) + async with LifespanManager(app): + yield TestClient(app) + await container.close() @asynccontextmanager async def dishka_auto_app( @@ -90,6 +105,8 @@ async def handler( (HTMXRequest, dishka_app), (Request, dishka_auto_app), (HTMXRequest, dishka_auto_app), + (Request, dishka_plugin_app), + (HTMXRequest, dishka_plugin_app), ], ) @pytest.mark.asyncio @@ -116,6 +133,8 @@ async def test_app_dependency( (HTMXRequest, dishka_app), (Request, dishka_auto_app), (HTMXRequest, dishka_auto_app), + (Request, dishka_plugin_app), + (HTMXRequest, dishka_plugin_app), ], ) @pytest.mark.asyncio @@ -141,6 +160,8 @@ async def test_request_dependency( (HTMXRequest, dishka_app), (Request, dishka_auto_app), (HTMXRequest, dishka_auto_app), + (Request, dishka_plugin_app), + (HTMXRequest, dishka_plugin_app), ], ) @pytest.mark.asyncio From 6ac9df96cfa9120636c2bdf62413f3fe94417aa4 Mon Sep 17 00:00:00 2001 From: euri10 Date: Wed, 14 May 2025 08:37:29 +0200 Subject: [PATCH 6/8] add ws tests --- .../litestar/test_litestar_websockets.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/integrations/litestar/test_litestar_websockets.py b/tests/integrations/litestar/test_litestar_websockets.py index f7c398a72..d8f4294f3 100644 --- a/tests/integrations/litestar/test_litestar_websockets.py +++ b/tests/integrations/litestar/test_litestar_websockets.py @@ -10,6 +10,7 @@ from dishka import make_async_container from dishka.integrations.litestar import ( + DishkaPlugin, DishkaRouter, FromDishka, inject_websocket, @@ -48,6 +49,18 @@ async def dishka_auto_app(view, provider) -> AsyncGenerator[TestClient, None]: await container.close() +@asynccontextmanager +async def dishka_plugin_app(view, + provider) -> AsyncGenerator[TestClient, None]: + router = DishkaRouter("", route_handlers=[]) + router.register(view) + container = make_async_container(provider) + app = Litestar([router], debug=True, plugins=[DishkaPlugin(container)]) + async with LifespanManager(app): + yield TestClient(app) + await container.close() + + @websocket_listener("/") @inject_websocket async def get_with_app( @@ -104,6 +117,10 @@ async def on_receive( (dishka_auto_app, auto_get_with_app), (dishka_app, GetWithApp), (dishka_auto_app, AutoGetWithApp), + (dishka_plugin_app, get_with_app), + (dishka_plugin_app, auto_get_with_app), + (dishka_plugin_app, GetWithApp), + (dishka_plugin_app, AutoGetWithApp), ], ) async def test_app_dependency( @@ -177,6 +194,10 @@ async def on_receive( (dishka_auto_app, auto_get_with_request), (dishka_app, GetWithRequest), (dishka_auto_app, AutoGetWithRequest), + (dishka_plugin_app, get_with_request), + (dishka_plugin_app, auto_get_with_request), + (dishka_plugin_app, GetWithRequest), + (dishka_plugin_app, AutoGetWithRequest), ], ) async def test_request_dependency( @@ -200,6 +221,10 @@ async def test_request_dependency( (dishka_auto_app, auto_get_with_request), (dishka_app, GetWithRequest), (dishka_auto_app, AutoGetWithRequest), + (dishka_plugin_app, get_with_request), + (dishka_plugin_app, auto_get_with_request), + (dishka_plugin_app, GetWithRequest), + (dishka_plugin_app, AutoGetWithRequest), ], ) async def test_request_dependency2( @@ -279,6 +304,10 @@ async def on_receive( (dishka_auto_app, auto_get_with_websocket), (dishka_app, GetWithWebsocket), (dishka_auto_app, AutoGetWithWebsocket), + (dishka_plugin_app, get_with_websocket), + (dishka_plugin_app, auto_get_with_websocket), + (dishka_plugin_app, GetWithWebsocket), + (dishka_plugin_app, AutoGetWithWebsocket), ], ) async def test_websocket_dependency( From 18528d216c07791fadb0252336b3c4d424a20976 Mon Sep 17 00:00:00 2001 From: euri10 Date: Wed, 14 May 2025 08:49:29 +0200 Subject: [PATCH 7/8] comments --- src/dishka/integrations/litestar.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/dishka/integrations/litestar.py b/src/dishka/integrations/litestar.py index 09a127a27..849277654 100644 --- a/src/dishka/integrations/litestar.py +++ b/src/dishka/integrations/litestar.py @@ -178,12 +178,8 @@ async def handle( self, scope: Scope, receive: Receive, send: Send, next_app: ASGIApp, ) -> None: - if scope.get("type") not in (ScopeType.HTTP, ScopeType.WEBSOCKET): - await next_app(scope, receive, send) - return - if scope.get("type") == ScopeType.HTTP: - request: Request = Request(scope) + request: Request = Request(scope=scope, receive=receive, send=send) r_context = {Request: request} di_scope = DIScope.REQUEST async with request.app.state.dishka_container( @@ -194,7 +190,8 @@ async def handle( await next_app(scope, receive, send) elif scope.get("type") == ScopeType.WEBSOCKET: - websocket: WebSocket = WebSocket(scope) + websocket: WebSocket = WebSocket(scope=scope, receive=receive, + send=send) w_context = {WebSocket: websocket} di_scope = DIScope.SESSION async with websocket.app.state.dishka_container( From 15b7595c42c8cbb50a347845ebd2566f9f38649f Mon Sep 17 00:00:00 2001 From: Andrey Tikhonov <17@itishka.org> Date: Sat, 17 May 2025 00:48:20 +0200 Subject: [PATCH 8/8] Conditional litestar plugin --- src/dishka/integrations/litestar.py | 58 +++++-------------- tests/integrations/litestar/test_litestar.py | 28 ++++++--- .../litestar/test_litestar_websockets.py | 55 ++++++++++++------ 3 files changed, 73 insertions(+), 68 deletions(-) diff --git a/src/dishka/integrations/litestar.py b/src/dishka/integrations/litestar.py index 849277654..8738ec4da 100644 --- a/src/dishka/integrations/litestar.py +++ b/src/dishka/integrations/litestar.py @@ -1,6 +1,4 @@ __all__ = [ - "DishkaMiddleware", - "DishkaPlugin", "FromDishka", "LitestarProvider", "inject", @@ -23,8 +21,6 @@ ) from litestar.handlers.websocket_handlers import WebsocketListenerRouteHandler from litestar.handlers.websocket_handlers._utils import ListenerHandler -from litestar.middleware import ASGIMiddleware -from litestar.plugins import InitPlugin from litestar.routes import BaseRoute from litestar.types import ( ASGIApp, @@ -34,6 +30,12 @@ Send, ) +try: + from litestar.plugins import InitPlugin + HAS_PLUGINS = True +except ImportError: + HAS_PLUGINS = False + from dishka import AsyncContainer, FromDishka, Provider, from_context from dishka import Scope as DIScope from dishka.integrations.base import wrap_injection @@ -171,42 +173,14 @@ def setup_dishka(container: AsyncContainer, app: Litestar) -> None: ) app.state.dishka_container = container +if HAS_PLUGINS: + __all__ += ["DishkaPlugin"] -class DishkaMiddleware(ASGIMiddleware): - scopes = (ScopeType.HTTP, ScopeType.WEBSOCKET) - async def handle( - self, scope: Scope, receive: Receive, - send: Send, next_app: ASGIApp, - ) -> None: - if scope.get("type") == ScopeType.HTTP: - request: Request = Request(scope=scope, receive=receive, send=send) - r_context = {Request: request} - di_scope = DIScope.REQUEST - async with request.app.state.dishka_container( - r_context, - scope=di_scope, - ) as request_container: - request.state.dishka_container = request_container - await next_app(scope, receive, send) - - elif scope.get("type") == ScopeType.WEBSOCKET: - websocket: WebSocket = WebSocket(scope=scope, receive=receive, - send=send) - w_context = {WebSocket: websocket} - di_scope = DIScope.SESSION - async with websocket.app.state.dishka_container( - w_context, - scope=di_scope, - ) as request_container: - websocket.state.dishka_container = request_container - await next_app(scope, receive, send) - - -class DishkaPlugin(InitPlugin): - def __init__(self, container: AsyncContainer): - self.container = container - - def on_app_init(self, app_config: AppConfig) -> AppConfig: - app_config.state.dishka_container = self.container - app_config.middleware.append(DishkaMiddleware()) - return app_config + class DishkaPlugin(InitPlugin): + def __init__(self, container: AsyncContainer): + self.container = container + + def on_app_init(self, app_config: AppConfig) -> AppConfig: + app_config.state.dishka_container = self.container + app_config.middleware.append(make_add_request_container_middleware) + return app_config diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index ab5e5bcc5..13d5ec123 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -1,3 +1,4 @@ +import importlib.util from contextlib import asynccontextmanager from unittest.mock import Mock @@ -8,9 +9,14 @@ from litestar.contrib.htmx.request import HTMXRequest from litestar.testing import TestClient +try: + importlib.util.find_spec("litestar.plugins.InitPlugin") + HAS_PLUGINS = True +except ImportError: + HAS_PLUGINS = False + from dishka import make_async_container from dishka.integrations.litestar import ( - DishkaPlugin, DishkaRouter, FromDishka, inject, @@ -46,6 +52,8 @@ async def dishka_plugin_app( provider, request_class: type[Request] = Request, ) -> TestClient: + from dishka.integrations.litestar import DishkaPlugin + container = make_async_container(provider) app = litestar.Litestar(request_class=request_class, plugins=[DishkaPlugin(container)]) @@ -105,8 +113,10 @@ async def handler( (HTMXRequest, dishka_app), (Request, dishka_auto_app), (HTMXRequest, dishka_auto_app), - (Request, dishka_plugin_app), - (HTMXRequest, dishka_plugin_app), + *(( + (Request, dishka_plugin_app), + (HTMXRequest, dishka_plugin_app), + ) if HAS_PLUGINS else ()), ], ) @pytest.mark.asyncio @@ -133,8 +143,10 @@ async def test_app_dependency( (HTMXRequest, dishka_app), (Request, dishka_auto_app), (HTMXRequest, dishka_auto_app), - (Request, dishka_plugin_app), - (HTMXRequest, dishka_plugin_app), + *(( + (Request, dishka_plugin_app), + (HTMXRequest, dishka_plugin_app), + ) if HAS_PLUGINS else ()), ], ) @pytest.mark.asyncio @@ -160,8 +172,10 @@ async def test_request_dependency( (HTMXRequest, dishka_app), (Request, dishka_auto_app), (HTMXRequest, dishka_auto_app), - (Request, dishka_plugin_app), - (HTMXRequest, dishka_plugin_app), + *(( + (Request, dishka_plugin_app), + (HTMXRequest, dishka_plugin_app), + ) if HAS_PLUGINS else ()), ], ) @pytest.mark.asyncio diff --git a/tests/integrations/litestar/test_litestar_websockets.py b/tests/integrations/litestar/test_litestar_websockets.py index d8f4294f3..acd0e9a46 100644 --- a/tests/integrations/litestar/test_litestar_websockets.py +++ b/tests/integrations/litestar/test_litestar_websockets.py @@ -1,3 +1,4 @@ +import importlib.util from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from unittest.mock import Mock @@ -8,9 +9,14 @@ from litestar.handlers import WebsocketListener from litestar.testing import TestClient +try: + importlib.util.find_spec("litestar.plugins.InitPlugin") + HAS_PLUGINS = True +except ImportError: + HAS_PLUGINS = False + from dishka import make_async_container from dishka.integrations.litestar import ( - DishkaPlugin, DishkaRouter, FromDishka, inject_websocket, @@ -50,8 +56,11 @@ async def dishka_auto_app(view, provider) -> AsyncGenerator[TestClient, None]: @asynccontextmanager -async def dishka_plugin_app(view, - provider) -> AsyncGenerator[TestClient, None]: +async def dishka_plugin_app( + view, provider, +) -> AsyncGenerator[TestClient, None]: + from dishka.integrations.litestar import DishkaPlugin + router = DishkaRouter("", route_handlers=[]) router.register(view) container = make_async_container(provider) @@ -117,10 +126,12 @@ async def on_receive( (dishka_auto_app, auto_get_with_app), (dishka_app, GetWithApp), (dishka_auto_app, AutoGetWithApp), - (dishka_plugin_app, get_with_app), - (dishka_plugin_app, auto_get_with_app), - (dishka_plugin_app, GetWithApp), - (dishka_plugin_app, AutoGetWithApp), + *(( + (dishka_plugin_app, get_with_app), + (dishka_plugin_app, auto_get_with_app), + (dishka_plugin_app, GetWithApp), + (dishka_plugin_app, AutoGetWithApp), + ) if HAS_PLUGINS else ()), ], ) async def test_app_dependency( @@ -194,10 +205,12 @@ async def on_receive( (dishka_auto_app, auto_get_with_request), (dishka_app, GetWithRequest), (dishka_auto_app, AutoGetWithRequest), - (dishka_plugin_app, get_with_request), - (dishka_plugin_app, auto_get_with_request), - (dishka_plugin_app, GetWithRequest), - (dishka_plugin_app, AutoGetWithRequest), + *(( + (dishka_plugin_app, get_with_request), + (dishka_plugin_app, auto_get_with_request), + (dishka_plugin_app, GetWithRequest), + (dishka_plugin_app, AutoGetWithRequest), + ) if HAS_PLUGINS else ()), ], ) async def test_request_dependency( @@ -221,10 +234,12 @@ async def test_request_dependency( (dishka_auto_app, auto_get_with_request), (dishka_app, GetWithRequest), (dishka_auto_app, AutoGetWithRequest), - (dishka_plugin_app, get_with_request), - (dishka_plugin_app, auto_get_with_request), - (dishka_plugin_app, GetWithRequest), - (dishka_plugin_app, AutoGetWithRequest), + *(( + (dishka_plugin_app, get_with_request), + (dishka_plugin_app, auto_get_with_request), + (dishka_plugin_app, GetWithRequest), + (dishka_plugin_app, AutoGetWithRequest), + ) if HAS_PLUGINS else ()), ], ) async def test_request_dependency2( @@ -304,10 +319,12 @@ async def on_receive( (dishka_auto_app, auto_get_with_websocket), (dishka_app, GetWithWebsocket), (dishka_auto_app, AutoGetWithWebsocket), - (dishka_plugin_app, get_with_websocket), - (dishka_plugin_app, auto_get_with_websocket), - (dishka_plugin_app, GetWithWebsocket), - (dishka_plugin_app, AutoGetWithWebsocket), + *(( + (dishka_plugin_app, get_with_websocket), + (dishka_plugin_app, auto_get_with_websocket), + (dishka_plugin_app, GetWithWebsocket), + (dishka_plugin_app, AutoGetWithWebsocket), + ) if HAS_PLUGINS else ()), ], ) async def test_websocket_dependency(