simple-justwatch-python-api 1.1.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/PKG-INFO +1 -1
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/pyproject.toml +2 -2
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/src/simplejustwatchapi/exceptions.py +11 -0
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/src/simplejustwatchapi/justwatch.py +92 -15
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/src/simplejustwatchapi/query.py +36 -6
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/src/simplejustwatchapi/tuples.py +3 -2
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/LICENSE +0 -0
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/README.md +0 -0
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/src/simplejustwatchapi/__init__.py +0 -0
- {simple_justwatch_python_api-1.1.0 → simple_justwatch_python_api-1.3.0}/src/simplejustwatchapi/graphql.py +0 -0
|
@@ -3,7 +3,7 @@ name = "simple-justwatch-python-api"
|
|
|
3
3
|
authors = [
|
|
4
4
|
{ name = "Electronic Mango", email = "78230210+Electronic-Mango@users.noreply.github.com" },
|
|
5
5
|
]
|
|
6
|
-
version = "1.
|
|
6
|
+
version = "1.3.0"
|
|
7
7
|
description = "A simple JustWatch Python API"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
license = "MIT"
|
|
@@ -30,7 +30,7 @@ dev = [
|
|
|
30
30
|
"pytest>=9.0.3",
|
|
31
31
|
"pytest-mock>=3.15.1",
|
|
32
32
|
"ruff>=0.15.10",
|
|
33
|
-
"zensical>=0.0.
|
|
33
|
+
"zensical>=0.0.37",
|
|
34
34
|
]
|
|
35
35
|
|
|
36
36
|
[project.urls]
|
|
@@ -40,4 +40,15 @@ class JustWatchHttpError(JustWatchError):
|
|
|
40
40
|
|
|
41
41
|
This is a general exception for any HTTP-related errors, such as non-`2xx` status
|
|
42
42
|
codes, network errors, timeouts, etc.
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
msg (str): Error message describing the HTTP error.
|
|
46
|
+
response (str | None): Optional text of the HTTP response, if available.
|
|
47
|
+
Usucally contains JSON with error responses from the API.
|
|
48
|
+
|
|
43
49
|
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, msg: str, response: str | None = None) -> None:
|
|
52
|
+
"""Init JustWatchHttpError with error message and optional response text."""
|
|
53
|
+
super().__init__(msg)
|
|
54
|
+
self.response = response
|
|
@@ -9,11 +9,11 @@ prepared GraphQL query.
|
|
|
9
9
|
Most functions have a number of common arguments (in addition to function-specific
|
|
10
10
|
ones, like `title` to search for):
|
|
11
11
|
|
|
12
|
-
| Name
|
|
13
|
-
|
|
14
|
-
| `country`
|
|
12
|
+
| Name | Description |
|
|
13
|
+
|------|-------------|
|
|
14
|
+
| `country` | 2-letter country code for which offers are selected, (e.g., `US`, \
|
|
15
15
|
`GB`, `DE`). |
|
|
16
|
-
| `language`
|
|
16
|
+
| `language` | Code for language in responses. It consists of 2 lowercase letters \
|
|
17
17
|
with optional uppercase alphanumeric suffix (e.g., `en`, `en-US`, \
|
|
18
18
|
`de`, `de-CH1901`). |
|
|
19
19
|
| `best_only` | Whether to return only "best" offers for each provider instead of, \
|
|
@@ -22,19 +22,23 @@ ones, like `title` to search for):
|
|
|
22
22
|
Functions returning data for multiple titles
|
|
23
23
|
([`search`][simplejustwatchapi.justwatch.search],
|
|
24
24
|
[`popular`][simplejustwatchapi.justwatch.popular])
|
|
25
|
-
also allow for specifying number of elements, basic pagination, and
|
|
26
|
-
|
|
25
|
+
also allow for specifying number of elements, basic pagination, and additional
|
|
26
|
+
filtering:
|
|
27
27
|
|
|
28
|
-
| Name
|
|
29
|
-
|
|
30
|
-
| `count`
|
|
31
|
-
| `offset`
|
|
28
|
+
| Name | Description |
|
|
29
|
+
|------|-------------|
|
|
30
|
+
| `count` | How many entries should be returned. |
|
|
31
|
+
| `offset` | Basic "pagination". Offset for the first returned result, i.e. how \
|
|
32
32
|
many first entries should be skipped. Everything is handled on API \
|
|
33
33
|
side, this library isn't doing any filtering. |
|
|
34
34
|
| `providers` | Providers (like Netflix, Amazon Prime Video) for which offers should \
|
|
35
35
|
returned. Requires 3-letter "short name". Check \
|
|
36
36
|
[`providers`][simplejustwatchapi.justwatch.providers] for an example \
|
|
37
|
-
of how you can get that value.
|
|
37
|
+
of how you can get that value. |
|
|
38
|
+
| `min_release_year` | Minimum release year of returned titles. |
|
|
39
|
+
| `max_release_year` | Maximum release year of returned titles. |
|
|
40
|
+
| `object_types` | Types of objects to filter for. It seems that only `SHOW` and \
|
|
41
|
+
`MOVIE` are useful, but it's not strictly enforced. |
|
|
38
42
|
|
|
39
43
|
Each function can raise two exceptions:
|
|
40
44
|
|
|
@@ -47,7 +51,7 @@ Each function can raise two exceptions:
|
|
|
47
51
|
country code. |
|
|
48
52
|
"""
|
|
49
53
|
|
|
50
|
-
from httpx import HTTPError, post
|
|
54
|
+
from httpx import HTTPError, HTTPStatusError, post
|
|
51
55
|
|
|
52
56
|
from simplejustwatchapi.exceptions import JustWatchHttpError
|
|
53
57
|
from simplejustwatchapi.query import (
|
|
@@ -79,6 +83,9 @@ def search(
|
|
|
79
83
|
best_only: bool = True,
|
|
80
84
|
offset: int = 0,
|
|
81
85
|
providers: list[str] | str | None = None,
|
|
86
|
+
min_release_year: int | None = None,
|
|
87
|
+
max_release_year: int | None = None,
|
|
88
|
+
object_types: list[str] | str | None = None,
|
|
82
89
|
) -> list[MediaEntry]:
|
|
83
90
|
"""
|
|
84
91
|
Search JustWatch for the given title.
|
|
@@ -148,18 +155,50 @@ def search(
|
|
|
148
155
|
You can look up values through [`providers`]
|
|
149
156
|
[simplejustwatchapi.justwatch.providers] function.
|
|
150
157
|
|
|
158
|
+
min_release_year (int | None): Minimum release year of returned titles.
|
|
159
|
+
|
|
160
|
+
If `None` (the default value), no filtering is done.
|
|
161
|
+
|
|
162
|
+
max_release_year (int | None): Maximum release year of returned titles.
|
|
163
|
+
|
|
164
|
+
If `None` (the default value), no filtering is done.
|
|
165
|
+
|
|
166
|
+
object_types (list[str] | str | None): Types of objects to filter for, like
|
|
167
|
+
`SHOW` or `MOVIE`.
|
|
168
|
+
|
|
169
|
+
It seems that only `SHOW` and `MOVIE` are useful, but it's not strictly
|
|
170
|
+
enforced. Types like `SHOW_EPISODE`, or `SHOW_SEASON` can be used, but they
|
|
171
|
+
seem to return TV shows, same as `SHOW`.
|
|
172
|
+
|
|
173
|
+
While the type value is not enforced, it **must** be a valid type, otherwise
|
|
174
|
+
API will respond with HTTP status code 422.
|
|
175
|
+
|
|
176
|
+
For single type it can be a single string, or a list with one string.
|
|
177
|
+
|
|
178
|
+
If `None` (the default value), no filtering is done.
|
|
179
|
+
|
|
151
180
|
Returns:
|
|
152
181
|
(list[MediaEntry]): List of tuples with details of search results.
|
|
153
182
|
|
|
154
183
|
Raises:
|
|
155
184
|
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
|
|
156
185
|
due to invalid language or country code.
|
|
186
|
+
|
|
157
187
|
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
|
|
158
188
|
responded with non-`2xx` status code.
|
|
159
189
|
|
|
160
190
|
"""
|
|
161
191
|
request = prepare_search_request(
|
|
162
|
-
title,
|
|
192
|
+
title,
|
|
193
|
+
country,
|
|
194
|
+
language,
|
|
195
|
+
count,
|
|
196
|
+
best_only,
|
|
197
|
+
offset,
|
|
198
|
+
providers,
|
|
199
|
+
min_release_year,
|
|
200
|
+
max_release_year,
|
|
201
|
+
object_types,
|
|
163
202
|
)
|
|
164
203
|
response = _post_to_jw_graphql_api(request)
|
|
165
204
|
return parse_search_response(response)
|
|
@@ -172,6 +211,9 @@ def popular(
|
|
|
172
211
|
best_only: bool = True,
|
|
173
212
|
offset: int = 0,
|
|
174
213
|
providers: list[str] | str | None = None,
|
|
214
|
+
min_release_year: int | None = None,
|
|
215
|
+
max_release_year: int | None = None,
|
|
216
|
+
object_types: list[str] | str | None = None,
|
|
175
217
|
) -> list[MediaEntry]:
|
|
176
218
|
"""
|
|
177
219
|
Look up all currently popular titles on JustWatch.
|
|
@@ -233,18 +275,47 @@ def popular(
|
|
|
233
275
|
You can look up values through [`providers`]
|
|
234
276
|
[simplejustwatchapi.justwatch.providers] function.
|
|
235
277
|
|
|
278
|
+
min_release_year (int | None): Minimum release year of returned titles.
|
|
279
|
+
If `None` (the default value), no filtering is done.
|
|
280
|
+
|
|
281
|
+
max_release_year (int | None): Maximum release year of returned titles.
|
|
282
|
+
If `None` (the default value), no filtering is done.
|
|
283
|
+
|
|
284
|
+
object_types (list[str] | str | None): Types of objects to filter for, like
|
|
285
|
+
`SHOW` or `MOVIE`.
|
|
286
|
+
|
|
287
|
+
It seems that only `SHOW` and `MOVIE` are useful, but it's not strictly
|
|
288
|
+
enforced. Types like `SHOW_EPISODE`, or `SHOW_SEASON` can be used, but they
|
|
289
|
+
seem to return TV shows, same as `SHOW`.
|
|
290
|
+
|
|
291
|
+
While the type value is not enforced, it **must** be a valid type, otherwise
|
|
292
|
+
API will respond with HTTP status code 422.
|
|
293
|
+
|
|
294
|
+
For single type it can be a single string, or a list with one string.
|
|
295
|
+
|
|
296
|
+
If `None` (the default value), no filtering is done.
|
|
297
|
+
|
|
236
298
|
Returns:
|
|
237
299
|
(list[MediaEntry]): List of tuples with details of popular titles.
|
|
238
300
|
|
|
239
301
|
Raises:
|
|
240
302
|
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
|
|
241
303
|
due to invalid language or country code.
|
|
304
|
+
|
|
242
305
|
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
|
|
243
306
|
responded with non-`2xx` status code.
|
|
244
307
|
|
|
245
308
|
"""
|
|
246
309
|
request = prepare_popular_request(
|
|
247
|
-
country,
|
|
310
|
+
country,
|
|
311
|
+
language,
|
|
312
|
+
count,
|
|
313
|
+
best_only,
|
|
314
|
+
offset,
|
|
315
|
+
providers,
|
|
316
|
+
min_release_year,
|
|
317
|
+
max_release_year,
|
|
318
|
+
object_types,
|
|
248
319
|
)
|
|
249
320
|
response = _post_to_jw_graphql_api(request)
|
|
250
321
|
return parse_popular_response(response)
|
|
@@ -309,6 +380,7 @@ def details(
|
|
|
309
380
|
Raises:
|
|
310
381
|
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
|
|
311
382
|
due to invalid language or country code.
|
|
383
|
+
|
|
312
384
|
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
|
|
313
385
|
responded with non-`2xx` status code.
|
|
314
386
|
|
|
@@ -358,6 +430,7 @@ def seasons(
|
|
|
358
430
|
Raises:
|
|
359
431
|
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
|
|
360
432
|
due to invalid language or country code.
|
|
433
|
+
|
|
361
434
|
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
|
|
362
435
|
responded with non-`2xx` status code.
|
|
363
436
|
|
|
@@ -408,6 +481,7 @@ def episodes(
|
|
|
408
481
|
Raises:
|
|
409
482
|
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
|
|
410
483
|
due to invalid language or country code.
|
|
484
|
+
|
|
411
485
|
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
|
|
412
486
|
responded with non-`2xx` status code.
|
|
413
487
|
|
|
@@ -477,6 +551,7 @@ def offers_for_countries(
|
|
|
477
551
|
Raises:
|
|
478
552
|
exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
|
|
479
553
|
due to invalid language or country code.
|
|
554
|
+
|
|
480
555
|
exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
|
|
481
556
|
responded with non-`2xx` status code.
|
|
482
557
|
|
|
@@ -538,6 +613,8 @@ def _post_to_jw_graphql_api(request_json: dict) -> dict:
|
|
|
538
613
|
try:
|
|
539
614
|
response = post(_GRAPHQL_API_URL, json=request_json)
|
|
540
615
|
response.raise_for_status()
|
|
616
|
+
return response.json()
|
|
617
|
+
except HTTPStatusError as e:
|
|
618
|
+
raise JustWatchHttpError(str(e), e.response.text) from e
|
|
541
619
|
except HTTPError as e:
|
|
542
620
|
raise JustWatchHttpError(str(e)) from e
|
|
543
|
-
return response.json()
|
|
@@ -35,10 +35,6 @@ from simplejustwatchapi.tuples import (
|
|
|
35
35
|
_DETAILS_URL = "https://justwatch.com"
|
|
36
36
|
_IMAGES_URL = "https://images.justwatch.com"
|
|
37
37
|
|
|
38
|
-
# TODO: Left here for posterity, remove once formats are well documented.
|
|
39
|
-
_COUNTRY_CODE_REGEX = r"^[A-Z]{2}$"
|
|
40
|
-
_LANGUAGE_CODE_REGEX = r"^[a-z]{2}(-[0-9A-Z]+)?$"
|
|
41
|
-
|
|
42
38
|
|
|
43
39
|
def prepare_search_request(
|
|
44
40
|
title: str,
|
|
@@ -48,6 +44,9 @@ def prepare_search_request(
|
|
|
48
44
|
best_only: bool,
|
|
49
45
|
offset: int,
|
|
50
46
|
providers: list[str] | str | None,
|
|
47
|
+
min_release_year: int | None,
|
|
48
|
+
max_release_year: int | None,
|
|
49
|
+
object_types: list[str] | str | None,
|
|
51
50
|
) -> dict[str, Any]:
|
|
52
51
|
"""
|
|
53
52
|
Prepare search request for JustWatch GraphQL API.
|
|
@@ -70,6 +69,10 @@ def prepare_search_request(
|
|
|
70
69
|
offset (int): Search results offset.
|
|
71
70
|
providers (list[str] | str | None): 3-letter service identifier(s),
|
|
72
71
|
or `None` for all providers.
|
|
72
|
+
min_release_year (int | None): Minimum release year of returned titles.
|
|
73
|
+
max_release_year (int | None): Maximum release year of returned titles.
|
|
74
|
+
object_types (list[str] | str | None): Types of objects to filter for, it seems
|
|
75
|
+
that only "SHOW" and "MOVIE" make sense.
|
|
73
76
|
|
|
74
77
|
Returns:
|
|
75
78
|
(dict[str, Any]): JSON with GraphQL POST body.
|
|
@@ -79,7 +82,11 @@ def prepare_search_request(
|
|
|
79
82
|
"operationName": "GetSearchTitles",
|
|
80
83
|
"variables": {
|
|
81
84
|
"first": count,
|
|
82
|
-
"searchTitlesFilter": {
|
|
85
|
+
"searchTitlesFilter": {
|
|
86
|
+
"searchQuery": title,
|
|
87
|
+
"packages": providers,
|
|
88
|
+
**_filter_variables(min_release_year, max_release_year, object_types),
|
|
89
|
+
},
|
|
83
90
|
**_common_variables(best_only),
|
|
84
91
|
**_locale_variables(country, language),
|
|
85
92
|
"offset": offset or None,
|
|
@@ -122,6 +129,9 @@ def prepare_popular_request(
|
|
|
122
129
|
best_only: bool,
|
|
123
130
|
offset: int,
|
|
124
131
|
providers: list[str] | str | None,
|
|
132
|
+
min_release_year: int | None,
|
|
133
|
+
max_release_year: int | None,
|
|
134
|
+
object_types: list[str] | str | None,
|
|
125
135
|
) -> dict[str, Any]:
|
|
126
136
|
"""
|
|
127
137
|
Prepare "get popular" request for JustWatch GraphQL API.
|
|
@@ -143,6 +153,10 @@ def prepare_popular_request(
|
|
|
143
153
|
offset (int): Search results offset.
|
|
144
154
|
providers (list[str] | str | None): 3-letter service identifier(s),
|
|
145
155
|
or `None` for all providers.
|
|
156
|
+
min_release_year (int | None): Minimum release year of returned titles.
|
|
157
|
+
max_release_year (int | None): Maximum release year of returned titles.
|
|
158
|
+
object_types (list[str] | str | None): Types of objects to filter for, it seems
|
|
159
|
+
that only "SHOW" and "MOVIE" make sense.
|
|
146
160
|
|
|
147
161
|
Returns:
|
|
148
162
|
(dict[str, Any]): JSON with GraphQL POST body.
|
|
@@ -152,7 +166,10 @@ def prepare_popular_request(
|
|
|
152
166
|
"operationName": "GetPopularTitles",
|
|
153
167
|
"variables": {
|
|
154
168
|
"first": count,
|
|
155
|
-
"popularTitlesFilter": {
|
|
169
|
+
"popularTitlesFilter": {
|
|
170
|
+
"packages": providers,
|
|
171
|
+
**_filter_variables(min_release_year, max_release_year, object_types),
|
|
172
|
+
},
|
|
156
173
|
**_common_variables(best_only),
|
|
157
174
|
**_locale_variables(country, language),
|
|
158
175
|
"offset": offset or None,
|
|
@@ -511,6 +528,19 @@ def _locale_variables(country: str, language: str) -> dict[str, str]:
|
|
|
511
528
|
return {"country": country.upper(), "language": language}
|
|
512
529
|
|
|
513
530
|
|
|
531
|
+
def _filter_variables(
|
|
532
|
+
min_release_year: int | None,
|
|
533
|
+
max_release_year: int | None,
|
|
534
|
+
object_types: list[str] | str | None,
|
|
535
|
+
) -> dict[str, Any]:
|
|
536
|
+
"""Return dict with variables related to looking up lists of titles."""
|
|
537
|
+
return {
|
|
538
|
+
"includeTitlesWithoutUrl": True,
|
|
539
|
+
"objectTypes": object_types,
|
|
540
|
+
"releaseYear": {"min": min_release_year, "max": max_release_year},
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
|
|
514
544
|
def _raise_for_errors_in_response(json: dict[str, Any]) -> None:
|
|
515
545
|
"""Raise JustWatchApiError if given JSON contains `errors` key."""
|
|
516
546
|
if "errors" in json:
|
|
@@ -222,7 +222,8 @@ class MediaEntry(NamedTuple):
|
|
|
222
222
|
object_id (int): Object ID, the numeric part of full entry ID.
|
|
223
223
|
object_type (str): Type of entry, e.g. `MOVIE`, `SHOW`.
|
|
224
224
|
title (str): Full title.
|
|
225
|
-
url (str): URL to JustWatch with details for this entry.
|
|
225
|
+
url (str | None): URL to JustWatch with details for this entry. Some entries
|
|
226
|
+
are missing dedicated JustWatch pages, for them this field is `None`.
|
|
226
227
|
release_year (int): Release year as a number.
|
|
227
228
|
release_date (str): Full release date as a string, e.g. `2013-12-16`.
|
|
228
229
|
runtime_minutes (int): Runtime in minutes.
|
|
@@ -255,7 +256,7 @@ class MediaEntry(NamedTuple):
|
|
|
255
256
|
object_id: int
|
|
256
257
|
object_type: str
|
|
257
258
|
title: str
|
|
258
|
-
url: str
|
|
259
|
+
url: str | None
|
|
259
260
|
release_year: int
|
|
260
261
|
release_date: str
|
|
261
262
|
runtime_minutes: int
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|