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)
response.history = list(history)
- if not response.is_redirect:
+ if not response.has_redirect_location:
return response
request = self._build_redirect_request(request, response)
return self._decoder
+ @property
+ 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:
+ """
+ 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 is_redirect(self) -> bool:
- return codes.is_redirect(self.status_code) and "location" in self.headers
+ 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(
"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:
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"
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.