Skip to content

Commit 326e988

Browse files
authored
fix: ensure BaseRouteHandler.on_registration is only called once per handler (#4738)
ensure BaseRouteHandler.on_registration is only called once per handler
1 parent d63f917 commit 326e988

2 files changed

Lines changed: 33 additions & 2 deletions

File tree

litestar/app.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ def _create_route_handler_method_map(
657657

658658
return route_map
659659

660-
def _build_routes(self, route_handlers: Iterable[BaseRouteHandler]) -> list[HTTPRoute | ASGIRoute | WebSocketRoute]:
660+
def _build_routes(self, route_handlers: Iterable[BaseRouteHandler]) -> list[HTTPRoute | ASGIRoute | WebSocketRoute]: # noqa: C901
661661
"""Create routes for all the handlers"""
662662
routes: list[HTTPRoute | ASGIRoute | WebSocketRoute] = []
663663

@@ -683,11 +683,19 @@ def _build_routes(self, route_handlers: Iterable[BaseRouteHandler]) -> list[HTTP
683683
HTTPRoute(path=path, route_handlers=_maybe_add_options_handler(path, http_handlers, root=self))
684684
)
685685

686+
registered_route_handlers = set()
687+
686688
for finalized_route in routes:
687689
route_handlers = get_route_handlers(finalized_route)
688690

689691
for route_handler in route_handlers:
690-
route_handler.on_registration(route=finalized_route, app=self)
692+
# handlers can be registered multiple times, if they define multiple
693+
# paths. in these cases, we don't want to call 'on_registration' more
694+
# than once, so we keep track of the handlers we've already called this
695+
# hook
696+
if route_handler not in registered_route_handlers:
697+
route_handler.on_registration(route=finalized_route, app=self)
698+
registered_route_handlers.add(route_handler)
691699

692700
for plugin in self.plugins.receive_route:
693701
plugin.receive_route(finalized_route)

tests/unit/test_app.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import pytest
1111
from click import Group
1212
from pytest import MonkeyPatch
13+
from pytest_mock import MockerFixture
1314

1415
from litestar import Litestar, MediaType, Request, Response, get
1516
from litestar.config.app import AppConfig, ExperimentalFeatures
@@ -22,6 +23,7 @@
2223
LitestarWarning,
2324
NotFoundException,
2425
)
26+
from litestar.handlers import BaseRouteHandler
2527
from litestar.plugins import CLIPlugin
2628
from litestar.status_codes import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR
2729
from litestar.testing import TestClient, create_test_client
@@ -424,3 +426,24 @@ def handler(scope: Scope) -> None:
424426
client.get("/")
425427

426428
mock.assert_called_once_with(app)
429+
430+
431+
def test_handler_registration_on_registration_called_only_once(mocker: MockerFixture) -> None:
432+
mock_on_registration = mocker.spy(BaseRouteHandler, "on_registration")
433+
434+
@get(["/a", "/b"], name="hello_this_is_a_test")
435+
async def handler() -> None:
436+
pass
437+
438+
Litestar([handler], openapi_config=None)
439+
440+
assert (
441+
len(
442+
[
443+
c
444+
for c in mock_on_registration.call_args_list
445+
if isinstance(c.args[0], BaseRouteHandler) and c.args[0].name == "hello_this_is_a_test"
446+
]
447+
)
448+
== 1
449+
)

0 commit comments

Comments
 (0)