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 asyncio.futures import Future
from collections import defaultdict, deque from collections import defaultdict, deque
from contextlib import suppress from contextlib import contextmanager, suppress
from functools import partial from functools import partial
from inspect import isawaitable from inspect import isawaitable
from os import environ from os import environ
@ -33,6 +33,7 @@ from typing import (
Deque, Deque,
Dict, Dict,
Iterable, Iterable,
Iterator,
List, List,
Optional, Optional,
Set, Set,
@ -433,14 +434,15 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
ctx = params.pop("route_context") ctx = params.pop("route_context")
routes = self.router.add(**params) with self.amend():
if isinstance(routes, Route): routes = self.router.add(**params)
routes = [routes] if isinstance(routes, Route):
routes = [routes]
for r in routes: for r in routes:
r.extra.websocket = websocket r.extra.websocket = websocket
r.extra.static = params.get("static", False) r.extra.static = params.get("static", False)
r.ctx.__dict__.update(ctx) r.ctx.__dict__.update(ctx)
return routes return routes
@ -449,17 +451,19 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
middleware: FutureMiddleware, middleware: FutureMiddleware,
route_names: Optional[List[str]] = None, route_names: Optional[List[str]] = None,
): ):
if route_names: with self.amend():
return self.register_named_middleware( if route_names:
middleware.middleware, route_names, middleware.attach_to return self.register_named_middleware(
) middleware.middleware, route_names, middleware.attach_to
else: )
return self.register_middleware( else:
middleware.middleware, middleware.attach_to return self.register_middleware(
) middleware.middleware, middleware.attach_to
)
def _apply_signal(self, signal: FutureSignal) -> Signal: def _apply_signal(self, signal: FutureSignal) -> Signal:
return self.signal_router.add(*signal) with self.amend():
return self.signal_router.add(*signal)
def dispatch( def dispatch(
self, self,
@ -1520,6 +1524,27 @@ class Sanic(StaticHandleMixin, BaseSanic, StartupMixin, metaclass=TouchUpMeta):
# Lifecycle # 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): def finalize(self):
try: try:
self.router.finalize() 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"