Repository: encode/httpx

Issue:
Add equivalent to requests.response `ok` property
Hey there, I'm wondering do we have some equivalent of `requests.response:ok` property in `httpx` library? If no, Is it planning to be added sometime in the future? thx!


Discussion:
Not a core dev, so I can't comment on adding that particular feature, but the status codes are accessible via their upper and lower-case names in `httpx.codes`:
```py
In [1]: import httpx
   ...:
   ...: print(httpx.codes.ok)
200
```
So you can do:
```py
assert response.status_code == httpx.codes.ok
# or
if response.status_code != httpx.codes.ok:
    ...
```
And also, the compatible `response.raise_for_status()` exists, so you can do:
```py
try:
	response = httpx.get(...)
	response.raise_for_status()
except httpx.HTTPError as ex:
    ...
```
Personally I've not used `response.ok` before, and only done `raise_for_status()`, but I can see why that might be a handy check while developing.
> So you can do:
> 
> 
> ```python
> assert response.status_code == httpx.codes.ok
> # or
> if response.status_code != httpx.codes.ok:
>     ...
> ```

That is not equivalent to what ``response.ok`` does in ``requests``.

> And also, the compatible `response.raise_for_status()` exists, so you can do:
> 
> 
> ```python
> try:
> 	response = httpx.get(...)
> 	response.raise_for_status()
> except httpx.HTTPError as ex:
>     ...
> ```

``response.ok`` in ``requests`` just calls ``response.raise_for_status`` in a try/except and returns a boolean based on that. ``raise_for_status`` is equivalent between ``httpx`` and ``requests``.

> Personally I've not used `response.ok` before, and only done `raise_for_status()`, but I can see why that might be a handy check while developing.

I've never used ``response.ok`` from ``requests`` either.
I think it’s a bit confusing, as the naming kinda points towards “True if the status code was 200 OK”

I’d probably be okay with something like an `is_error` property, mirroring our existing `is_redirect` property.

I’m not sure about an `is_success`?
Yeah agree with posts above, @tomchristie I guess both `is_error` and `is_success` methods would be useful!
> `response.ok` in `requests` just calls `response.raise_for_status` in a try/except and returns a boolean based on that. `raise_for_status` is equivalent between `httpx` and `requests`.

Good point. Here's the relevant snippet:
https://github.com/psf/requests/blob/428f7a275914f60a8f1e76a7d69516d617433d30/requests/models.py#L697-L710
```py
class Response(object):
...
    @property
    def ok(self):
        """Returns True if :attr:`status_code` is less than 400, False if not.
        This attribute checks if the status code of the response is between
        400 and 600 to see if there was a client error or a server error. If
        the status code is between 200 and 400, this will return True. This
        is **not** a check to see if the response code is ``200 OK``.
        """
        try:
            self.raise_for_status()
        except HTTPError:
            return False
        return True
```
`is_error` and `is_success` could be good aliases, would need to be documented in compatibility.md

Resolving patch:
diff --git a/httpx/models.py b/httpx/models.py
--- a/httpx/models.py
+++ b/httpx/models.py
@@ -846,6 +846,10 @@ def decoder(self) -> Decoder:
 
         return self._decoder
 
+    @property
+    def is_error(self) -> bool:
+        return StatusCode.is_error(self.status_code)
+
     @property
     def is_redirect(self) -> bool:
         return StatusCode.is_redirect(self.status_code) and "location" in self.headers
@@ -861,11 +865,9 @@ def raise_for_status(self) -> None:
 
         if StatusCode.is_client_error(self.status_code):
             message = message.format(self, error_type="Client Error")
+            raise HTTPError(message, response=self)
         elif StatusCode.is_server_error(self.status_code):
             message = message.format(self, error_type="Server Error")
-        else:
-            message = ""
-        if message:
             raise HTTPError(message, response=self)
 
     def json(self, **kwargs: typing.Any) -> typing.Union[dict, list]:
diff --git a/httpx/status_codes.py b/httpx/status_codes.py
--- a/httpx/status_codes.py
+++ b/httpx/status_codes.py
@@ -49,6 +49,10 @@ def is_redirect(cls, value: int) -> bool:
             StatusCode.PERMANENT_REDIRECT,
         )
 
+    @classmethod
+    def is_error(cls, value: int) -> bool:
+        return 400 <= value <= 599
+
     @classmethod
     def is_client_error(cls, value: int) -> bool:
         return 400 <= value <= 499


Test patch:
diff --git a/tests/models/test_responses.py b/tests/models/test_responses.py
--- a/tests/models/test_responses.py
+++ b/tests/models/test_responses.py
@@ -23,6 +23,7 @@ def test_response():
     assert response.reason_phrase == "OK"
     assert response.text == "Hello, world!"
     assert response.elapsed == datetime.timedelta(0)
+    assert not response.is_error
 
 
 def test_response_repr():