Skip to content

Commit 9329160

Browse files
authored
Deprecate WS_1004_NO_STATUS_RCVD and WS_1005_ABNORMAL_CLOSURE (#1580)
* Deprecate `WS_1004_NO_STATUS_RCVD` and `WS_1005_ABNORMAL_CLOSURE` * Fix linter * Fix coverage
1 parent 5b56e7d commit 9329160

File tree

3 files changed

+203
-2
lines changed

3 files changed

+203
-2
lines changed

starlette/_pep562.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# flake8: noqa
2+
"""
3+
Backport of PEP 562.
4+
https://pypi.org/search/?q=pep562
5+
Licensed under MIT
6+
Copyright (c) 2018 Isaac Muse <isaacmuse@gmail.com>
7+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9+
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
10+
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11+
The above copyright notice and this permission notice shall be included in all copies or substantial portions
12+
of the Software.
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
14+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
15+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
16+
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
17+
IN THE SOFTWARE.
18+
"""
19+
import sys
20+
from typing import Any, Callable, List, Optional
21+
22+
23+
class Pep562:
24+
"""
25+
Backport of PEP 562 <https://pypi.org/search/?q=pep562>.
26+
Wraps the module in a class that exposes the mechanics to override `__dir__` and `__getattr__`.
27+
The given module will be searched for overrides of `__dir__` and `__getattr__` and use them when needed.
28+
"""
29+
30+
def __init__(self, name: str) -> None: # pragma: no cover
31+
"""Acquire `__getattr__` and `__dir__`, but only replace module for versions less than Python 3.7."""
32+
33+
self._module = sys.modules[name]
34+
self._get_attr = getattr(self._module, "__getattr__", None)
35+
self._get_dir: Optional[Callable[..., List[str]]] = getattr(
36+
self._module, "__dir__", None
37+
)
38+
sys.modules[name] = self # type: ignore[assignment]
39+
40+
def __dir__(self) -> List[str]: # pragma: no cover
41+
"""Return the overridden `dir` if one was provided, else apply `dir` to the module."""
42+
43+
return self._get_dir() if self._get_dir else dir(self._module)
44+
45+
def __getattr__(self, name: str) -> Any: # pragma: no cover
46+
"""
47+
Attempt to retrieve the attribute from the module, and if missing, use the overridden function if present.
48+
"""
49+
50+
try:
51+
return getattr(self._module, name)
52+
except AttributeError:
53+
if self._get_attr:
54+
return self._get_attr(name)
55+
raise
56+
57+
58+
def pep562(module_name: str) -> None: # pragma: no cover
59+
"""Helper function to apply PEP 562."""
60+
61+
if sys.version_info < (3, 7):
62+
Pep562(module_name)

starlette/status.py

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,93 @@
55
66
And RFC 2324 - https://tools.ietf.org/html/rfc2324
77
"""
8+
import sys
9+
import warnings
10+
from typing import List
11+
12+
from starlette._pep562 import pep562
13+
14+
__all__ = (
15+
"HTTP_100_CONTINUE",
16+
"HTTP_101_SWITCHING_PROTOCOLS",
17+
"HTTP_102_PROCESSING",
18+
"HTTP_103_EARLY_HINTS",
19+
"HTTP_200_OK",
20+
"HTTP_201_CREATED",
21+
"HTTP_202_ACCEPTED",
22+
"HTTP_203_NON_AUTHORITATIVE_INFORMATION",
23+
"HTTP_204_NO_CONTENT",
24+
"HTTP_205_RESET_CONTENT",
25+
"HTTP_206_PARTIAL_CONTENT",
26+
"HTTP_207_MULTI_STATUS",
27+
"HTTP_208_ALREADY_REPORTED",
28+
"HTTP_226_IM_USED",
29+
"HTTP_300_MULTIPLE_CHOICES",
30+
"HTTP_301_MOVED_PERMANENTLY",
31+
"HTTP_302_FOUND",
32+
"HTTP_303_SEE_OTHER",
33+
"HTTP_304_NOT_MODIFIED",
34+
"HTTP_305_USE_PROXY",
35+
"HTTP_306_RESERVED",
36+
"HTTP_307_TEMPORARY_REDIRECT",
37+
"HTTP_308_PERMANENT_REDIRECT",
38+
"HTTP_400_BAD_REQUEST",
39+
"HTTP_401_UNAUTHORIZED",
40+
"HTTP_402_PAYMENT_REQUIRED",
41+
"HTTP_403_FORBIDDEN",
42+
"HTTP_404_NOT_FOUND",
43+
"HTTP_405_METHOD_NOT_ALLOWED",
44+
"HTTP_406_NOT_ACCEPTABLE",
45+
"HTTP_407_PROXY_AUTHENTICATION_REQUIRED",
46+
"HTTP_408_REQUEST_TIMEOUT",
47+
"HTTP_409_CONFLICT",
48+
"HTTP_410_GONE",
49+
"HTTP_411_LENGTH_REQUIRED",
50+
"HTTP_412_PRECONDITION_FAILED",
51+
"HTTP_413_REQUEST_ENTITY_TOO_LARGE",
52+
"HTTP_414_REQUEST_URI_TOO_LONG",
53+
"HTTP_415_UNSUPPORTED_MEDIA_TYPE",
54+
"HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE",
55+
"HTTP_417_EXPECTATION_FAILED",
56+
"HTTP_418_IM_A_TEAPOT",
57+
"HTTP_421_MISDIRECTED_REQUEST",
58+
"HTTP_422_UNPROCESSABLE_ENTITY",
59+
"HTTP_423_LOCKED",
60+
"HTTP_424_FAILED_DEPENDENCY",
61+
"HTTP_425_TOO_EARLY",
62+
"HTTP_426_UPGRADE_REQUIRED",
63+
"HTTP_428_PRECONDITION_REQUIRED",
64+
"HTTP_429_TOO_MANY_REQUESTS",
65+
"HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE",
66+
"HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS",
67+
"HTTP_500_INTERNAL_SERVER_ERROR",
68+
"HTTP_501_NOT_IMPLEMENTED",
69+
"HTTP_502_BAD_GATEWAY",
70+
"HTTP_503_SERVICE_UNAVAILABLE",
71+
"HTTP_504_GATEWAY_TIMEOUT",
72+
"HTTP_505_HTTP_VERSION_NOT_SUPPORTED",
73+
"HTTP_506_VARIANT_ALSO_NEGOTIATES",
74+
"HTTP_507_INSUFFICIENT_STORAGE",
75+
"HTTP_508_LOOP_DETECTED",
76+
"HTTP_510_NOT_EXTENDED",
77+
"HTTP_511_NETWORK_AUTHENTICATION_REQUIRED",
78+
"WS_1000_NORMAL_CLOSURE",
79+
"WS_1001_GOING_AWAY",
80+
"WS_1002_PROTOCOL_ERROR",
81+
"WS_1003_UNSUPPORTED_DATA",
82+
"WS_1005_NO_STATUS_RCVD",
83+
"WS_1006_ABNORMAL_CLOSURE",
84+
"WS_1007_INVALID_FRAME_PAYLOAD_DATA",
85+
"WS_1008_POLICY_VIOLATION",
86+
"WS_1009_MESSAGE_TOO_BIG",
87+
"WS_1010_MANDATORY_EXT",
88+
"WS_1011_INTERNAL_ERROR",
89+
"WS_1012_SERVICE_RESTART",
90+
"WS_1013_TRY_AGAIN_LATER",
91+
"WS_1014_BAD_GATEWAY",
92+
"WS_1015_TLS_HANDSHAKE",
93+
)
94+
895
HTTP_100_CONTINUE = 100
996
HTTP_101_SWITCHING_PROTOCOLS = 101
1097
HTTP_102_PROCESSING = 102
@@ -79,8 +166,8 @@
79166
WS_1001_GOING_AWAY = 1001
80167
WS_1002_PROTOCOL_ERROR = 1002
81168
WS_1003_UNSUPPORTED_DATA = 1003
82-
WS_1004_NO_STATUS_RCVD = 1004
83-
WS_1005_ABNORMAL_CLOSURE = 1005
169+
WS_1005_NO_STATUS_RCVD = 1005
170+
WS_1006_ABNORMAL_CLOSURE = 1006
84171
WS_1007_INVALID_FRAME_PAYLOAD_DATA = 1007
85172
WS_1008_POLICY_VIOLATION = 1008
86173
WS_1009_MESSAGE_TOO_BIG = 1009
@@ -90,3 +177,30 @@
90177
WS_1013_TRY_AGAIN_LATER = 1013
91178
WS_1014_BAD_GATEWAY = 1014
92179
WS_1015_TLS_HANDSHAKE = 1015
180+
181+
182+
__deprecated__ = {"WS_1004_NO_STATUS_RCVD": 1004, "WS_1005_ABNORMAL_CLOSURE": 1005}
183+
184+
185+
def __getattr__(name: str) -> int:
186+
deprecation_changes = {
187+
"WS_1004_NO_STATUS_RCVD": "WS_1005_NO_STATUS_RCVD",
188+
"WS_1005_ABNORMAL_CLOSURE": "WS_1006_ABNORMAL_CLOSURE",
189+
}
190+
deprecated = __deprecated__.get(name)
191+
if deprecated:
192+
stacklevel = 3 if sys.version_info >= (3, 7) else 4
193+
warnings.warn(
194+
f"'{name}' is deprecated. Use '{deprecation_changes[name]}' instead.",
195+
category=DeprecationWarning,
196+
stacklevel=stacklevel,
197+
)
198+
return deprecated
199+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
200+
201+
202+
def __dir__() -> List[str]:
203+
return sorted(list(__all__) + list(__deprecated__.keys())) # pragma: no cover
204+
205+
206+
pep562(__name__)

tests/test_status.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import importlib
2+
3+
import pytest
4+
5+
6+
@pytest.mark.parametrize(
7+
"constant,msg",
8+
(
9+
(
10+
"WS_1004_NO_STATUS_RCVD",
11+
"'WS_1004_NO_STATUS_RCVD' is deprecated. "
12+
"Use 'WS_1005_NO_STATUS_RCVD' instead.",
13+
),
14+
(
15+
"WS_1005_ABNORMAL_CLOSURE",
16+
"'WS_1005_ABNORMAL_CLOSURE' is deprecated. "
17+
"Use 'WS_1006_ABNORMAL_CLOSURE' instead.",
18+
),
19+
),
20+
)
21+
def test_deprecated_types(constant: str, msg: str) -> None:
22+
with pytest.warns(DeprecationWarning) as record:
23+
getattr(importlib.import_module("starlette.status"), constant)
24+
assert len(record) == 1
25+
assert msg in str(record.list[0])

0 commit comments

Comments
 (0)