Skip to content

Commit

Permalink
Merge branch 'master' into fix/ipv6-url-bug
Browse files Browse the repository at this point in the history
  • Loading branch information
kousikmitra committed Dec 3, 2022
2 parents fdeaca4 + 865efeb commit 85464a9
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 96 deletions.
8 changes: 4 additions & 4 deletions requirements.txt
Expand Up @@ -3,21 +3,21 @@

# Testing
autoflake==1.5.3
black==22.8.0
black==22.10.0
coverage==6.5.0
flake8==3.9.2
importlib-metadata==4.13.0
isort==5.10.1
mypy==0.971
mypy==0.991
typing_extensions==4.4.0
types-contextvars==2.4.7
types-PyYAML==6.0.12
types-PyYAML==6.0.12.2
types-dataclasses==0.6.6
pytest==7.2.0
trio==0.21.0

# Documentation
mkdocs==1.4.0
mkdocs==1.4.2
mkdocs-material==8.5.7
mkautodoc==0.2.0

Expand Down
81 changes: 32 additions & 49 deletions starlette/applications.py
@@ -1,4 +1,5 @@
import typing
import warnings

from starlette.datastructures import State, URLPath
from starlette.middleware import Middleware
Expand Down Expand Up @@ -123,43 +124,17 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
scope["app"] = self
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
return self.router.on_event(event_type)

def mount(
self, path: str, app: ASGIApp, name: typing.Optional[str] = None
) -> 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)

def host(
self, host: str, app: ASGIApp, name: typing.Optional[str] = None
) -> 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)

def add_middleware(
Expand Down Expand Up @@ -200,7 +175,13 @@ def add_websocket_route(

def exception_handler(
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:
self.add_exception_handler(exc_class_or_status_code, func)
return func
Expand All @@ -213,18 +194,19 @@ def route(
methods: typing.Optional[typing.List[str]] = None,
name: typing.Optional[str] = None,
include_in_schema: bool = True,
) -> typing.Callable: # pragma: nocover
) -> typing.Callable:
"""
We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach:
routes = [
Route(path, endpoint=..., ...),
...
]
app = Starlette(routes=routes)
>>> routes = [Route(path, endpoint=...), ...]
>>> 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:
self.router.add_route(
Expand All @@ -240,38 +222,39 @@ def decorator(func: typing.Callable) -> typing.Callable:

def websocket_route(
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.
Instead you should use the following approach:
routes = [
WebSocketRoute(path, endpoint=..., ...),
...
]
app = Starlette(routes=routes)
>>> routes = [WebSocketRoute(path, endpoint=...), ...]
>>> 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:
self.router.add_websocket_route(path, func, name=name)
return func

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.
Instead you should use the following approach:
middleware = [
Middleware(...),
...
]
app = Starlette(middleware=middleware)
>>> 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 (
middleware_type == "http"
), 'Currently only middleware("http") is supported.'
Expand Down
2 changes: 1 addition & 1 deletion starlette/convertors.py
Expand Up @@ -51,7 +51,7 @@ def to_string(self, value: int) -> str:


class FloatConvertor(Convertor):
regex = "[0-9]+(.[0-9]+)?"
regex = r"[0-9]+(\.[0-9]+)?"

def convert(self, value: str) -> float:
return float(value)
Expand Down
64 changes: 23 additions & 41 deletions starlette/routing.py
Expand Up @@ -737,41 +737,15 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
def __eq__(self, other: typing.Any) -> bool:
return isinstance(other, Router) and self.routes == other.routes

# The following usages are now discouraged in favour of configuration
#  during Router.__init__(...)
def mount(
self, path: str, app: ASGIApp, name: typing.Optional[str] = None
) -> 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)
self.routes.append(route)

def host(
self, host: str, app: ASGIApp, name: typing.Optional[str] = None
) -> 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)
self.routes.append(route)

Expand Down Expand Up @@ -804,18 +778,19 @@ def route(
methods: typing.Optional[typing.List[str]] = None,
name: typing.Optional[str] = None,
include_in_schema: bool = True,
) -> typing.Callable: # pragma: nocover
) -> typing.Callable:
"""
We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach:
routes = [
Route(path, endpoint=..., ...),
...
]
app = Starlette(routes=routes)
>>> routes = [Route(path, endpoint=...), ...]
>>> 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:
self.add_route(
Expand All @@ -831,18 +806,19 @@ def decorator(func: typing.Callable) -> typing.Callable:

def websocket_route(
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.
Instead you should use the following approach:
routes = [
WebSocketRoute(path, endpoint=..., ...),
...
]
app = Starlette(routes=routes)
>>> routes = [WebSocketRoute(path, endpoint=...), ...]
>>> 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:
self.add_websocket_route(path, func, name=name)
Expand All @@ -860,7 +836,13 @@ def add_event_handler(
else:
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:
self.add_event_handler(event_type, func)
return func
Expand Down
6 changes: 5 additions & 1 deletion starlette/testclient.py
Expand Up @@ -368,6 +368,7 @@ def __init__(
backend: str = "asyncio",
backend_options: typing.Optional[typing.Dict[str, typing.Any]] = None,
cookies: httpx._client.CookieTypes = None,
headers: typing.Dict[str, str] = None,
) -> None:
self.async_backend = _AsyncBackend(
backend=backend, backend_options=backend_options or {}
Expand All @@ -385,10 +386,13 @@ def __init__(
raise_server_exceptions=raise_server_exceptions,
root_path=root_path,
)
if headers is None:
headers = {}
headers.setdefault("user-agent", "testclient")
super().__init__(
app=self.app,
base_url=base_url,
headers={"user-agent": "testclient"},
headers=headers,
transport=transport,
follow_redirects=True,
cookies=cookies,
Expand Down
57 changes: 57 additions & 0 deletions tests/test_applications.py
Expand Up @@ -429,3 +429,60 @@ def lifespan(app):
assert not cleanup_complete
assert startup_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
14 changes: 14 additions & 0 deletions tests/test_convertors.py
Expand Up @@ -54,3 +54,17 @@ def test_datetime_convertor(test_client_factory, app: Router):
app.url_path_for("datetime-convertor", param=datetime(1996, 1, 22, 23, 0, 0))
== "/datetime/1996-01-22T23:00:00"
)


@pytest.mark.parametrize("param, status_code", [("1.0", 200), ("1-0", 404)])
def test_default_float_convertor(test_client_factory, param: str, status_code: int):
def float_convertor(request):
param = request.path_params["param"]
assert isinstance(param, float)
return JSONResponse({"float": param})

app = Router(routes=[Route("/{param:float}", endpoint=float_convertor)])

client = test_client_factory(app)
response = client.get(f"/{param}")
assert response.status_code == status_code

0 comments on commit 85464a9

Please sign in to comment.