Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions src/httpx2/httpx2/_status_codes.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
from __future__ import annotations

from enum import IntEnum
import warnings
from enum import EnumMeta, IntEnum

__all__ = ["codes"]


class codes(IntEnum):
# Constant names that were renamed by RFC 9110, kept as deprecated aliases
# mapping each old name to its canonical replacement.
_DEPRECATED_CODES = {
"REQUEST_ENTITY_TOO_LARGE": "CONTENT_TOO_LARGE",
"REQUEST_URI_TOO_LONG": "URI_TOO_LONG",
"REQUESTED_RANGE_NOT_SATISFIABLE": "RANGE_NOT_SATISFIABLE",
"UNPROCESSABLE_ENTITY": "UNPROCESSABLE_CONTENT",
}


class _CodesMeta(EnumMeta):
def __getattr__(cls, name: str) -> codes:
if name in _DEPRECATED_CODES:
new_name = _DEPRECATED_CODES[name]
warnings.warn(
f"'{name}' is deprecated. Use '{new_name}' instead.",
category=DeprecationWarning,
stacklevel=2,
)
return cls[new_name]
raise AttributeError(name)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this exception matches the message you'd receive before adding this metadata?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it does!



class codes(IntEnum, metaclass=_CodesMeta):
"""HTTP status codes and reason phrases

Status codes from the following RFCs are all observed:

* RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616
* RFC 9110: HTTP Semantics, obsoletes 7231 & 7238
* RFC 6585: Additional HTTP Status Codes
* RFC 3229: Delta encoding in HTTP
* RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518
* RFC 5842: Binding Extensions to WebDAV
* RFC 7238: Permanent Redirect
* RFC 2295: Transparent Content Negotiation in HTTP
* RFC 2774: An HTTP Extension Framework
* RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2)
Expand Down Expand Up @@ -126,14 +148,14 @@ def is_error(cls, value: int) -> bool:
GONE = 410, "Gone"
LENGTH_REQUIRED = 411, "Length Required"
PRECONDITION_FAILED = 412, "Precondition Failed"
REQUEST_ENTITY_TOO_LARGE = 413, "Request Entity Too Large"
REQUEST_URI_TOO_LONG = 414, "Request-URI Too Long"
CONTENT_TOO_LARGE = 413, "Content Too Large"
URI_TOO_LONG = 414, "URI Too Long"
UNSUPPORTED_MEDIA_TYPE = 415, "Unsupported Media Type"
REQUESTED_RANGE_NOT_SATISFIABLE = 416, "Requested Range Not Satisfiable"
RANGE_NOT_SATISFIABLE = 416, "Range Not Satisfiable"
EXPECTATION_FAILED = 417, "Expectation Failed"
IM_A_TEAPOT = 418, "I'm a teapot"
MISDIRECTED_REQUEST = 421, "Misdirected Request"
UNPROCESSABLE_ENTITY = 422, "Unprocessable Entity"
UNPROCESSABLE_CONTENT = 422, "Unprocessable Content"
LOCKED = 423, "Locked"
FAILED_DEPENDENCY = 424, "Failed Dependency"
TOO_EARLY = 425, "Too Early"
Expand Down
33 changes: 32 additions & 1 deletion tests/httpx2/test_status_codes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

import httpx2


Expand All @@ -16,7 +18,7 @@ def test_status_code_phrase_lookup() -> None:


def test_lowercase_status_code() -> None:
assert httpx2.codes.not_found == 404 # type: ignore
assert httpx2.codes.not_found == 404


def test_reason_phrase_for_status_code() -> None:
Expand All @@ -25,3 +27,32 @@ def test_reason_phrase_for_status_code() -> None:

def test_reason_phrase_for_unknown_status_code() -> None:
assert httpx2.codes.get_reason_phrase(499) == ""


def test_rfc9110_status_texts() -> None:
assert httpx2.codes.get_reason_phrase(413) == "Content Too Large"
assert httpx2.codes.get_reason_phrase(414) == "URI Too Long"
assert httpx2.codes.get_reason_phrase(416) == "Range Not Satisfiable"
assert httpx2.codes.get_reason_phrase(422) == "Unprocessable Content"


@pytest.mark.parametrize(
("old_name", "new_name"),
[
("REQUEST_ENTITY_TOO_LARGE", "CONTENT_TOO_LARGE"),
("REQUEST_URI_TOO_LONG", "URI_TOO_LONG"),
("REQUESTED_RANGE_NOT_SATISFIABLE", "RANGE_NOT_SATISFIABLE"),
("UNPROCESSABLE_ENTITY", "UNPROCESSABLE_CONTENT"),
],
)
def test_pre_rfc9110_aliases_are_deprecated(old_name: str, new_name: str) -> None:
# the pre-RFC 9110 constant names are kept as deprecated aliases
msg = f"'{old_name}' is deprecated. Use '{new_name}' instead."
with pytest.warns(DeprecationWarning, match=msg):
assert getattr(httpx2.codes, old_name) == httpx2.codes[new_name]


def test_unknown_status_code_attribute_raises() -> None:
name = "NOT_A_REAL_CODE"
with pytest.raises(AttributeError):
getattr(httpx2.codes, name)
Loading