Skip to content

Commit 3497ce5

Browse files
Handle more HTTP-related errors
Use JustWatchHttpError for all HTTP-related errors, not just error status codes.
1 parent 436e2d5 commit 3497ce5

6 files changed

Lines changed: 72 additions & 51 deletions

File tree

docs/usage.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ Examples of parsed responses are in the GitHub repository in
3333

3434
Each function can raise two exceptions:
3535

36-
- [`JustWatchHttpError`](#http-errors) - JustWatch API responded with non-`2xx` code.
37-
- [`JustWatchApiError`](#api-errors) - JSON response from JustWatch API contains
38-
errors.
36+
- [`JustWatchHttpError`](#http-errors) - HTTP-related error occurred.
37+
- [`JustWatchApiError`](#api-errors) - JSON response contains errors.
3938

4039
You can check [Exceptions](API Reference/exceptions.md) page for more details.
4140

@@ -279,7 +278,8 @@ Example function call and its output is in
279278
### HTTP errors
280279

281280
[`JustWatchHttpError`][simplejustwatchapi.exceptions.JustWatchHttpError]{data-preview}
282-
is raised when JustWatch API responds with non-`2xx` status code.
281+
is raised when HTTP-related error occurs, e.g., JustWatch API responds with
282+
non-`2xx` status code.
283283

284284
Non-`2xx` response status codes can happen when trying to use incorrect type for
285285
parameters, e.g., trying to use a non-numeric string for `count`:
@@ -290,8 +290,7 @@ from simplejustwatchapi import search, JustWatchHttpError
290290
try:
291291
results = search("The Matrix", count="five")
292292
except JustWatchHttpError as e:
293-
print(e.code, e.message)
294-
# In this case "e.message" is a JSON, but handled as a regular string.
293+
print(str(e))
295294
```
296295

297296
!!! note "Numeric strings instead of `int`"
@@ -309,8 +308,7 @@ except JustWatchHttpError as e:
309308
### API errors
310309

311310
[`JustWatchApiError`][simplejustwatchapi.exceptions.JustWatchApiError]{data-preview} is
312-
raised when JustWatch API **does** respond with `2xx` status code, but the internal JSON
313-
response contain errors.
311+
raised when no HTTP-related errors occur, but the internal JSON response contain errors.
314312

315313
API errors can occur for invalid country code:
316314
```python

src/simplejustwatchapi/exceptions.py

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,7 @@
44
All exceptions inherit from [`JustWatchError`]
55
[simplejustwatchapi.exceptions.JustWatchError] for easier catching.
66
7-
Specific exceptions are raised for non-`2xx` HTTP response status codes and GraphQL
8-
API response errors.
9-
10-
Each exception includes relevant information for why it was raised, but not always in
11-
any particular parsed format. For example, [`JustWatchApiError`]
12-
[simplejustwatchapi.exceptions.JustWatchApiError] includes the list of errors from the
13-
API response, but stored as a `dict`/JSON, as it was received from the API.
14-
7+
Specific exceptions are raised for HTTP-related errors and GraphQL API response errors.
158
"""
169

1710

@@ -23,7 +16,7 @@ class JustWatchApiError(JustWatchError):
2316
"""
2417
Raised when JustWatch API returned errors in JSON response.
2518
26-
If this error is raised, then API responded with status code `2xx`, but there are
19+
If this error is raised, then no HTTP-related error occurred, but there are
2720
listed errors in the internal JSON response. It can happen for too high complexity
2821
of request, invalid node ID in functions like [`details`]
2922
[simplejustwatchapi.justwatch.details], or invalid country or language codes.
@@ -43,18 +36,8 @@ def __init__(self, errors: list[dict]) -> None:
4336

4437
class JustWatchHttpError(JustWatchError):
4538
"""
46-
Raised when JustWatch API returned a non-`2xx` status code.
47-
48-
Any additional verification is not performed, ony the status code is checked.
49-
50-
Attributes:
51-
code (int): HTTP status code returned by the API.
52-
message (str): HTTP message response from the API.
39+
Raised when HTTP-related error occurs.
5340
41+
This is a general exception for any HTTP-related errors, such as non-`2xx` status
42+
codes, network errors, timeouts, etc.
5443
"""
55-
56-
def __init__(self, code: int, message: str) -> None:
57-
"""Init JustWatchHttpError with status code and message from response."""
58-
super().__init__(f"HTTP code {code}: {message}")
59-
self.code = code
60-
self.message = message

src/simplejustwatchapi/justwatch.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@
4141
| Exception | Cause |
4242
|-----------|-------|
4343
| [`JustWatchHttpError`][simplejustwatchapi.exceptions.JustWatchHttpError] | \
44-
JustWatch API responded with non-`2xx` code. |
44+
HTTP error occurred, e.g., JustWatch API responded with non-`2xx` status code. |
4545
| [`JustWatchApiError`][simplejustwatchapi.exceptions.JustWatchApiError] | \
46-
JSON response from JustWatch API contains errors (e.g., due to invalid language or \
47-
country code). If this exception is raised, then API responded with `2xx` code. |
46+
JSON response from JustWatch API contains errors, e.g., due to invalid language or \
47+
country code. |
4848
"""
4949

50-
from httpx import post
50+
from httpx import HTTPError, post
5151

5252
from simplejustwatchapi.exceptions import JustWatchHttpError
5353
from simplejustwatchapi.query import (
@@ -154,7 +154,8 @@ def search(
154154
Raises:
155155
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
156156
due to invalid language or country code.
157-
exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
157+
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
158+
responded with non-`2xx` status code.
158159
159160
"""
160161
request = prepare_search_request(
@@ -238,7 +239,8 @@ def popular(
238239
Raises:
239240
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
240241
due to invalid language or country code.
241-
exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
242+
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
243+
responded with non-`2xx` status code.
242244
243245
"""
244246
request = prepare_popular_request(
@@ -307,7 +309,8 @@ def details(
307309
Raises:
308310
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
309311
due to invalid language or country code.
310-
exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
312+
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
313+
responded with non-`2xx` status code.
311314
312315
"""
313316
request = prepare_details_request(node_id, country, language, best_only)
@@ -355,7 +358,8 @@ def seasons(
355358
Raises:
356359
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
357360
due to invalid language or country code.
358-
exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
361+
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
362+
responded with non-`2xx` status code.
359363
360364
"""
361365
request = prepare_seasons_request(show_id, country, language, best_only)
@@ -404,7 +408,8 @@ def episodes(
404408
Raises:
405409
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
406410
due to invalid language or country code.
407-
exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
411+
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
412+
responded with non-`2xx` status code.
408413
409414
"""
410415
request = prepare_episodes_request(season_id, country, language, best_only)
@@ -472,7 +477,8 @@ def offers_for_countries(
472477
Raises:
473478
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
474479
due to invalid language or country code.
475-
exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
480+
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
481+
responded with non-`2xx` status code.
476482
477483
"""
478484
if not countries:
@@ -506,7 +512,8 @@ def providers(country: str = "US") -> list[OfferPackage]:
506512
Raises:
507513
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
508514
due to invalid language or country code.
509-
exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
515+
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
516+
responded with non-`2xx` status code.
510517
511518
"""
512519
request = prepare_providers_request(country)
@@ -525,10 +532,12 @@ def _post_to_jw_graphql_api(request_json: dict) -> dict:
525532
(dict): JSON response from the API.
526533
527534
Raises:
528-
exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
535+
exceptions.JustWatchHttpError: HTTP-related error occurred.
529536
530537
"""
531-
response = post(_GRAPHQL_API_URL, json=request_json)
532-
if not response.is_success:
533-
raise JustWatchHttpError(response.status_code, response.text)
538+
try:
539+
response = post(_GRAPHQL_API_URL, json=request_json)
540+
response.raise_for_status()
541+
except HTTPError as e:
542+
raise JustWatchHttpError(str(e)) from e
534543
return response.json()

src/simplejustwatchapi/query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Functions are prepared in pairs - prepare request and parse response for specific
55
operation. "Request" functions do no verification of input data; "parse" functions check
66
if returned JSON/`dict` contain `error` key. In such case a [`JustWatchApiError`]
7-
[simplejustwatchapi.erxceptions.JustWatchApiError] is raised.
7+
[simplejustwatchapi.exceptions.JustWatchApiError] is raised.
88
99
All "parse" functions convert JSON returned by API into request-specific Python
1010
[`NamedTuple`][typing.NamedTuple].

src/simplejustwatchapi/tuples.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class OfferPackage(NamedTuple):
2424
function to return data about all available providers.
2525
2626
Attributes:
27-
id (str): ID of the provider/plaform for this offer.
27+
id (str): ID of the provider/platform for this offer.
2828
package_id (int): Package ID. I'm not sure how it's different from regular `id`.
2929
name (str): Name of the platform in format suited to display for users.
3030
technical_name (str): Technical name of the platform,

test/simplejustwatchapi/test_justwatch.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from unittest.mock import MagicMock, patch
22

3+
from httpx import Request, RequestError, Response
34
from pytest import fixture, mark, raises
45

56
from simplejustwatchapi.exceptions import JustWatchHttpError
@@ -31,15 +32,27 @@
3132
def post_mock_success(mocker):
3233
post_mock = mocker.patch("simplejustwatchapi.justwatch.post")
3334
post_mock.return_value.json.return_value = DUMMY_RESPONSE
34-
post_mock.return_value.is_success = True
3535
yield post_mock
3636
post_mock.assert_called_with(JUSTWATCH_GRAPHQL_URL, json=REQUEST)
3737

3838

3939
@fixture
40-
def post_mock_failure(mocker):
40+
def post_mock_request_error(mocker):
4141
post_mock = mocker.patch("simplejustwatchapi.justwatch.post")
42-
post_mock.return_value.is_success = False
42+
post_mock.side_effect = RequestError("HTTP request error")
43+
mock_request = Request(method="POST", url=JUSTWATCH_GRAPHQL_URL)
44+
post_mock.return_value = Response(status_code=200, request=mock_request)
45+
# Technically setting the return value is not necessary, since the side effect will
46+
# be raised before the return value is used, but it can be useful for debugging if
47+
# the test fails due to the side effect not being raised for some reason.
48+
return post_mock
49+
50+
51+
@fixture
52+
def post_mock_status_error(mocker):
53+
post_mock = mocker.patch("simplejustwatchapi.justwatch.post")
54+
mock_request = Request(method="POST", url=JUSTWATCH_GRAPHQL_URL)
55+
post_mock.return_value = Response(status_code=420, request=mock_request)
4356
return post_mock
4457

4558

@@ -143,7 +156,25 @@ def test_providers(requests_mock, parser_mock, post_mock_success):
143156
("prepare_providers_request", providers, (PROVIDERS_INPUT,)),
144157
],
145158
)
146-
def test_search_raises_http_error(prepare_name, function, inputs, post_mock_failure):
159+
def test_http_request_error(prepare_name, function, inputs, post_mock_request_error):
160+
full_mock_name = f"simplejustwatchapi.justwatch.{prepare_name}"
161+
with patch(full_mock_name), raises(JustWatchHttpError):
162+
function(*inputs)
163+
164+
165+
@mark.parametrize(
166+
argnames=("prepare_name", "function", "inputs"),
167+
argvalues=[
168+
("prepare_search_request", search, SEARCH_INPUT),
169+
("prepare_popular_request", popular, POPULAR_INPUT),
170+
("prepare_details_request", details, DETAILS_INPUT),
171+
("prepare_seasons_request", seasons, DETAILS_INPUT),
172+
("prepare_episodes_request", episodes, DETAILS_INPUT),
173+
("prepare_offers_for_countries_request", offers_for_countries, OFFERS_INPUT),
174+
("prepare_providers_request", providers, (PROVIDERS_INPUT,)),
175+
],
176+
)
177+
def test_http_status_error(prepare_name, function, inputs, post_mock_status_error):
147178
full_mock_name = f"simplejustwatchapi.justwatch.{prepare_name}"
148179
with patch(full_mock_name), raises(JustWatchHttpError):
149180
function(*inputs)

0 commit comments

Comments
 (0)