Add convenience for dynamic changes to routing (#2704)

This commit is contained in:
Adam Hopkins 2023-03-19 15:40:58 +02:00 committed by GitHub
parent 5ee36fd933
commit 8f265b8169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 17 deletions

View File

@ -16,7 +16,7 @@ from asyncio import (
)
from asyncio.futures import Future
from collections import defaultdict, deque
from contextlib import suppress
from contextlib import contextmanager, suppress
from functools import partial
from inspect import isawaitable
from os import environ
@ -33,6 +33,7 @@ from typing import (
Deque,
Dict,
Iterable,
Iterator,
List,
Optional,
Set,
@ -433,6 +434,7 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
ctx = params.pop("route_context")
with self.amend():
routes = self.router.add(**params)
if isinstance(routes, Route):
routes = [routes]
@ -449,6 +451,7 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
middleware: FutureMiddleware,
route_names: Optional[List[str]] = None,
):
with self.amend():
if route_names:
return self.register_named_middleware(
middleware.middleware, route_names, middleware.attach_to
@ -459,6 +462,7 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
)
def _apply_signal(self, signal: FutureSignal) -> Signal:
with self.amend():
return self.signal_router.add(*signal)
def dispatch(
@ -1520,6 +1524,27 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
# Lifecycle
# -------------------------------------------------------------------- #
@contextmanager
def amend(self) -> Iterator[None]:
"""
If the application has started, this function allows changes
to be made to add routes, middleware, and signals.
"""
if not self.state.is_started:
yield
else:
do_router = self.router.finalized
do_signal_router = self.signal_router.finalized
if do_router:
self.router.reset()
if do_signal_router:
self.signal_router.reset()
yield
if do_signal_router:
self.signalize(self.config.TOUCHUP)
if do_router:
self.finalize()
def finalize(self):
try:
self.router.finalize()

54
tests/test_late_adds.py Normal file
View File

@ -0,0 +1,54 @@
import pytest
from sanic import Sanic, text
@pytest.fixture
def late_app(app: Sanic):
app.config.TOUCHUP = False
app.get("/")(lambda _: text(""))
return app
def test_late_route(late_app: Sanic):
@late_app.before_server_start
async def late(app: Sanic):
@app.get("/late")
def handler(_):
return text("late")
_, response = late_app.test_client.get("/late")
assert response.status_code == 200
assert response.text == "late"
def test_late_middleware(late_app: Sanic):
@late_app.get("/late")
def handler(request):
return text(request.ctx.late)
@late_app.before_server_start
async def late(app: Sanic):
@app.on_request
def handler(request):
request.ctx.late = "late"
_, response = late_app.test_client.get("/late")
assert response.status_code == 200
assert response.text == "late"
def test_late_signal(late_app: Sanic):
@late_app.get("/late")
def handler(request):
return text(request.ctx.late)
@late_app.before_server_start
async def late(app: Sanic):
@app.signal("http.lifecycle.request")
def handler(request):
request.ctx.late = "late"
_, response = late_app.test_client.get("/late")
assert response.status_code == 200
assert response.text == "late"