is_informational / is_success / is_redirect / is_client_error / is_server_error (#1854)
This commit is contained in:
parent
ff9813e84d
commit
a761e17abc
@ -945,7 +945,7 @@ class Client(BaseClient):
|
||||
hook(response)
|
||||
response.history = list(history)
|
||||
|
||||
if not response.is_redirect:
|
||||
if not response.has_redirect_location:
|
||||
return response
|
||||
|
||||
request = self._build_redirect_request(request, response)
|
||||
@ -1640,7 +1640,7 @@ class AsyncClient(BaseClient):
|
||||
|
||||
response.history = list(history)
|
||||
|
||||
if not response.is_redirect:
|
||||
if not response.has_redirect_location:
|
||||
return response
|
||||
|
||||
request = self._build_redirect_request(request, response)
|
||||
|
||||
104
httpx/_models.py
104
httpx/_models.py
@ -1400,22 +1400,79 @@ class Response:
|
||||
return self._decoder
|
||||
|
||||
@property
|
||||
def is_error(self) -> bool:
|
||||
return codes.is_error(self.status_code)
|
||||
def is_informational(self) -> bool:
|
||||
"""
|
||||
A property which is `True` for 1xx status codes, `False` otherwise.
|
||||
"""
|
||||
return codes.is_informational(self.status_code)
|
||||
|
||||
@property
|
||||
def is_success(self) -> bool:
|
||||
"""
|
||||
A property which is `True` for 2xx status codes, `False` otherwise.
|
||||
"""
|
||||
return codes.is_success(self.status_code)
|
||||
|
||||
@property
|
||||
def is_redirect(self) -> bool:
|
||||
return codes.is_redirect(self.status_code) and "location" in self.headers
|
||||
"""
|
||||
A property which is `True` for 3xx status codes, `False` otherwise.
|
||||
|
||||
Note that not all responses with a 3xx status code indicate a URL redirect.
|
||||
|
||||
Use `response.has_redirect_location` to determine responses with a properly
|
||||
formed URL redirection.
|
||||
"""
|
||||
return codes.is_redirect(self.status_code)
|
||||
|
||||
@property
|
||||
def is_client_error(self) -> bool:
|
||||
"""
|
||||
A property which is `True` for 4xx status codes, `False` otherwise.
|
||||
"""
|
||||
return codes.is_client_error(self.status_code)
|
||||
|
||||
@property
|
||||
def is_server_error(self) -> bool:
|
||||
"""
|
||||
A property which is `True` for 5xx status codes, `False` otherwise.
|
||||
"""
|
||||
return codes.is_server_error(self.status_code)
|
||||
|
||||
@property
|
||||
def is_error(self) -> bool:
|
||||
"""
|
||||
A property which is `True` for 4xx and 5xx status codes, `False` otherwise.
|
||||
"""
|
||||
return codes.is_error(self.status_code)
|
||||
|
||||
@property
|
||||
def has_redirect_location(self) -> bool:
|
||||
"""
|
||||
Returns True for 3xx responses with a properly formed URL redirection,
|
||||
`False` otherwise.
|
||||
"""
|
||||
return (
|
||||
self.status_code
|
||||
in (
|
||||
# 301 (Cacheable redirect. Method may change to GET.)
|
||||
codes.MOVED_PERMANENTLY,
|
||||
# 302 (Uncacheable redirect. Method may change to GET.)
|
||||
codes.FOUND,
|
||||
# 303 (Client should make a GET or HEAD request.)
|
||||
codes.SEE_OTHER,
|
||||
# 307 (Equiv. 302, but retain method)
|
||||
codes.TEMPORARY_REDIRECT,
|
||||
# 308 (Equiv. 301, but retain method)
|
||||
codes.PERMANENT_REDIRECT,
|
||||
)
|
||||
and "Location" in self.headers
|
||||
)
|
||||
|
||||
def raise_for_status(self) -> None:
|
||||
"""
|
||||
Raise the `HTTPStatusError` if one occurred.
|
||||
"""
|
||||
message = (
|
||||
"{0.status_code} {error_type}: {0.reason_phrase} for url: {0.url}\n"
|
||||
"For more information check: https://httpstatuses.com/{0.status_code}"
|
||||
)
|
||||
|
||||
request = self._request
|
||||
if request is None:
|
||||
raise RuntimeError(
|
||||
@ -1423,12 +1480,31 @@ class Response:
|
||||
"instance has not been set on this response."
|
||||
)
|
||||
|
||||
if codes.is_client_error(self.status_code):
|
||||
message = message.format(self, error_type="Client Error")
|
||||
raise HTTPStatusError(message, request=request, response=self)
|
||||
elif codes.is_server_error(self.status_code):
|
||||
message = message.format(self, error_type="Server Error")
|
||||
raise HTTPStatusError(message, request=request, response=self)
|
||||
if self.is_success:
|
||||
return
|
||||
|
||||
if self.has_redirect_location:
|
||||
message = (
|
||||
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
|
||||
"Redirect location: '{0.headers[location]}'\n"
|
||||
"For more information check: https://httpstatuses.com/{0.status_code}"
|
||||
)
|
||||
else:
|
||||
message = (
|
||||
"{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
|
||||
"For more information check: https://httpstatuses.com/{0.status_code}"
|
||||
)
|
||||
|
||||
status_class = self.status_code // 100
|
||||
error_types = {
|
||||
1: "Informational response",
|
||||
3: "Redirect response",
|
||||
4: "Client error",
|
||||
5: "Server error",
|
||||
}
|
||||
error_type = error_types.get(status_class, "Invalid status code")
|
||||
message = message.format(self, error_type=error_type)
|
||||
raise HTTPStatusError(message, request=request, response=self)
|
||||
|
||||
def json(self, **kwargs: typing.Any) -> typing.Any:
|
||||
if self.charset_encoding is None and self.content and len(self.content) > 3:
|
||||
|
||||
@ -39,32 +39,47 @@ class codes(IntEnum):
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def is_redirect(cls, value: int) -> bool:
|
||||
return value in (
|
||||
# 301 (Cacheable redirect. Method may change to GET.)
|
||||
codes.MOVED_PERMANENTLY,
|
||||
# 302 (Uncacheable redirect. Method may change to GET.)
|
||||
codes.FOUND,
|
||||
# 303 (Client should make a GET or HEAD request.)
|
||||
codes.SEE_OTHER,
|
||||
# 307 (Equiv. 302, but retain method)
|
||||
codes.TEMPORARY_REDIRECT,
|
||||
# 308 (Equiv. 301, but retain method)
|
||||
codes.PERMANENT_REDIRECT,
|
||||
)
|
||||
def is_informational(cls, value: int) -> bool:
|
||||
"""
|
||||
Returns `True` for 1xx status codes, `False` otherwise.
|
||||
"""
|
||||
return 100 <= value <= 199
|
||||
|
||||
@classmethod
|
||||
def is_error(cls, value: int) -> bool:
|
||||
return 400 <= value <= 599
|
||||
def is_success(cls, value: int) -> bool:
|
||||
"""
|
||||
Returns `True` for 2xx status codes, `False` otherwise.
|
||||
"""
|
||||
return 200 <= value <= 299
|
||||
|
||||
@classmethod
|
||||
def is_redirect(cls, value: int) -> bool:
|
||||
"""
|
||||
Returns `True` for 3xx status codes, `False` otherwise.
|
||||
"""
|
||||
return 300 <= value <= 399
|
||||
|
||||
@classmethod
|
||||
def is_client_error(cls, value: int) -> bool:
|
||||
"""
|
||||
Returns `True` for 4xx status codes, `False` otherwise.
|
||||
"""
|
||||
return 400 <= value <= 499
|
||||
|
||||
@classmethod
|
||||
def is_server_error(cls, value: int) -> bool:
|
||||
"""
|
||||
Returns `True` for 5xx status codes, `False` otherwise.
|
||||
"""
|
||||
return 500 <= value <= 599
|
||||
|
||||
@classmethod
|
||||
def is_error(cls, value: int) -> bool:
|
||||
"""
|
||||
Returns `True` for 4xx or 5xx status codes, `False` otherwise.
|
||||
"""
|
||||
return 400 <= value <= 599
|
||||
|
||||
# informational
|
||||
CONTINUE = 100, "Continue"
|
||||
SWITCHING_PROTOCOLS = 101, "Switching Protocols"
|
||||
|
||||
@ -90,15 +90,49 @@ def test_raise_for_status():
|
||||
response = httpx.Response(200, request=request)
|
||||
response.raise_for_status()
|
||||
|
||||
# 1xx status codes are informational responses.
|
||||
response = httpx.Response(101, request=request)
|
||||
assert response.is_informational
|
||||
with pytest.raises(httpx.HTTPStatusError) as exc_info:
|
||||
response.raise_for_status()
|
||||
assert str(exc_info.value) == (
|
||||
"Informational response '101 Switching Protocols' for url 'https://example.org'\n"
|
||||
"For more information check: https://httpstatuses.com/101"
|
||||
)
|
||||
|
||||
# 3xx status codes are redirections.
|
||||
headers = {"location": "https://other.org"}
|
||||
response = httpx.Response(303, headers=headers, request=request)
|
||||
assert response.is_redirect
|
||||
with pytest.raises(httpx.HTTPStatusError) as exc_info:
|
||||
response.raise_for_status()
|
||||
assert str(exc_info.value) == (
|
||||
"Redirect response '303 See Other' for url 'https://example.org'\n"
|
||||
"Redirect location: 'https://other.org'\n"
|
||||
"For more information check: https://httpstatuses.com/303"
|
||||
)
|
||||
|
||||
# 4xx status codes are a client error.
|
||||
response = httpx.Response(403, request=request)
|
||||
with pytest.raises(httpx.HTTPStatusError):
|
||||
assert response.is_client_error
|
||||
assert response.is_error
|
||||
with pytest.raises(httpx.HTTPStatusError) as exc_info:
|
||||
response.raise_for_status()
|
||||
assert str(exc_info.value) == (
|
||||
"Client error '403 Forbidden' for url 'https://example.org'\n"
|
||||
"For more information check: https://httpstatuses.com/403"
|
||||
)
|
||||
|
||||
# 5xx status codes are a server error.
|
||||
response = httpx.Response(500, request=request)
|
||||
with pytest.raises(httpx.HTTPStatusError):
|
||||
assert response.is_server_error
|
||||
assert response.is_error
|
||||
with pytest.raises(httpx.HTTPStatusError) as exc_info:
|
||||
response.raise_for_status()
|
||||
assert str(exc_info.value) == (
|
||||
"Server error '500 Internal Server Error' for url 'https://example.org'\n"
|
||||
"For more information check: https://httpstatuses.com/500"
|
||||
)
|
||||
|
||||
# Calling .raise_for_status without setting a request instance is
|
||||
# not valid. Should raise a runtime error.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user