From d952ac2b83471a03189e8c29c31a82bb56253a16 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 23 Jan 2023 07:50:42 -0600 Subject: [PATCH 1/8] Lazily build middleware Closes #2002 --- starlette/applications.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/starlette/applications.py b/starlette/applications.py index a46cbaa0e..a310db125 100644 --- a/starlette/applications.py +++ b/starlette/applications.py @@ -74,7 +74,7 @@ def __init__( {} if exception_handlers is None else dict(exception_handlers) ) self.user_middleware = [] if middleware is None else list(middleware) - self.middleware_stack = self.build_middleware_stack() + self.middleware_stack = None def build_middleware_stack(self) -> ASGIApp: debug = self.debug @@ -115,13 +115,14 @@ def debug(self) -> bool: @debug.setter def debug(self, value: bool) -> None: self._debug = value - self.middleware_stack = self.build_middleware_stack() def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath: return self.router.url_path_for(name, **path_params) async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: scope["app"] = self + if self.middleware_stack is None: + self.middleware_stack = self.build_middleware_stack() await self.middleware_stack(scope, receive, send) def on_event(self, event_type: str) -> typing.Callable: # pragma: nocover @@ -140,8 +141,11 @@ def host( def add_middleware( self, middleware_class: type, **options: typing.Any ) -> None: # pragma: no cover + if self.middleware_stack is not None: + raise RuntimeError( + "Cannot add middlewares after an application has started" + ) self.user_middleware.insert(0, Middleware(middleware_class, **options)) - self.middleware_stack = self.build_middleware_stack() def add_exception_handler( self, @@ -149,7 +153,6 @@ def add_exception_handler( handler: typing.Callable, ) -> None: # pragma: no cover self.exception_handlers[exc_class_or_status_code] = handler - self.middleware_stack = self.build_middleware_stack() def add_event_handler( self, event_type: str, func: typing.Callable From e09fd5df060b84bcf6ff2f5cc8c8e45116215d51 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 23 Jan 2023 07:58:18 -0600 Subject: [PATCH 2/8] add test --- tests/test_applications.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_applications.py b/tests/test_applications.py index fcacbe633..316f1cea8 100644 --- a/tests/test_applications.py +++ b/tests/test_applications.py @@ -1,7 +1,9 @@ import os from contextlib import asynccontextmanager +from typing import Any, Callable import anyio +import httpx import pytest from starlette import status @@ -13,6 +15,7 @@ from starlette.responses import JSONResponse, PlainTextResponse from starlette.routing import Host, Mount, Route, Router, WebSocketRoute from starlette.staticfiles import StaticFiles +from starlette.types import ASGIApp from starlette.websockets import WebSocket @@ -486,3 +489,37 @@ async def startup(): app.on_event("startup")(startup) assert len(record) == 1 + + +def test_middleware_stack_init(test_client_factory: Callable[[ASGIApp], httpx.Client]): + class NoOpMiddleware: + def __init__(self, app: ASGIApp): + self.app = app + + async def __call__(self, *args: Any): + await self.app(*args) + + class SimpleInitializableMiddleware: + counter = 0 + + def __init__(self, app: ASGIApp): + self.app = app + SimpleInitializableMiddleware.counter += 1 + + async def __call__(self, *args: Any): + await self.app(*args) + + def get_app() -> ASGIApp: + app = Starlette() + app.add_middleware(SimpleInitializableMiddleware) + app.add_middleware(NoOpMiddleware) + return app + + with test_client_factory(get_app()): + pass + + assert SimpleInitializableMiddleware.counter == 1 + + test_client_factory(get_app()).get("/foo") + + assert SimpleInitializableMiddleware.counter == 2 From 8654547d7ebdea15bedcc1a38d76203222b7be60 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 23 Jan 2023 07:59:35 -0600 Subject: [PATCH 3/8] add test --- tests/test_applications.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_applications.py b/tests/test_applications.py index 316f1cea8..ba10aff8e 100644 --- a/tests/test_applications.py +++ b/tests/test_applications.py @@ -515,11 +515,19 @@ def get_app() -> ASGIApp: app.add_middleware(NoOpMiddleware) return app - with test_client_factory(get_app()): + app = get_app() + + with test_client_factory(app): pass assert SimpleInitializableMiddleware.counter == 1 - test_client_factory(get_app()).get("/foo") + test_client_factory(app).get("/foo") + + assert SimpleInitializableMiddleware.counter == 1 + + app = get_app() + + test_client_factory(app).get("/foo") assert SimpleInitializableMiddleware.counter == 2 From 4edcb8008506cbdbba4ca6831817233f23378da0 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 23 Jan 2023 12:07:02 -0600 Subject: [PATCH 4/8] fix types --- starlette/applications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starlette/applications.py b/starlette/applications.py index a310db125..96e02167c 100644 --- a/starlette/applications.py +++ b/starlette/applications.py @@ -74,7 +74,7 @@ def __init__( {} if exception_handlers is None else dict(exception_handlers) ) self.user_middleware = [] if middleware is None else list(middleware) - self.middleware_stack = None + self.middleware_stack: typing.Optional[ASGIApp] = None def build_middleware_stack(self) -> ASGIApp: debug = self.debug From 0816c33ea6bdac2ffd31884332518467799a59d8 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Sat, 4 Feb 2023 01:29:28 -0800 Subject: [PATCH 5/8] Update starlette/applications.py Co-authored-by: Marcelo Trylesinski --- starlette/applications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starlette/applications.py b/starlette/applications.py index 96e02167c..85aa055cd 100644 --- a/starlette/applications.py +++ b/starlette/applications.py @@ -140,7 +140,7 @@ def host( def add_middleware( self, middleware_class: type, **options: typing.Any - ) -> None: # pragma: no cover + ) -> None: if self.middleware_stack is not None: raise RuntimeError( "Cannot add middlewares after an application has started" From 6fd9d57388f6cf61e49d8622c90e90ee105e7816 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Sat, 4 Feb 2023 17:33:54 -0800 Subject: [PATCH 6/8] lint --- starlette/applications.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/starlette/applications.py b/starlette/applications.py index 85aa055cd..bd9db20c2 100644 --- a/starlette/applications.py +++ b/starlette/applications.py @@ -138,9 +138,7 @@ def host( ) -> None: # pragma: no cover self.router.host(host, app=app, name=name) - def add_middleware( - self, middleware_class: type, **options: typing.Any - ) -> None: + def add_middleware(self, middleware_class: type, **options: typing.Any) -> None: if self.middleware_stack is not None: raise RuntimeError( "Cannot add middlewares after an application has started" From fd0507eb8efe1800fed3b48a8c5a16663d0b8306 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Sat, 4 Feb 2023 17:41:00 -0800 Subject: [PATCH 7/8] skip coverage on error --- starlette/applications.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/starlette/applications.py b/starlette/applications.py index bd9db20c2..dc11163c3 100644 --- a/starlette/applications.py +++ b/starlette/applications.py @@ -139,9 +139,9 @@ def host( self.router.host(host, app=app, name=name) def add_middleware(self, middleware_class: type, **options: typing.Any) -> None: - if self.middleware_stack is not None: + if self.middleware_stack is not None: # pragma: no cover raise RuntimeError( - "Cannot add middlewares after an application has started" + "Cannot add middleware after an application has started" ) self.user_middleware.insert(0, Middleware(middleware_class, **options)) From 9a1a15bf64143623bb46f4d354be07bf57e4410a Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Sat, 4 Feb 2023 17:42:41 -0800 Subject: [PATCH 8/8] remove setter --- starlette/applications.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/starlette/applications.py b/starlette/applications.py index dc11163c3..c68ad864a 100644 --- a/starlette/applications.py +++ b/starlette/applications.py @@ -65,7 +65,7 @@ def __init__( on_startup is None and on_shutdown is None ), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both." - self._debug = debug + self.debug = debug self.state = State() self.router = Router( routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan @@ -108,14 +108,6 @@ def build_middleware_stack(self) -> ASGIApp: def routes(self) -> typing.List[BaseRoute]: return self.router.routes - @property - def debug(self) -> bool: - return self._debug - - @debug.setter - def debug(self, value: bool) -> None: - self._debug = value - def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath: return self.router.url_path_for(name, **path_params) @@ -140,9 +132,7 @@ def host( def add_middleware(self, middleware_class: type, **options: typing.Any) -> None: if self.middleware_stack is not None: # pragma: no cover - raise RuntimeError( - "Cannot add middleware after an application has started" - ) + raise RuntimeError("Cannot add middleware after an application has started") self.user_middleware.insert(0, Middleware(middleware_class, **options)) def add_exception_handler(