diff --git a/docs/deprecation.md b/docs/deprecation.md new file mode 100644 index 000000000..2b16cd6f5 --- /dev/null +++ b/docs/deprecation.md @@ -0,0 +1,61 @@ +# Deprecation Policy + +The goal of this policy is to reduce the impact of changes on users and developers of the project by providing +clear guidelines and a well-defined process for deprecating functionalities. This policy applies to both features +and API interfaces. + +!!! info "Terminology" + **Deprecation** and **removal** of a feature have different meanings. **Deprecation** refers to the process of + communicating to users that a feature will be removed in the future. **Removal** refers to the actual + deletion of the feature from the codebase. + +## Starlette Versions + +Starlette follows [Semantic Versioning](https://semver.org/). + +## Process to Remove a Feature + +We'll consider two kinds of processes: **drop Python version support** and **feature removal**. + +### Python Version + +Starlette will aim to support a Python version until the [EOL date of that version](https://endoflife.date/python). +When a Python version reaches EOL, Starlette will drop support for that version in the next **minor** release. + +The drop of Python version support will be documented in the release notes, but the user will **not** be warned it in code. + +### Feature Removal + +The first step to remove a feature is to **deprecate** it, which is done in **major** releases. + +The deprecation of a feature needs to be followed by a warning message using `warnings.warn` in the code that +uses the deprecated feature. The warning message should include the version in which the feature will be removed. + +The format of the message should follow: + +> *`code` is deprecated and will be removed in version `version`.* + +The `code` can be a *function*, *module* or *feature* name, and the `version` should be the next major release. + +The deprecation warning may include an advice on how to replace the deprecated feature. + +> *Use `alternative` instead.* + +As a full example, imagine we are in version 1.0.0, and we want to deprecate the `potato` function. +We would add the follow warning: + +```python +def potato(): + warnings.warn( + "potato is deprecated and will be removed in version 2.0.0. " + "Use banana instead.", + DeprecationWarning, + ) + +def banana(): + ... +``` + +The deprecation of a feature will be documented in the release notes, and the user will be warned about it. + +Also, in the above example, on version 2.0.0, the `potato` function will be removed from the codebase. diff --git a/docs/release-notes.md b/docs/release-notes.md index aaf3b7d4c..ed7affdd6 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,3 +1,17 @@ +## 1.0.0 + +Wow!!! 1.0.0 is here! 🎉 + +### Removed + +* Removed `WSGIMiddleware`, which is deprecated since `0.19.0`. Please use [`a2wsgi`](https://github.com/abersheeran/a2wsgi) instead. +* Removed `run_until_first_complete`, which is deprecated since `0.19.0`. +* Removed `iscoroutinefunction_or_partial`, which is deprecated since `0.20.1`. + It was an internal function, which we have replaced by `_utils.is_async_callable`. +* Removed `WS_1004_NO_STATUS_RCVD` and `WS_1005_ABNORMAL_CLOSURE` from the `status` module, which were deprecated since `0.19.1`. +* Removed `ExceptionMiddleware` from the `exceptions` module, which was deprecated since `0.19.1`. + The same middleware can be found in the `middleware.exceptions` module. + ## 0.28.0 June 7, 2023 diff --git a/mkdocs.yml b/mkdocs.yml index 1677fe0e9..a2d1a4678 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -47,6 +47,7 @@ nav: - Test Client: 'testclient.md' - Third Party Packages: 'third-party-packages.md' - Contributing: 'contributing.md' + - Deprecation Policy: 'deprecation.md' - Release Notes: 'release-notes.md' markdown_extensions: diff --git a/pyproject.toml b/pyproject.toml index 03c250a46..db5b92151 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,12 +78,9 @@ xfail_strict = true filterwarnings = [ # Turn warnings that aren't filtered into exceptions "error", - "ignore: run_until_first_complete is deprecated and will be removed in a future version.:DeprecationWarning", - "ignore: starlette.middleware.wsgi is deprecated and will be removed in a future release.*:DeprecationWarning", "ignore: Async generator 'starlette.requests.Request.stream' was garbage collected before it had been exhausted.*:ResourceWarning", "ignore: path is deprecated.*:DeprecationWarning:certifi", "ignore: Use 'content=<...>' to upload raw bytes/text content.:DeprecationWarning", - "ignore: The `allow_redirects` argument is deprecated. Use `follow_redirects` instead.:DeprecationWarning", "ignore: 'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning", "ignore: You seem to already have a custom sys.excepthook handler installed. I'll skip installing Trio's custom handler, but this means MultiErrors will not show full tracebacks.:RuntimeWarning", ] diff --git a/starlette/__init__.py b/starlette/__init__.py index df7db8648..5becc17c0 100644 --- a/starlette/__init__.py +++ b/starlette/__init__.py @@ -1 +1 @@ -__version__ = "0.28.0" +__version__ = "1.0.0" diff --git a/starlette/applications.py b/starlette/applications.py index 5fc11f955..b1931fdf2 100644 --- a/starlette/applications.py +++ b/starlette/applications.py @@ -1,9 +1,7 @@ import typing -import warnings from starlette.datastructures import State, URLPath from starlette.middleware import Middleware -from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.errors import ServerErrorMiddleware from starlette.middleware.exceptions import ExceptionMiddleware from starlette.requests import Request @@ -121,19 +119,6 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: self.middleware_stack = self.build_middleware_stack() await self.middleware_stack(scope, receive, send) - 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 - self.router.mount(path, app=app, name=name) - - def host( - self, host: str, app: ASGIApp, name: typing.Optional[str] = None - ) -> None: # pragma: no cover - self.router.host(host, app=app, name=name) - def add_middleware(self, middleware_class: type, **options: typing.Any) -> None: if self.middleware_stack is not None: # pragma: no cover raise RuntimeError("Cannot add middleware after an application has started") @@ -145,117 +130,3 @@ def add_exception_handler( handler: typing.Callable, ) -> None: # pragma: no cover self.exception_handlers[exc_class_or_status_code] = handler - - def add_event_handler( - self, event_type: str, func: typing.Callable - ) -> None: # pragma: no cover - self.router.add_event_handler(event_type, func) - - def add_route( - self, - path: str, - route: typing.Callable, - methods: typing.Optional[typing.List[str]] = None, - name: typing.Optional[str] = None, - include_in_schema: bool = True, - ) -> None: # pragma: no cover - self.router.add_route( - path, route, methods=methods, name=name, include_in_schema=include_in_schema - ) - - def add_websocket_route( - self, path: str, route: typing.Callable, name: typing.Optional[str] = None - ) -> None: # pragma: no cover - self.router.add_websocket_route(path, route, name=name) - - def exception_handler( - self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]] - ) -> 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 - - return decorator - - def route( - self, - path: str, - methods: typing.Optional[typing.List[str]] = None, - name: typing.Optional[str] = None, - include_in_schema: bool = True, - ) -> 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) - """ - 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( - path, - func, - methods=methods, - name=name, - include_in_schema=include_in_schema, - ) - return func - - return decorator - - def websocket_route( - self, path: str, name: typing.Optional[str] = None - ) -> 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) - """ - 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: - """ - 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) - """ - 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.' - - def decorator(func: typing.Callable) -> typing.Callable: - self.add_middleware(BaseHTTPMiddleware, dispatch=func) - return func - - return decorator diff --git a/starlette/concurrency.py b/starlette/concurrency.py index 5c76cb3df..2905aa570 100644 --- a/starlette/concurrency.py +++ b/starlette/concurrency.py @@ -1,7 +1,6 @@ import functools import sys import typing -import warnings import anyio @@ -15,23 +14,6 @@ P = ParamSpec("P") -async def run_until_first_complete(*args: typing.Tuple[typing.Callable, dict]) -> None: - warnings.warn( - "run_until_first_complete is deprecated " - "and will be removed in a future version.", - DeprecationWarning, - ) - - async with anyio.create_task_group() as task_group: - - async def run(func: typing.Callable[[], typing.Coroutine]) -> None: - await func() - task_group.cancel_scope.cancel() - - for func, kwargs in args: - task_group.start_soon(run, functools.partial(func, **kwargs)) - - async def run_in_threadpool( func: typing.Callable[P, T], *args: P.args, **kwargs: P.kwargs ) -> T: diff --git a/starlette/exceptions.py b/starlette/exceptions.py index cc08ed909..323e0c6f6 100644 --- a/starlette/exceptions.py +++ b/starlette/exceptions.py @@ -1,8 +1,5 @@ import http import typing -import warnings - -__all__ = ("HTTPException", "WebSocketException") class HTTPException(Exception): @@ -37,24 +34,3 @@ def __str__(self) -> str: def __repr__(self) -> str: class_name = self.__class__.__name__ return f"{class_name}(code={self.code!r}, reason={self.reason!r})" - - -__deprecated__ = "ExceptionMiddleware" - - -def __getattr__(name: str) -> typing.Any: # pragma: no cover - if name == __deprecated__: - from starlette.middleware.exceptions import ExceptionMiddleware - - warnings.warn( - f"{__deprecated__} is deprecated on `starlette.exceptions`. " - f"Import it from `starlette.middleware.exceptions` instead.", - category=DeprecationWarning, - stacklevel=3, - ) - return ExceptionMiddleware - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") - - -def __dir__() -> typing.List[str]: - return sorted(list(__all__) + [__deprecated__]) # pragma: no cover diff --git a/starlette/middleware/wsgi.py b/starlette/middleware/wsgi.py deleted file mode 100644 index 9dbd06528..000000000 --- a/starlette/middleware/wsgi.py +++ /dev/null @@ -1,140 +0,0 @@ -import io -import math -import sys -import typing -import warnings - -import anyio - -from starlette.types import Receive, Scope, Send - -warnings.warn( - "starlette.middleware.wsgi is deprecated and will be removed in a future release. " - "Please refer to https://github.com/abersheeran/a2wsgi as a replacement.", - DeprecationWarning, -) - - -def build_environ(scope: Scope, body: bytes) -> dict: - """ - Builds a scope and request body into a WSGI environ object. - """ - environ = { - "REQUEST_METHOD": scope["method"], - "SCRIPT_NAME": scope.get("root_path", "").encode("utf8").decode("latin1"), - "PATH_INFO": scope["path"].encode("utf8").decode("latin1"), - "QUERY_STRING": scope["query_string"].decode("ascii"), - "SERVER_PROTOCOL": f"HTTP/{scope['http_version']}", - "wsgi.version": (1, 0), - "wsgi.url_scheme": scope.get("scheme", "http"), - "wsgi.input": io.BytesIO(body), - "wsgi.errors": sys.stdout, - "wsgi.multithread": True, - "wsgi.multiprocess": True, - "wsgi.run_once": False, - } - - # Get server name and port - required in WSGI, not in ASGI - server = scope.get("server") or ("localhost", 80) - environ["SERVER_NAME"] = server[0] - environ["SERVER_PORT"] = server[1] - - # Get client IP address - if scope.get("client"): - environ["REMOTE_ADDR"] = scope["client"][0] - - # Go through headers and make them into environ entries - for name, value in scope.get("headers", []): - name = name.decode("latin1") - if name == "content-length": - corrected_name = "CONTENT_LENGTH" - elif name == "content-type": - corrected_name = "CONTENT_TYPE" - else: - corrected_name = f"HTTP_{name}".upper().replace("-", "_") - # HTTPbis say only ASCII chars are allowed in headers, but we latin1 just in - # case - value = value.decode("latin1") - if corrected_name in environ: - value = environ[corrected_name] + "," + value - environ[corrected_name] = value - return environ - - -class WSGIMiddleware: - def __init__(self, app: typing.Callable) -> None: - self.app = app - - async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: - assert scope["type"] == "http" - responder = WSGIResponder(self.app, scope) - await responder(receive, send) - - -class WSGIResponder: - def __init__(self, app: typing.Callable, scope: Scope) -> None: - self.app = app - self.scope = scope - self.status = None - self.response_headers = None - self.stream_send, self.stream_receive = anyio.create_memory_object_stream( - math.inf - ) - self.response_started = False - self.exc_info: typing.Any = None - - async def __call__(self, receive: Receive, send: Send) -> None: - body = b"" - more_body = True - while more_body: - message = await receive() - body += message.get("body", b"") - more_body = message.get("more_body", False) - environ = build_environ(self.scope, body) - - async with anyio.create_task_group() as task_group: - task_group.start_soon(self.sender, send) - async with self.stream_send: - await anyio.to_thread.run_sync(self.wsgi, environ, self.start_response) - if self.exc_info is not None: - raise self.exc_info[0].with_traceback(self.exc_info[1], self.exc_info[2]) - - async def sender(self, send: Send) -> None: - async with self.stream_receive: - async for message in self.stream_receive: - await send(message) - - def start_response( - self, - status: str, - response_headers: typing.List[typing.Tuple[str, str]], - exc_info: typing.Any = None, - ) -> None: - self.exc_info = exc_info - if not self.response_started: - self.response_started = True - status_code_string, _ = status.split(" ", 1) - status_code = int(status_code_string) - headers = [ - (name.strip().encode("ascii").lower(), value.strip().encode("ascii")) - for name, value in response_headers - ] - anyio.from_thread.run( - self.stream_send.send, - { - "type": "http.response.start", - "status": status_code, - "headers": headers, - }, - ) - - def wsgi(self, environ: dict, start_response: typing.Callable) -> None: - for chunk in self.app(environ, start_response): - anyio.from_thread.run( - self.stream_send.send, - {"type": "http.response.body", "body": chunk, "more_body": True}, - ) - - anyio.from_thread.run( - self.stream_send.send, {"type": "http.response.body", "body": b""} - ) diff --git a/starlette/requests.py b/starlette/requests.py index dbcaad875..838c95998 100644 --- a/starlette/requests.py +++ b/starlette/requests.py @@ -207,7 +207,7 @@ def method(self) -> str: @property def receive(self) -> Receive: - return self._receive + return self._receive # pragma: no cover async def stream(self) -> typing.AsyncGenerator[bytes, None]: if hasattr(self, "_body"): diff --git a/starlette/routing.py b/starlette/routing.py index 8e01c8562..ad78994a7 100644 --- a/starlette/routing.py +++ b/starlette/routing.py @@ -39,21 +39,6 @@ class Match(Enum): FULL = 2 -def iscoroutinefunction_or_partial(obj: typing.Any) -> bool: # pragma: no cover - """ - Correctly determines if an object is a coroutine function, - including those wrapped in functools.partial objects. - """ - warnings.warn( - "iscoroutinefunction_or_partial is deprecated, " - "and will be removed in a future release.", - DeprecationWarning, - ) - while isinstance(obj, functools.partial): - obj = obj.func - return inspect.iscoroutinefunction(obj) - - def request_response(func: typing.Callable) -> ASGIApp: """ Takes a function or coroutine `func(request) -> response`, @@ -793,60 +778,6 @@ def add_websocket_route( route = WebSocketRoute(path, endpoint=endpoint, name=name) self.routes.append(route) - def route( - self, - path: str, - methods: typing.Optional[typing.List[str]] = None, - name: typing.Optional[str] = None, - include_in_schema: bool = True, - ) -> 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) - """ - 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( - path, - func, - methods=methods, - name=name, - include_in_schema=include_in_schema, - ) - return func - - return decorator - - def websocket_route( - self, path: str, name: typing.Optional[str] = None - ) -> 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) - """ - 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) - return func - - return decorator - def add_event_handler( self, event_type: str, func: typing.Callable ) -> None: # pragma: no cover @@ -856,16 +787,3 @@ def add_event_handler( self.on_startup.append(func) else: self.on_shutdown.append(func) - - 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/lifespan/ for recommended approach.", - DeprecationWarning, - ) - - def decorator(func: typing.Callable) -> typing.Callable: - self.add_event_handler(event_type, func) - return func - - return decorator diff --git a/starlette/status.py b/starlette/status.py index 1689328a4..8d2800774 100644 --- a/starlette/status.py +++ b/starlette/status.py @@ -5,89 +5,6 @@ And RFC 2324 - https://tools.ietf.org/html/rfc2324 """ -import warnings -from typing import List - -__all__ = ( - "HTTP_100_CONTINUE", - "HTTP_101_SWITCHING_PROTOCOLS", - "HTTP_102_PROCESSING", - "HTTP_103_EARLY_HINTS", - "HTTP_200_OK", - "HTTP_201_CREATED", - "HTTP_202_ACCEPTED", - "HTTP_203_NON_AUTHORITATIVE_INFORMATION", - "HTTP_204_NO_CONTENT", - "HTTP_205_RESET_CONTENT", - "HTTP_206_PARTIAL_CONTENT", - "HTTP_207_MULTI_STATUS", - "HTTP_208_ALREADY_REPORTED", - "HTTP_226_IM_USED", - "HTTP_300_MULTIPLE_CHOICES", - "HTTP_301_MOVED_PERMANENTLY", - "HTTP_302_FOUND", - "HTTP_303_SEE_OTHER", - "HTTP_304_NOT_MODIFIED", - "HTTP_305_USE_PROXY", - "HTTP_306_RESERVED", - "HTTP_307_TEMPORARY_REDIRECT", - "HTTP_308_PERMANENT_REDIRECT", - "HTTP_400_BAD_REQUEST", - "HTTP_401_UNAUTHORIZED", - "HTTP_402_PAYMENT_REQUIRED", - "HTTP_403_FORBIDDEN", - "HTTP_404_NOT_FOUND", - "HTTP_405_METHOD_NOT_ALLOWED", - "HTTP_406_NOT_ACCEPTABLE", - "HTTP_407_PROXY_AUTHENTICATION_REQUIRED", - "HTTP_408_REQUEST_TIMEOUT", - "HTTP_409_CONFLICT", - "HTTP_410_GONE", - "HTTP_411_LENGTH_REQUIRED", - "HTTP_412_PRECONDITION_FAILED", - "HTTP_413_REQUEST_ENTITY_TOO_LARGE", - "HTTP_414_REQUEST_URI_TOO_LONG", - "HTTP_415_UNSUPPORTED_MEDIA_TYPE", - "HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE", - "HTTP_417_EXPECTATION_FAILED", - "HTTP_418_IM_A_TEAPOT", - "HTTP_421_MISDIRECTED_REQUEST", - "HTTP_422_UNPROCESSABLE_ENTITY", - "HTTP_423_LOCKED", - "HTTP_424_FAILED_DEPENDENCY", - "HTTP_425_TOO_EARLY", - "HTTP_426_UPGRADE_REQUIRED", - "HTTP_428_PRECONDITION_REQUIRED", - "HTTP_429_TOO_MANY_REQUESTS", - "HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE", - "HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS", - "HTTP_500_INTERNAL_SERVER_ERROR", - "HTTP_501_NOT_IMPLEMENTED", - "HTTP_502_BAD_GATEWAY", - "HTTP_503_SERVICE_UNAVAILABLE", - "HTTP_504_GATEWAY_TIMEOUT", - "HTTP_505_HTTP_VERSION_NOT_SUPPORTED", - "HTTP_506_VARIANT_ALSO_NEGOTIATES", - "HTTP_507_INSUFFICIENT_STORAGE", - "HTTP_508_LOOP_DETECTED", - "HTTP_510_NOT_EXTENDED", - "HTTP_511_NETWORK_AUTHENTICATION_REQUIRED", - "WS_1000_NORMAL_CLOSURE", - "WS_1001_GOING_AWAY", - "WS_1002_PROTOCOL_ERROR", - "WS_1003_UNSUPPORTED_DATA", - "WS_1005_NO_STATUS_RCVD", - "WS_1006_ABNORMAL_CLOSURE", - "WS_1007_INVALID_FRAME_PAYLOAD_DATA", - "WS_1008_POLICY_VIOLATION", - "WS_1009_MESSAGE_TOO_BIG", - "WS_1010_MANDATORY_EXT", - "WS_1011_INTERNAL_ERROR", - "WS_1012_SERVICE_RESTART", - "WS_1013_TRY_AGAIN_LATER", - "WS_1014_BAD_GATEWAY", - "WS_1015_TLS_HANDSHAKE", -) HTTP_100_CONTINUE = 100 HTTP_101_SWITCHING_PROTOCOLS = 101 @@ -174,26 +91,3 @@ WS_1013_TRY_AGAIN_LATER = 1013 WS_1014_BAD_GATEWAY = 1014 WS_1015_TLS_HANDSHAKE = 1015 - - -__deprecated__ = {"WS_1004_NO_STATUS_RCVD": 1004, "WS_1005_ABNORMAL_CLOSURE": 1005} - - -def __getattr__(name: str) -> int: - deprecation_changes = { - "WS_1004_NO_STATUS_RCVD": "WS_1005_NO_STATUS_RCVD", - "WS_1005_ABNORMAL_CLOSURE": "WS_1006_ABNORMAL_CLOSURE", - } - deprecated = __deprecated__.get(name) - if deprecated: - warnings.warn( - f"'{name}' is deprecated. Use '{deprecation_changes[name]}' instead.", - category=DeprecationWarning, - stacklevel=3, - ) - return deprecated - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") - - -def __dir__() -> List[str]: - return sorted(list(__all__) + list(__deprecated__.keys())) # pragma: no cover diff --git a/starlette/templating.py b/starlette/templating.py index ec9ca193d..d0e50e049 100644 --- a/starlette/templating.py +++ b/starlette/templating.py @@ -1,5 +1,4 @@ import typing -import warnings from os import PathLike from starlette.background import BackgroundTask @@ -20,7 +19,11 @@ else: # pragma: nocover pass_context = jinja2.contextfunction # type: ignore[attr-defined] except ModuleNotFoundError: # pragma: nocover - jinja2 = None # type: ignore[assignment] + raise RuntimeError( + "Jinja2Templates requires the jinja2 package to be installed.\n" + "You can install it with:\n" + " $ pip install jinja2\n" + ) class _TemplateResponse(Response): @@ -75,7 +78,6 @@ def __init__( context_processors: typing.Optional[ typing.List[typing.Callable[[Request], typing.Dict[str, typing.Any]]] ] = None, - **env_options: typing.Any, ) -> None: ... @@ -100,27 +102,22 @@ def __init__( typing.List[typing.Callable[[Request], typing.Dict[str, typing.Any]]] ] = None, env: typing.Optional["jinja2.Environment"] = None, - **env_options: typing.Any, ) -> None: - if env_options: - warnings.warn( - "Extra environment options are deprecated. Use a preconfigured jinja2.Environment instead.", # noqa: E501 - DeprecationWarning, - ) - assert jinja2 is not None, "jinja2 must be installed to use Jinja2Templates" - assert directory or env, "either 'directory' or 'env' arguments must be passed" self.context_processors = context_processors or [] if directory is not None: - self.env = self._create_env(directory, **env_options) + self.env = self._create_env(directory) elif env is not None: self.env = env + else: + raise RuntimeError( + "Either 'directory' or 'env' must be passed to Jinja2Templates" + ) def _create_env( self, directory: typing.Union[ str, PathLike, typing.Sequence[typing.Union[str, PathLike]] ], - **env_options: typing.Any, ) -> "jinja2.Environment": @pass_context # TODO: Make `__name` a positional-only argument when we drop Python 3.7 @@ -130,10 +127,7 @@ def url_for(context: dict, __name: str, **path_params: typing.Any) -> URL: return request.url_for(__name, **path_params) loader = jinja2.FileSystemLoader(directory) - env_options.setdefault("loader", loader) - env_options.setdefault("autoescape", True) - - env = jinja2.Environment(**env_options) + env = jinja2.Environment(loader=loader, autoescape=True) env.globals["url_for"] = url_for return env diff --git a/starlette/testclient.py b/starlette/testclient.py index 7d4f3e396..7b8005e20 100644 --- a/starlette/testclient.py +++ b/starlette/testclient.py @@ -6,7 +6,6 @@ import queue import sys import typing -import warnings from concurrent.futures import Future from types import GeneratorType from urllib.parse import unquote, urljoin @@ -423,29 +422,6 @@ def _portal_factory(self) -> typing.Generator[anyio.abc.BlockingPortal, None, No ) as portal: yield portal - def _choose_redirect_arg( - self, - follow_redirects: typing.Optional[bool], - allow_redirects: typing.Optional[bool], - ) -> typing.Union[bool, httpx._client.UseClientDefault]: - redirect: typing.Union[ - bool, httpx._client.UseClientDefault - ] = httpx._client.USE_CLIENT_DEFAULT - if allow_redirects is not None: - message = ( - "The `allow_redirects` argument is deprecated. " - "Use `follow_redirects` instead." - ) - warnings.warn(message, DeprecationWarning) - redirect = allow_redirects - if follow_redirects is not None: - redirect = follow_redirects - elif allow_redirects is not None and follow_redirects is not None: - raise RuntimeError( # pragma: no cover - "Cannot use both `allow_redirects` and `follow_redirects`." - ) - return redirect - def request( # type: ignore[override] self, method: str, @@ -461,15 +437,15 @@ def request( # type: ignore[override] auth: typing.Union[ httpx._types.AuthTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, - follow_redirects: typing.Optional[bool] = None, - allow_redirects: typing.Optional[bool] = None, + follow_redirects: typing.Union[ + bool, httpx._client.UseClientDefault + ] = httpx._client.USE_CLIENT_DEFAULT, timeout: typing.Union[ httpx._client.TimeoutTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, extensions: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> httpx.Response: url = self.base_url.join(url) - redirect = self._choose_redirect_arg(follow_redirects, allow_redirects) return super().request( method, url, @@ -481,7 +457,7 @@ def request( # type: ignore[override] headers=headers, cookies=cookies, auth=auth, - follow_redirects=redirect, + follow_redirects=follow_redirects, timeout=timeout, extensions=extensions, ) @@ -496,21 +472,21 @@ def get( # type: ignore[override] auth: typing.Union[ httpx._types.AuthTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, - follow_redirects: typing.Optional[bool] = None, - allow_redirects: typing.Optional[bool] = None, + follow_redirects: typing.Union[ + bool, httpx._client.UseClientDefault + ] = httpx._client.USE_CLIENT_DEFAULT, timeout: typing.Union[ httpx._client.TimeoutTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, extensions: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> httpx.Response: - redirect = self._choose_redirect_arg(follow_redirects, allow_redirects) return super().get( url, params=params, headers=headers, cookies=cookies, auth=auth, - follow_redirects=redirect, + follow_redirects=follow_redirects, timeout=timeout, extensions=extensions, ) @@ -525,21 +501,21 @@ def options( # type: ignore[override] auth: typing.Union[ httpx._types.AuthTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, - follow_redirects: typing.Optional[bool] = None, - allow_redirects: typing.Optional[bool] = None, + follow_redirects: typing.Union[ + bool, httpx._client.UseClientDefault + ] = httpx._client.USE_CLIENT_DEFAULT, timeout: typing.Union[ httpx._client.TimeoutTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, extensions: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> httpx.Response: - redirect = self._choose_redirect_arg(follow_redirects, allow_redirects) return super().options( url, params=params, headers=headers, cookies=cookies, auth=auth, - follow_redirects=redirect, + follow_redirects=follow_redirects, timeout=timeout, extensions=extensions, ) @@ -554,21 +530,21 @@ def head( # type: ignore[override] auth: typing.Union[ httpx._types.AuthTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, - follow_redirects: typing.Optional[bool] = None, - allow_redirects: typing.Optional[bool] = None, + follow_redirects: typing.Union[ + bool, httpx._client.UseClientDefault + ] = httpx._client.USE_CLIENT_DEFAULT, timeout: typing.Union[ httpx._client.TimeoutTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, extensions: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> httpx.Response: - redirect = self._choose_redirect_arg(follow_redirects, allow_redirects) return super().head( url, params=params, headers=headers, cookies=cookies, auth=auth, - follow_redirects=redirect, + follow_redirects=follow_redirects, timeout=timeout, extensions=extensions, ) @@ -587,14 +563,14 @@ def post( # type: ignore[override] auth: typing.Union[ httpx._types.AuthTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, - follow_redirects: typing.Optional[bool] = None, - allow_redirects: typing.Optional[bool] = None, + follow_redirects: typing.Union[ + bool, httpx._client.UseClientDefault + ] = httpx._client.USE_CLIENT_DEFAULT, timeout: typing.Union[ httpx._client.TimeoutTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, extensions: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> httpx.Response: - redirect = self._choose_redirect_arg(follow_redirects, allow_redirects) return super().post( url, content=content, @@ -605,7 +581,7 @@ def post( # type: ignore[override] headers=headers, cookies=cookies, auth=auth, - follow_redirects=redirect, + follow_redirects=follow_redirects, timeout=timeout, extensions=extensions, ) @@ -624,14 +600,14 @@ def put( # type: ignore[override] auth: typing.Union[ httpx._types.AuthTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, - follow_redirects: typing.Optional[bool] = None, - allow_redirects: typing.Optional[bool] = None, + follow_redirects: typing.Union[ + bool, httpx._client.UseClientDefault + ] = httpx._client.USE_CLIENT_DEFAULT, timeout: typing.Union[ httpx._client.TimeoutTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, extensions: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> httpx.Response: - redirect = self._choose_redirect_arg(follow_redirects, allow_redirects) return super().put( url, content=content, @@ -642,7 +618,7 @@ def put( # type: ignore[override] headers=headers, cookies=cookies, auth=auth, - follow_redirects=redirect, + follow_redirects=follow_redirects, timeout=timeout, extensions=extensions, ) @@ -661,14 +637,14 @@ def patch( # type: ignore[override] auth: typing.Union[ httpx._types.AuthTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, - follow_redirects: typing.Optional[bool] = None, - allow_redirects: typing.Optional[bool] = None, + follow_redirects: typing.Union[ + bool, httpx._client.UseClientDefault + ] = httpx._client.USE_CLIENT_DEFAULT, timeout: typing.Union[ httpx._client.TimeoutTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, extensions: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> httpx.Response: - redirect = self._choose_redirect_arg(follow_redirects, allow_redirects) return super().patch( url, content=content, @@ -679,7 +655,7 @@ def patch( # type: ignore[override] headers=headers, cookies=cookies, auth=auth, - follow_redirects=redirect, + follow_redirects=follow_redirects, timeout=timeout, extensions=extensions, ) @@ -694,21 +670,21 @@ def delete( # type: ignore[override] auth: typing.Union[ httpx._types.AuthTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, - follow_redirects: typing.Optional[bool] = None, - allow_redirects: typing.Optional[bool] = None, + follow_redirects: typing.Union[ + bool, httpx._client.UseClientDefault + ] = httpx._client.USE_CLIENT_DEFAULT, timeout: typing.Union[ httpx._client.TimeoutTypes, httpx._client.UseClientDefault ] = httpx._client.USE_CLIENT_DEFAULT, extensions: typing.Optional[typing.Dict[str, typing.Any]] = None, ) -> httpx.Response: - redirect = self._choose_redirect_arg(follow_redirects, allow_redirects) return super().delete( url, params=params, headers=headers, cookies=cookies, auth=auth, - follow_redirects=redirect, + follow_redirects=follow_redirects, timeout=timeout, extensions=extensions, ) diff --git a/tests/middleware/test_https_redirect.py b/tests/middleware/test_https_redirect.py index 5e498c146..962b9c1c1 100644 --- a/tests/middleware/test_https_redirect.py +++ b/tests/middleware/test_https_redirect.py @@ -19,21 +19,21 @@ def homepage(request): assert response.status_code == 200 client = test_client_factory(app) - response = client.get("/", allow_redirects=False) + response = client.get("/", follow_redirects=False) assert response.status_code == 307 assert response.headers["location"] == "https://testserver/" client = test_client_factory(app, base_url="http://testserver:80") - response = client.get("/", allow_redirects=False) + response = client.get("/", follow_redirects=False) assert response.status_code == 307 assert response.headers["location"] == "https://testserver/" client = test_client_factory(app, base_url="http://testserver:443") - response = client.get("/", allow_redirects=False) + response = client.get("/", follow_redirects=False) assert response.status_code == 307 assert response.headers["location"] == "https://testserver/" client = test_client_factory(app, base_url="http://testserver:123") - response = client.get("/", allow_redirects=False) + response = client.get("/", follow_redirects=False) assert response.status_code == 307 assert response.headers["location"] == "https://testserver:123/" diff --git a/tests/middleware/test_wsgi.py b/tests/middleware/test_wsgi.py deleted file mode 100644 index bcb4cd6ff..000000000 --- a/tests/middleware/test_wsgi.py +++ /dev/null @@ -1,144 +0,0 @@ -import sys - -import pytest - -from starlette.middleware.wsgi import WSGIMiddleware, build_environ - - -def hello_world(environ, start_response): - status = "200 OK" - output = b"Hello World!\n" - headers = [ - ("Content-Type", "text/plain; charset=utf-8"), - ("Content-Length", str(len(output))), - ] - start_response(status, headers) - return [output] - - -def echo_body(environ, start_response): - status = "200 OK" - output = environ["wsgi.input"].read() - headers = [ - ("Content-Type", "text/plain; charset=utf-8"), - ("Content-Length", str(len(output))), - ] - start_response(status, headers) - return [output] - - -def raise_exception(environ, start_response): - raise RuntimeError("Something went wrong") - - -def return_exc_info(environ, start_response): - try: - raise RuntimeError("Something went wrong") - except RuntimeError: - status = "500 Internal Server Error" - output = b"Internal Server Error" - headers = [ - ("Content-Type", "text/plain; charset=utf-8"), - ("Content-Length", str(len(output))), - ] - start_response(status, headers, exc_info=sys.exc_info()) - return [output] - - -def test_wsgi_get(test_client_factory): - app = WSGIMiddleware(hello_world) - client = test_client_factory(app) - response = client.get("/") - assert response.status_code == 200 - assert response.text == "Hello World!\n" - - -def test_wsgi_post(test_client_factory): - app = WSGIMiddleware(echo_body) - client = test_client_factory(app) - response = client.post("/", json={"example": 123}) - assert response.status_code == 200 - assert response.text == '{"example": 123}' - - -def test_wsgi_exception(test_client_factory): - # Note that we're testing the WSGI app directly here. - # The HTTP protocol implementations would catch this error and return 500. - app = WSGIMiddleware(raise_exception) - client = test_client_factory(app) - with pytest.raises(RuntimeError): - client.get("/") - - -def test_wsgi_exc_info(test_client_factory): - # Note that we're testing the WSGI app directly here. - # The HTTP protocol implementations would catch this error and return 500. - app = WSGIMiddleware(return_exc_info) - client = test_client_factory(app) - with pytest.raises(RuntimeError): - response = client.get("/") - - app = WSGIMiddleware(return_exc_info) - client = test_client_factory(app, raise_server_exceptions=False) - response = client.get("/") - assert response.status_code == 500 - assert response.text == "Internal Server Error" - - -def test_build_environ(): - scope = { - "type": "http", - "http_version": "1.1", - "method": "GET", - "scheme": "https", - "path": "/", - "query_string": b"a=123&b=456", - "headers": [ - (b"host", b"www.example.org"), - (b"content-type", b"application/json"), - (b"content-length", b"18"), - (b"accept", b"application/json"), - (b"accept", b"text/plain"), - ], - "client": ("134.56.78.4", 1453), - "server": ("www.example.org", 443), - } - body = b'{"example":"body"}' - environ = build_environ(scope, body) - stream = environ.pop("wsgi.input") - assert stream.read() == b'{"example":"body"}' - assert environ == { - "CONTENT_LENGTH": "18", - "CONTENT_TYPE": "application/json", - "HTTP_ACCEPT": "application/json,text/plain", - "HTTP_HOST": "www.example.org", - "PATH_INFO": "/", - "QUERY_STRING": "a=123&b=456", - "REMOTE_ADDR": "134.56.78.4", - "REQUEST_METHOD": "GET", - "SCRIPT_NAME": "", - "SERVER_NAME": "www.example.org", - "SERVER_PORT": 443, - "SERVER_PROTOCOL": "HTTP/1.1", - "wsgi.errors": sys.stdout, - "wsgi.multiprocess": True, - "wsgi.multithread": True, - "wsgi.run_once": False, - "wsgi.url_scheme": "https", - "wsgi.version": (1, 0), - } - - -def test_build_environ_encoding() -> None: - scope = { - "type": "http", - "http_version": "1.1", - "method": "GET", - "path": "/小星", - "root_path": "/中国", - "query_string": b"a=123&b=456", - "headers": [], - } - environ = build_environ(scope, b"") - assert environ["SCRIPT_NAME"] == "/中国".encode().decode("latin-1") - assert environ["PATH_INFO"] == "/小星".encode().decode("latin-1") diff --git a/tests/test_applications.py b/tests/test_applications.py index e30ec9295..56b5cf48b 100644 --- a/tests/test_applications.py +++ b/tests/test_applications.py @@ -437,63 +437,6 @@ def lifespan(app): 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 - - def test_middleware_stack_init(test_client_factory: Callable[[ASGIApp], httpx.Client]): class NoOpMiddleware: def __init__(self, app: ASGIApp): diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 22b9da0e8..6ac1b39b2 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -1,33 +1,11 @@ from contextvars import ContextVar -import anyio -import pytest - from starlette.applications import Starlette -from starlette.concurrency import run_until_first_complete from starlette.requests import Request from starlette.responses import Response from starlette.routing import Route -@pytest.mark.anyio -async def test_run_until_first_complete(): - task1_finished = anyio.Event() - task2_finished = anyio.Event() - - async def task1(): - task1_finished.set() - - async def task2(): - await task1_finished.wait() - await anyio.sleep(0) # pragma: nocover - task2_finished.set() # pragma: nocover - - await run_until_first_complete((task1, {}), (task2, {})) - assert task1_finished.is_set() - assert not task2_finished.is_set() - - def test_accessing_context_from_threaded_sync_endpoint(test_client_factory) -> None: ctxvar: ContextVar[bytes] = ContextVar("ctxvar") ctxvar.set(b"data") diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 735f2401e..9c7d09b8c 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,5 +1,3 @@ -import warnings - import pytest from starlette.exceptions import HTTPException, WebSocketException @@ -185,19 +183,6 @@ class CustomWebSocketException(WebSocketException): ) -def test_exception_middleware_deprecation() -> None: - # this test should be removed once the deprecation shim is removed - with pytest.warns(DeprecationWarning): - from starlette.exceptions import ExceptionMiddleware # noqa: F401 - - with warnings.catch_warnings(): - warnings.simplefilter("error") - import starlette.exceptions - - with pytest.warns(DeprecationWarning): - starlette.exceptions.ExceptionMiddleware - - def test_request_in_app_and_handler_is_the_same_object(client) -> None: response = client.post("/consume_body_in_endpoint_and_handler", content=b"Hello!") assert response.status_code == 422 diff --git a/tests/test_responses.py b/tests/test_responses.py index 284bda1ef..b9f7079b0 100644 --- a/tests/test_responses.py +++ b/tests/test_responses.py @@ -87,7 +87,7 @@ async def app(scope, receive, send): await response(scope, receive, send) client: TestClient = test_client_factory(app) - response = client.request("GET", "/redirect", allow_redirects=False) + response = client.request("GET", "/redirect", follow_redirects=False) assert response.url == "http://testserver/redirect" assert response.headers["content-length"] == "0" diff --git a/tests/test_routing.py b/tests/test_routing.py index 129293224..34b379073 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -952,24 +952,6 @@ def test_mount_asgi_app_with_middleware_url_path_for() -> None: mounted_app_with_middleware.url_path_for("route") -def test_add_route_to_app_after_mount( - test_client_factory: typing.Callable[..., TestClient], -) -> None: - """Checks that Mount will pick up routes - added to the underlying app after it is mounted - """ - inner_app = Router() - app = Mount("/http", app=inner_app) - inner_app.add_route( - "/inner", - endpoint=homepage, - methods=["GET"], - ) - client = test_client_factory(app) - response = client.get("/http/inner") - assert response.status_code == 200 - - def test_exception_on_mounted_apps(test_client_factory): def exc(request): raise Exception("Exc") @@ -1104,20 +1086,3 @@ def test_host_named_repr() -> None: ) # test for substring because repr(Router) returns unique object ID 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) diff --git a/tests/test_staticfiles.py b/tests/test_staticfiles.py index 23d0b57fc..ece3356ac 100644 --- a/tests/test_staticfiles.py +++ b/tests/test_staticfiles.py @@ -9,9 +9,6 @@ from starlette.applications import Starlette from starlette.exceptions import HTTPException -from starlette.middleware import Middleware -from starlette.middleware.base import BaseHTTPMiddleware -from starlette.requests import Request from starlette.routing import Mount from starlette.staticfiles import StaticFiles @@ -40,28 +37,6 @@ def test_staticfiles_with_pathlib(tmp_path: Path, test_client_factory): assert response.text == "" -def test_staticfiles_head_with_middleware(tmpdir, test_client_factory): - """ - see https://github.com/encode/starlette/pull/935 - """ - path = os.path.join(tmpdir, "example.txt") - with open(path, "w") as file: - file.write("x" * 100) - - async def does_nothing_middleware(request: Request, call_next): - response = await call_next(request) - return response - - routes = [Mount("/static", app=StaticFiles(directory=tmpdir), name="static")] - middleware = [Middleware(BaseHTTPMiddleware, dispatch=does_nothing_middleware)] - app = Starlette(routes=routes, middleware=middleware) - - client = test_client_factory(app) - response = client.head("/static/example.txt") - assert response.status_code == 200 - assert response.headers.get("content-length") == "100" - - def test_staticfiles_with_package(test_client_factory): app = StaticFiles(packages=["tests"]) client = test_client_factory(app) diff --git a/tests/test_status.py b/tests/test_status.py deleted file mode 100644 index 04719e87e..000000000 --- a/tests/test_status.py +++ /dev/null @@ -1,25 +0,0 @@ -import importlib - -import pytest - - -@pytest.mark.parametrize( - "constant,msg", - ( - ( - "WS_1004_NO_STATUS_RCVD", - "'WS_1004_NO_STATUS_RCVD' is deprecated. " - "Use 'WS_1005_NO_STATUS_RCVD' instead.", - ), - ( - "WS_1005_ABNORMAL_CLOSURE", - "'WS_1005_ABNORMAL_CLOSURE' is deprecated. " - "Use 'WS_1006_ABNORMAL_CLOSURE' instead.", - ), - ), -) -def test_deprecated_types(constant: str, msg: str) -> None: - with pytest.warns(DeprecationWarning) as record: - getattr(importlib.import_module("starlette.status"), constant) - assert len(record) == 1 - assert msg in str(record.list[0]) diff --git a/tests/test_templates.py b/tests/test_templates.py index 1f1909f4b..7971284c9 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -129,7 +129,8 @@ async def page_b(request): def test_templates_require_directory_or_environment(): with pytest.raises( - AssertionError, match="either 'directory' or 'env' arguments must be passed" + RuntimeError, + match="Either 'directory' or 'env' must be passed to Jinja2Templates", ): Jinja2Templates() # type: ignore[call-overload] @@ -153,8 +154,3 @@ def test_templates_with_environment(tmpdir): templates = Jinja2Templates(env=env) template = templates.get_template("index.html") assert template.render({}) == "Hello" - - -def test_templates_with_environment_options_emit_warning(tmpdir): - with pytest.warns(DeprecationWarning): - Jinja2Templates(str(tmpdir), autoescape=True)