Deprecate `Starlette` and `Router` decorators (#1897)

This commit is contained in:
Marcelo Trylesinski 2022-12-03 08:54:08 +01:00 committed by GitHub
parent b77a41db62
commit fd1e91a8de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 129 additions and 90 deletions

View File

@ -1,4 +1,5 @@
import typing import typing
import warnings
from starlette.datastructures import State, URLPath from starlette.datastructures import State, URLPath
from starlette.middleware import Middleware from starlette.middleware import Middleware
@ -123,43 +124,17 @@ class Starlette:
scope["app"] = self scope["app"] = self
await self.middleware_stack(scope, receive, send) await self.middleware_stack(scope, receive, send)
# The following usages are now discouraged in favour of configuration
# during Starlette.__init__(...)
def on_event(self, event_type: str) -> typing.Callable: # pragma: nocover def on_event(self, event_type: str) -> typing.Callable: # pragma: nocover
return self.router.on_event(event_type) return self.router.on_event(event_type)
def mount( def mount(
self, path: str, app: ASGIApp, name: typing.Optional[str] = None self, path: str, app: ASGIApp, name: typing.Optional[str] = None
) -> None: # pragma: nocover ) -> None: # pragma: nocover
"""
We no longer document this API, and its usage is discouraged.
Instead you should use the following approach:
routes = [
Mount(path, ...),
...
]
app = Starlette(routes=routes)
"""
self.router.mount(path, app=app, name=name) self.router.mount(path, app=app, name=name)
def host( def host(
self, host: str, app: ASGIApp, name: typing.Optional[str] = None self, host: str, app: ASGIApp, name: typing.Optional[str] = None
) -> None: # pragma: no cover ) -> None: # pragma: no cover
"""
We no longer document this API, and its usage is discouraged.
Instead you should use the following approach:
routes = [
Host(path, ...),
...
]
app = Starlette(routes=routes)
"""
self.router.host(host, app=app, name=name) self.router.host(host, app=app, name=name)
def add_middleware( def add_middleware(
@ -200,7 +175,13 @@ class Starlette:
def exception_handler( def exception_handler(
self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]] self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]]
) -> typing.Callable: # pragma: nocover ) -> typing.Callable:
warnings.warn(
"The `exception_handler` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
"Refer to https://www.starlette.io/exceptions/ for the recommended approach.", # noqa: E501
DeprecationWarning,
)
def decorator(func: typing.Callable) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable:
self.add_exception_handler(exc_class_or_status_code, func) self.add_exception_handler(exc_class_or_status_code, func)
return func return func
@ -213,18 +194,19 @@ class Starlette:
methods: typing.Optional[typing.List[str]] = None, methods: typing.Optional[typing.List[str]] = None,
name: typing.Optional[str] = None, name: typing.Optional[str] = None,
include_in_schema: bool = True, include_in_schema: bool = True,
) -> typing.Callable: # pragma: nocover ) -> typing.Callable:
""" """
We no longer document this decorator style API, and its usage is discouraged. We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach: Instead you should use the following approach:
routes = [ >>> routes = [Route(path, endpoint=...), ...]
Route(path, endpoint=..., ...), >>> app = Starlette(routes=routes)
...
]
app = Starlette(routes=routes)
""" """
warnings.warn(
"The `route` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
"Refer to https://www.starlette.io/routing/ for the recommended approach.", # noqa: E501
DeprecationWarning,
)
def decorator(func: typing.Callable) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable:
self.router.add_route( self.router.add_route(
@ -240,18 +222,19 @@ class Starlette:
def websocket_route( def websocket_route(
self, path: str, name: typing.Optional[str] = None self, path: str, name: typing.Optional[str] = None
) -> typing.Callable: # pragma: nocover ) -> typing.Callable:
""" """
We no longer document this decorator style API, and its usage is discouraged. We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach: Instead you should use the following approach:
routes = [ >>> routes = [WebSocketRoute(path, endpoint=...), ...]
WebSocketRoute(path, endpoint=..., ...), >>> app = Starlette(routes=routes)
...
]
app = Starlette(routes=routes)
""" """
warnings.warn(
"The `websocket_route` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
"Refer to https://www.starlette.io/routing/#websocket-routing for the recommended approach.", # noqa: E501
DeprecationWarning,
)
def decorator(func: typing.Callable) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable:
self.router.add_websocket_route(path, func, name=name) self.router.add_websocket_route(path, func, name=name)
@ -259,19 +242,19 @@ class Starlette:
return decorator return decorator
def middleware(self, middleware_type: str) -> typing.Callable: # pragma: nocover def middleware(self, middleware_type: str) -> typing.Callable:
""" """
We no longer document this decorator style API, and its usage is discouraged. We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach: Instead you should use the following approach:
middleware = [ >>> middleware = [Middleware(...), ...]
Middleware(...), >>> app = Starlette(middleware=middleware)
...
]
app = Starlette(middleware=middleware)
""" """
warnings.warn(
"The `middleware` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
"Refer to https://www.starlette.io/middleware/#using-middleware for recommended approach.", # noqa: E501
DeprecationWarning,
)
assert ( assert (
middleware_type == "http" middleware_type == "http"
), 'Currently only middleware("http") is supported.' ), 'Currently only middleware("http") is supported.'

View File

@ -737,41 +737,15 @@ class Router:
def __eq__(self, other: typing.Any) -> bool: def __eq__(self, other: typing.Any) -> bool:
return isinstance(other, Router) and self.routes == other.routes return isinstance(other, Router) and self.routes == other.routes
# The following usages are now discouraged in favour of configuration
#  during Router.__init__(...)
def mount( def mount(
self, path: str, app: ASGIApp, name: typing.Optional[str] = None self, path: str, app: ASGIApp, name: typing.Optional[str] = None
) -> None: # pragma: nocover ) -> None: # pragma: nocover
"""
We no longer document this API, and its usage is discouraged.
Instead you should use the following approach:
routes = [
Mount(path, ...),
...
]
app = Starlette(routes=routes)
"""
route = Mount(path, app=app, name=name) route = Mount(path, app=app, name=name)
self.routes.append(route) self.routes.append(route)
def host( def host(
self, host: str, app: ASGIApp, name: typing.Optional[str] = None self, host: str, app: ASGIApp, name: typing.Optional[str] = None
) -> None: # pragma: no cover ) -> None: # pragma: no cover
"""
We no longer document this API, and its usage is discouraged.
Instead you should use the following approach:
routes = [
Host(path, ...),
...
]
app = Starlette(routes=routes)
"""
route = Host(host, app=app, name=name) route = Host(host, app=app, name=name)
self.routes.append(route) self.routes.append(route)
@ -804,18 +778,19 @@ class Router:
methods: typing.Optional[typing.List[str]] = None, methods: typing.Optional[typing.List[str]] = None,
name: typing.Optional[str] = None, name: typing.Optional[str] = None,
include_in_schema: bool = True, include_in_schema: bool = True,
) -> typing.Callable: # pragma: nocover ) -> typing.Callable:
""" """
We no longer document this decorator style API, and its usage is discouraged. We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach: Instead you should use the following approach:
routes = [ >>> routes = [Route(path, endpoint=...), ...]
Route(path, endpoint=..., ...), >>> app = Starlette(routes=routes)
...
]
app = Starlette(routes=routes)
""" """
warnings.warn(
"The `route` decorator is deprecated, and will be removed in version 1.0.0."
"Refer to https://www.starlette.io/routing/#http-routing for the recommended approach.", # noqa: E501
DeprecationWarning,
)
def decorator(func: typing.Callable) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable:
self.add_route( self.add_route(
@ -831,18 +806,19 @@ class Router:
def websocket_route( def websocket_route(
self, path: str, name: typing.Optional[str] = None self, path: str, name: typing.Optional[str] = None
) -> typing.Callable: # pragma: nocover ) -> typing.Callable:
""" """
We no longer document this decorator style API, and its usage is discouraged. We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach: Instead you should use the following approach:
routes = [ >>> routes = [WebSocketRoute(path, endpoint=...), ...]
WebSocketRoute(path, endpoint=..., ...), >>> app = Starlette(routes=routes)
...
]
app = Starlette(routes=routes)
""" """
warnings.warn(
"The `websocket_route` decorator is deprecated, and will be removed in version 1.0.0. Refer to " # noqa: E501
"https://www.starlette.io/routing/#websocket-routing for the recommended approach.", # noqa: E501
DeprecationWarning,
)
def decorator(func: typing.Callable) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable:
self.add_websocket_route(path, func, name=name) self.add_websocket_route(path, func, name=name)
@ -860,7 +836,13 @@ class Router:
else: else:
self.on_shutdown.append(func) self.on_shutdown.append(func)
def on_event(self, event_type: str) -> typing.Callable: # pragma: nocover def on_event(self, event_type: str) -> typing.Callable:
warnings.warn(
"The `on_event` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
"Refer to https://www.starlette.io/events/#registering-events for recommended approach.", # noqa: E501
DeprecationWarning,
)
def decorator(func: typing.Callable) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable:
self.add_event_handler(event_type, func) self.add_event_handler(event_type, func)
return func return func

View File

@ -429,3 +429,60 @@ def test_app_sync_gen_lifespan(test_client_factory):
assert not cleanup_complete assert not cleanup_complete
assert startup_complete assert startup_complete
assert cleanup_complete assert cleanup_complete
def test_decorator_deprecations() -> None:
app = Starlette()
with pytest.deprecated_call(
match=(
"The `exception_handler` decorator is deprecated, "
"and will be removed in version 1.0.0."
)
) as record:
app.exception_handler(500)(http_exception)
assert len(record) == 1
with pytest.deprecated_call(
match=(
"The `middleware` decorator is deprecated, "
"and will be removed in version 1.0.0."
)
) as record:
async def middleware(request, call_next):
... # pragma: no cover
app.middleware("http")(middleware)
assert len(record) == 1
with pytest.deprecated_call(
match=(
"The `route` decorator is deprecated, "
"and will be removed in version 1.0.0."
)
) as record:
app.route("/")(async_homepage)
assert len(record) == 1
with pytest.deprecated_call(
match=(
"The `websocket_route` decorator is deprecated, "
"and will be removed in version 1.0.0."
)
) as record:
app.websocket_route("/ws")(websocket_endpoint)
assert len(record) == 1
with pytest.deprecated_call(
match=(
"The `on_event` decorator is deprecated, "
"and will be removed in version 1.0.0."
)
) as record:
async def startup():
... # pragma: no cover
app.on_event("startup")(startup)
assert len(record) == 1

View File

@ -1020,3 +1020,20 @@ def test_host_named_repr() -> None:
) )
# test for substring because repr(Router) returns unique object ID # test for substring because repr(Router) returns unique object ID
assert repr(route).startswith("Host(host='example.com', name='app', app=") assert repr(route).startswith("Host(host='example.com', name='app', app=")
def test_decorator_deprecations() -> None:
router = Router()
with pytest.deprecated_call():
router.route("/")(homepage)
with pytest.deprecated_call():
router.websocket_route("/ws")(websocket_endpoint)
with pytest.deprecated_call():
async def startup() -> None:
... # pragma: nocover
router.on_event("startup")(startup)