Skip to content

Commit

Permalink
Single consistent name for status codes (#1088)
Browse files Browse the repository at this point in the history
* Single consistent name for status codes
* Gentle deprecation for httpx.StatusCode
  • Loading branch information
tomchristie authored Jul 27, 2020
1 parent 2e60e14 commit 35f09d1
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 17 deletions.
2 changes: 1 addition & 1 deletion httpx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"ResponseClosed",
"ResponseNotRead",
"RequestNotRead",
"StatusCode",
"StreamConsumed",
"StreamError",
"ProxyError",
Expand All @@ -90,7 +91,6 @@
"URL",
"URLLib3Transport",
"URLLib3ProxyTransport",
"StatusCode",
"Cookies",
"Headers",
"QueryParams",
Expand Down
12 changes: 6 additions & 6 deletions httpx/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
ResponseNotRead,
StreamConsumed,
)
from ._status_codes import StatusCode
from ._status_codes import codes
from ._types import (
CookieTypes,
HeaderTypes,
Expand Down Expand Up @@ -660,7 +660,7 @@ def elapsed(self) -> datetime.timedelta:

@property
def reason_phrase(self) -> str:
return StatusCode.get_reason_phrase(self.status_code)
return codes.get_reason_phrase(self.status_code)

@property
def url(self) -> typing.Optional[URL]:
Expand Down Expand Up @@ -758,11 +758,11 @@ def decoder(self) -> Decoder:

@property
def is_error(self) -> bool:
return StatusCode.is_error(self.status_code)
return codes.is_error(self.status_code)

@property
def is_redirect(self) -> bool:
return StatusCode.is_redirect(self.status_code) and "location" in self.headers
return codes.is_redirect(self.status_code) and "location" in self.headers

def raise_for_status(self) -> None:
"""
Expand All @@ -773,10 +773,10 @@ def raise_for_status(self) -> None:
"For more information check: https://httpstatuses.com/{0.status_code}"
)

if StatusCode.is_client_error(self.status_code):
if codes.is_client_error(self.status_code):
message = message.format(self, error_type="Client Error")
raise HTTPStatusError(message, response=self)
elif StatusCode.is_server_error(self.status_code):
elif codes.is_server_error(self.status_code):
message = message.format(self, error_type="Server Error")
raise HTTPStatusError(message, response=self)

Expand Down
39 changes: 29 additions & 10 deletions httpx/_status_codes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import warnings
from enum import IntEnum


class StatusCode(IntEnum):
class codes(IntEnum):
"""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
Expand All @@ -17,7 +18,7 @@ class StatusCode(IntEnum):
* RFC 7725: An HTTP Status Code to Report Legal Obstacles
"""

def __new__(cls, value: int, phrase: str = "") -> "StatusCode":
def __new__(cls, value: int, phrase: str = "") -> "codes":
obj = int.__new__(cls, value) # type: ignore
obj._value_ = value

Expand All @@ -30,23 +31,23 @@ def __str__(self) -> str:
@classmethod
def get_reason_phrase(cls, value: int) -> str:
try:
return StatusCode(value).phrase # type: ignore
return codes(value).phrase # type: ignore
except ValueError:
return ""

@classmethod
def is_redirect(cls, value: int) -> bool:
return value in (
# 301 (Cacheable redirect. Method may change to GET.)
StatusCode.MOVED_PERMANENTLY,
codes.MOVED_PERMANENTLY,
# 302 (Uncacheable redirect. Method may change to GET.)
StatusCode.FOUND,
codes.FOUND,
# 303 (Client should make a GET or HEAD request.)
StatusCode.SEE_OTHER,
codes.SEE_OTHER,
# 307 (Equiv. 302, but retain method)
StatusCode.TEMPORARY_REDIRECT,
codes.TEMPORARY_REDIRECT,
# 308 (Equiv. 301, but retain method)
StatusCode.PERMANENT_REDIRECT,
codes.PERMANENT_REDIRECT,
)

@classmethod
Expand Down Expand Up @@ -132,8 +133,26 @@ def is_server_error(cls, value: int) -> bool:
NETWORK_AUTHENTICATION_REQUIRED = 511, "Network Authentication Required"


codes = StatusCode

#  Include lower-case styles for `requests` compatibility.
for code in codes:
setattr(codes, code._name_.lower(), int(code))


class StatusCodeCompat:
def __call__(self, *args, **kwargs): # type: ignore
message = "`httpx.StatusCode` is deprecated. Use `httpx.codes` instead."
warnings.warn(message, DeprecationWarning)
return codes(*args, **kwargs)

def __getattr__(self, attr): # type: ignore
message = "`httpx.StatusCode` is deprecated. Use `httpx.codes` instead."
warnings.warn(message, DeprecationWarning)
return getattr(codes, attr)

def __getitem__(self, item): # type: ignore
message = "`httpx.StatusCode` is deprecated. Use `httpx.codes` instead."
warnings.warn(message, DeprecationWarning)
return codes[item]


StatusCode = StatusCodeCompat()
21 changes: 21 additions & 0 deletions tests/test_status_codes.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

import httpx


Expand All @@ -6,6 +8,14 @@ def test_status_code_as_int():
assert str(httpx.codes.NOT_FOUND) == "404"


def test_status_code_value_lookup():
assert httpx.codes(404) == 404


def test_status_code_phrase_lookup():
assert httpx.codes["NOT_FOUND"] == 404


def test_lowercase_status_code():
assert httpx.codes.not_found == 404 # type: ignore

Expand All @@ -16,3 +26,14 @@ def test_reason_phrase_for_status_code():

def test_reason_phrase_for_unknown_status_code():
assert httpx.codes.get_reason_phrase(499) == ""


def test_deprecated_status_code_class():
with pytest.warns(DeprecationWarning):
assert httpx.StatusCode.NOT_FOUND == 404

with pytest.warns(DeprecationWarning):
assert httpx.StatusCode(404) == 404

with pytest.warns(DeprecationWarning):
assert httpx.StatusCode["NOT_FOUND"] == 404

0 comments on commit 35f09d1

Please sign in to comment.