simple-justwatch-python-api 0.19.0__tar.gz → 1.0.1__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.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: simple-justwatch-python-api
3
+ Version: 1.0.1
4
+ Summary: A simple JustWatch Python API
5
+ Keywords: justwatch,api,graphql
6
+ Author: Electronic Mango
7
+ Author-email: Electronic Mango <78230210+Electronic-Mango@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Internet :: WWW/HTTP
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Dist: httpx>=0.28.1
21
+ Requires-Python: >=3.11
22
+ Project-URL: Homepage, https://github.com/Electronic-Mango/simple-justwatch-python-api
23
+ Project-URL: Documentation, https://electronic-mango.github.io/simple-justwatch-python-api
24
+ Project-URL: Repository, https://github.com/Electronic-Mango/simple-justwatch-python-api
25
+ Project-URL: Changelog, https://github.com/Electronic-Mango/simple-justwatch-python-api/blob/main/CHANGELOG.md
26
+ Project-URL: Releases, https://github.com/Electronic-Mango/simple-justwatch-python-api/releases
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Simple JustWatch Python API
30
+
31
+ [![PyPi](https://img.shields.io/pypi/v/simple-justwatch-python-api.svg)](https://pypi.python.org/pypi/simple-justwatch-python-api)
32
+ [![License](https://img.shields.io/pypi/l/simple-justwatch-python-api.svg)](https://pypi.python.org/pypi/simple-justwatch-python-api)
33
+ [![Python versions](https://img.shields.io/pypi/pyversions/simple-justwatch-python-api.svg)](https://pypi.python.org/pypi/simple-justwatch-python-api)
34
+ [![CodeQL](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/codeql.yml/badge.svg)](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/codeql.yml)
35
+ [![Ruff](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/ruff.yml/badge.svg)](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/ruff.yml)
36
+ [![Pytest](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/pytest.yml/badge.svg)](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/pytest.yml)
37
+ [![Coverage Status](https://coveralls.io/repos/github/Electronic-Mango/simple-justwatch-python-api/badge.svg?branch=main)](https://coveralls.io/github/Electronic-Mango/simple-justwatch-python-api?branch=main)
38
+
39
+ A simple unofficial JustWatch Python API which uses [`GraphQL`](https://graphql.org/)
40
+ to access JustWatch data, available for Python `3.11+`.
41
+
42
+ This project is managed by [uv](https://docs.astral.sh/uv/).
43
+
44
+
45
+
46
+ ## Installation
47
+
48
+ This library is available through
49
+ [PyPi](https://pypi.org/project/simple-justwatch-python-api/):
50
+ ```bash
51
+ pip install simple-justwatch-python-api
52
+ ```
53
+
54
+
55
+ ## Documentation
56
+
57
+ Detailed documentation is available at:
58
+ <https://electronic-mango.github.io/simple-justwatch-python-api/>.
59
+
60
+
61
+
62
+ ## Highlights
63
+
64
+ This Python library has multiple functions:
65
+
66
+ - `search` - search for entries based on title
67
+ - `popular` - get a list of currently popular titles
68
+ - `details` - get details for entry based on its node ID
69
+ - `seasons` - get information about all seasons of a show
70
+ - `episodes` - get information about all episodes of a season
71
+ - `offers_for_countries` - get offers for entry based on its node ID, can look for
72
+ offers in multiple countries
73
+ - `providers` - get data about available providers (e.g., Netflix)
74
+
75
+ Example outputs from all functions are in [`examples/`](examples/) directory.
76
+
77
+
78
+
79
+ ## Quick example
80
+
81
+ ```python
82
+ from simplejustwatchapi import search
83
+
84
+ results = search("The Matrix", country="US", language="en", count=3)
85
+
86
+ for entry in results:
87
+ print(entry.title, entry.object_type, len(entry.offers))
88
+ ```
89
+
90
+
91
+ ## License
92
+
93
+ This library is licensed under **MIT license** ([LICENSE](./LICENSE) or
94
+ <https://opensource.org/license/MIT>)
95
+
96
+
97
+
98
+ ## Disclaimer
99
+
100
+ This library is in no way affiliated, associated, authorized, endorsed by, or in any way
101
+ officially connected with JustWatch. This is an independent and unofficial project.
102
+
103
+ Use at your own risk.
@@ -0,0 +1,75 @@
1
+ # Simple JustWatch Python API
2
+
3
+ [![PyPi](https://img.shields.io/pypi/v/simple-justwatch-python-api.svg)](https://pypi.python.org/pypi/simple-justwatch-python-api)
4
+ [![License](https://img.shields.io/pypi/l/simple-justwatch-python-api.svg)](https://pypi.python.org/pypi/simple-justwatch-python-api)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/simple-justwatch-python-api.svg)](https://pypi.python.org/pypi/simple-justwatch-python-api)
6
+ [![CodeQL](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/codeql.yml/badge.svg)](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/codeql.yml)
7
+ [![Ruff](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/ruff.yml/badge.svg)](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/ruff.yml)
8
+ [![Pytest](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/pytest.yml/badge.svg)](https://github.com/Electronic-Mango/simple-justwatch-python-api/actions/workflows/pytest.yml)
9
+ [![Coverage Status](https://coveralls.io/repos/github/Electronic-Mango/simple-justwatch-python-api/badge.svg?branch=main)](https://coveralls.io/github/Electronic-Mango/simple-justwatch-python-api?branch=main)
10
+
11
+ A simple unofficial JustWatch Python API which uses [`GraphQL`](https://graphql.org/)
12
+ to access JustWatch data, available for Python `3.11+`.
13
+
14
+ This project is managed by [uv](https://docs.astral.sh/uv/).
15
+
16
+
17
+
18
+ ## Installation
19
+
20
+ This library is available through
21
+ [PyPi](https://pypi.org/project/simple-justwatch-python-api/):
22
+ ```bash
23
+ pip install simple-justwatch-python-api
24
+ ```
25
+
26
+
27
+ ## Documentation
28
+
29
+ Detailed documentation is available at:
30
+ <https://electronic-mango.github.io/simple-justwatch-python-api/>.
31
+
32
+
33
+
34
+ ## Highlights
35
+
36
+ This Python library has multiple functions:
37
+
38
+ - `search` - search for entries based on title
39
+ - `popular` - get a list of currently popular titles
40
+ - `details` - get details for entry based on its node ID
41
+ - `seasons` - get information about all seasons of a show
42
+ - `episodes` - get information about all episodes of a season
43
+ - `offers_for_countries` - get offers for entry based on its node ID, can look for
44
+ offers in multiple countries
45
+ - `providers` - get data about available providers (e.g., Netflix)
46
+
47
+ Example outputs from all functions are in [`examples/`](examples/) directory.
48
+
49
+
50
+
51
+ ## Quick example
52
+
53
+ ```python
54
+ from simplejustwatchapi import search
55
+
56
+ results = search("The Matrix", country="US", language="en", count=3)
57
+
58
+ for entry in results:
59
+ print(entry.title, entry.object_type, len(entry.offers))
60
+ ```
61
+
62
+
63
+ ## License
64
+
65
+ This library is licensed under **MIT license** ([LICENSE](./LICENSE) or
66
+ <https://opensource.org/license/MIT>)
67
+
68
+
69
+
70
+ ## Disclaimer
71
+
72
+ This library is in no way affiliated, associated, authorized, endorsed by, or in any way
73
+ officially connected with JustWatch. This is an independent and unofficial project.
74
+
75
+ Use at your own risk.
@@ -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 = "0.19.0"
6
+ version = "1.0.1"
7
7
  description = "A simple JustWatch Python API"
8
8
  readme = "README.md"
9
9
  license = "MIT"
@@ -11,7 +11,7 @@ license-files = ["LICENSE"]
11
11
  requires-python = ">= 3.11"
12
12
  keywords = ["justwatch", "api", "graphql"]
13
13
  classifiers = [
14
- "Development Status :: 3 - Alpha",
14
+ "Development Status :: 5 - Production/Stable",
15
15
  "Intended Audience :: Developers",
16
16
  "Operating System :: OS Independent",
17
17
  "Programming Language :: Python :: 3 :: Only",
@@ -26,20 +26,22 @@ dependencies = ["httpx>=0.28.1"]
26
26
 
27
27
  [dependency-groups]
28
28
  dev = [
29
- "pytest>=9.0.2",
29
+ "mkdocstrings-python>=2.0.3",
30
+ "pytest>=9.0.3",
30
31
  "pytest-mock>=3.15.1",
31
- "ruff>=0.15.8",
32
- "sphinx>=9.0.4",
33
- "sphinx-rtd-theme>=3.1.0",
32
+ "ruff>=0.15.10",
33
+ "zensical>=0.0.32",
34
34
  ]
35
35
 
36
36
  [project.urls]
37
37
  Homepage = "https://github.com/Electronic-Mango/simple-justwatch-python-api"
38
38
  Documentation = "https://electronic-mango.github.io/simple-justwatch-python-api"
39
39
  Repository = "https://github.com/Electronic-Mango/simple-justwatch-python-api"
40
+ Changelog = "https://github.com/Electronic-Mango/simple-justwatch-python-api/blob/main/CHANGELOG.md"
41
+ Releases = "https://github.com/Electronic-Mango/simple-justwatch-python-api/releases"
40
42
 
41
43
  [build-system]
42
- requires = ["uv_build>=0.11.2,<0.12"]
44
+ requires = ["uv_build>=0.11.3,<0.12"]
43
45
  build-backend = "uv_build"
44
46
 
45
47
  [tool.uv.build-backend]
@@ -48,11 +50,7 @@ module-name = "simplejustwatchapi"
48
50
  [tool.pytest.ini_options]
49
51
  pythonpath = ["src", "test"]
50
52
 
51
- [tool.ruff]
52
- line-length = 100
53
-
54
53
  [tool.ruff.lint]
55
- exclude = ["./examples/*", "./docs/*"]
56
54
  select = ["ALL"]
57
55
  ignore = [
58
56
  "ANN401", # Type hinting JSONs is problematic
@@ -62,7 +60,6 @@ ignore = [
62
60
  "FBT",
63
61
  "FIX002",
64
62
  "PLR0913", # I think it makes sense for functions here to have more arguments
65
- "S101", # Remove once asserts are converted to exceptions
66
63
  "TD002",
67
64
  "TD003",
68
65
  ]
@@ -0,0 +1,45 @@
1
+ """The main simplejustwatchapi package with "public" interface."""
2
+
3
+ from simplejustwatchapi.exceptions import (
4
+ JustWatchApiError,
5
+ JustWatchError,
6
+ JustWatchHttpError,
7
+ )
8
+ from simplejustwatchapi.justwatch import (
9
+ details,
10
+ episodes,
11
+ offers_for_countries,
12
+ popular,
13
+ providers,
14
+ search,
15
+ seasons,
16
+ )
17
+ from simplejustwatchapi.tuples import (
18
+ Episode,
19
+ Interactions,
20
+ MediaEntry,
21
+ Offer,
22
+ OfferPackage,
23
+ Scoring,
24
+ StreamingCharts,
25
+ )
26
+
27
+ __all__ = [
28
+ "Episode",
29
+ "Interactions",
30
+ "JustWatchApiError",
31
+ "JustWatchError",
32
+ "JustWatchHttpError",
33
+ "MediaEntry",
34
+ "Offer",
35
+ "OfferPackage",
36
+ "Scoring",
37
+ "StreamingCharts",
38
+ "details",
39
+ "episodes",
40
+ "offers_for_countries",
41
+ "popular",
42
+ "providers",
43
+ "search",
44
+ "seasons",
45
+ ]
@@ -0,0 +1,60 @@
1
+ """
2
+ Exceptions raised by this library.
3
+
4
+ All exceptions inherit from [`JustWatchError`]
5
+ [simplejustwatchapi.exceptions.JustWatchError] for easier catching.
6
+
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
+
15
+ """
16
+
17
+
18
+ class JustWatchError(Exception):
19
+ """Common parent for all exceptions raised by this library."""
20
+
21
+
22
+ class JustWatchApiError(JustWatchError):
23
+ """
24
+ Raised when JustWatch API returned errors in JSON response.
25
+
26
+ If this error is raised, then API responded with status code `2xx`, but there are
27
+ listed errors in the internal JSON response. It can happen for too high complexity
28
+ of request, invalid node ID in functions like [`details`]
29
+ [simplejustwatchapi.justwatch.details], or invalid country or language codes.
30
+
31
+ Attributes:
32
+ errors (list[dict]): List of all errors in the JSON response from the API.
33
+ The `dict` elements are themselves basic JSONs, each with at least two
34
+ keys - `message` and `code`.
35
+
36
+ """
37
+
38
+ def __init__(self, errors: list[dict]) -> None:
39
+ """Init JustWatchApiError with internal errors from JSON response."""
40
+ super().__init__(f"Errors in JSON response: {errors}")
41
+ self.errors = errors
42
+
43
+
44
+ class JustWatchHttpError(JustWatchError):
45
+ """
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.
53
+
54
+ """
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
@@ -1,14 +1,16 @@
1
1
  """
2
2
  Module responsible for preparing full GraphQL queries.
3
3
 
4
- Queries are usually prepared as main query + details fragment + offers fragment.
4
+ Queries are usually prepared as main query + needed fragments.
5
+ Specific details are stored as separate GraphQL fragments / Python strings for easier
6
+ reuse and maintainability.
5
7
 
6
8
  In the long term these queries should be moved to dedicated GraphQL resource files
7
- to allow for formatting and syntax checking. However, the functions used for constructing
8
- full queries shouldn't change.
9
+ to allow for formatting and syntax checking. However, the functions used for
10
+ constructing full queries shouldn't change.
9
11
  """
10
12
 
11
- # TODO: Convert these strings into resources, e.g.:
13
+ # TODO: Convert these strings into resources, e.g.,:
12
14
  # https://docs.python.org/3/library/importlib.resources.html
13
15
 
14
16
  _GRAPHQL_SEARCH_QUERY = """
@@ -322,10 +324,15 @@ def graphql_search_query() -> str:
322
324
  """
323
325
  Prepare GraphQL query used for searching for entries.
324
326
 
325
- The query is GetSearchTitles query + details fragment + offers fragment + package fragment.
327
+ The full query is:
328
+
329
+ - `GetSearchTitles` query
330
+ - `TitleDetails` fragment
331
+ - `TitleOffer` fragment
332
+ - `PackageDetails` fragment
326
333
 
327
334
  Returns:
328
- str: Full GraphQL "search" query.
335
+ (str): Full GraphQL `GetSearchTitles` query.
329
336
 
330
337
  """
331
338
  return (
@@ -340,10 +347,15 @@ def graphql_popular_query() -> str:
340
347
  """
341
348
  Prepare GraphQL query used for looking up currently popular titles.
342
349
 
343
- The query is GetPopularTitles query + details fragment + offers fragment + package fragment.
350
+ The full query is:
351
+
352
+ - `GetPopularTitles` query
353
+ - `TitleDetails` fragment
354
+ - `TitleOffer` fragment
355
+ - `PackageDetails` fragment
344
356
 
345
357
  Returns:
346
- str: Full GraphQL "popular" query.
358
+ (str): Full GraphQL `GetPopularTitles` query.
347
359
 
348
360
  """
349
361
  return (
@@ -358,10 +370,13 @@ def graphql_providers_query() -> str:
358
370
  """
359
371
  Prepare GraphQL query used for looking up all providers for a given country.
360
372
 
361
- The query is GetProviders query + package fragment.
373
+ The full query is:
374
+
375
+ - `GetProviders` query
376
+ - `PackageDetails` fragment
362
377
 
363
378
  Returns:
364
- str: Full GraphQL "providers" query.
379
+ (str): Full GraphQL `GetProviders` query.
365
380
 
366
381
  """
367
382
  return _GRAPHQL_PROVIDERS_QUERY + _GRAPHQL_PACKAGE_FRAGMENT
@@ -371,12 +386,18 @@ def graphql_details_query() -> str:
371
386
  """
372
387
  Prepare GraphQL query used for getting details regarding a single entry.
373
388
 
374
- The full query is GetTitleNode query + details fragment + offers fragment + package fragment.
389
+ The full query is:
390
+
391
+ - `GetTitleNode` query
392
+ - `TitleDetails` fragment
393
+ - `TitleOffer` fragment
394
+ - `PackageDetails` fragment
395
+
375
396
  It is meant for movies and shows, but can be used for seasons and episodes as well,
376
397
  it just won't return full season/episodes list.
377
398
 
378
399
  Returns:
379
- str: Full GraphQL "get details" query.
400
+ (str): Full GraphQL `GetTitleNode` query.
380
401
 
381
402
  """
382
403
  return (
@@ -391,14 +412,20 @@ def graphql_seasons_query() -> str:
391
412
  """
392
413
  Prepare GraphQL query used for getting a list of seasons for a single show.
393
414
 
394
- The full query is GetTitleNode query (with seasons list) + details fragment + offers fragment
395
- + package fragment.
415
+ The full query is:
416
+
417
+ - `GetTitleNode` query, but only with a list of seasons
418
+ - `TitleDetails` fragment
419
+ - `TitleOffer` fragment
420
+ - `PackageDetails` fragment
421
+
396
422
  It will only return data for shows with a list of all available seasons, ascending.
397
- Details query itself matches :func:`graphql_details_query`, its conditions will return all
423
+ `TitleDetails` query itself matches [`graphql_details_query`]
424
+ [simplejustwatchapi.graphql.graphql_details_query], its conditions will return all
398
425
  relevant data for seasons.
399
426
 
400
427
  Returns:
401
- str: Full GraphQL "get seasons" query.
428
+ (str): GraphQL `GetTitleNode` query with a list of seasons.
402
429
 
403
430
  """
404
431
  return (
@@ -413,14 +440,20 @@ def graphql_episodes_query() -> str:
413
440
  """
414
441
  Prepare GraphQL query used for getting a list of episodes for a single show season.
415
442
 
416
- The full query is GetTitleNode query (with episodes list) + details fragment + offers fragment
417
- + package fragment.
418
- It will only return data for show seasons with a list of all available episodes, ascending.
419
- Details query itself matches :func:`graphql_details_query`, its conditions will return all
443
+ The full query is:
444
+
445
+ - `GetTitleNode` query, but only with a list of episodes
446
+ - `TitleDetails` fragment
447
+ - `TitleOffer` fragment
448
+ - `PackageDetails` fragment
449
+
450
+ It will only return data for show seasons with a list of all available episodes,
451
+ ascending. `TitleDetails` query itself matches [`graphql_details_query`]
452
+ [simplejustwatchapi.graphql.graphql_details_query], its conditions will return all
420
453
  relevant data for episodes.
421
454
 
422
455
  Returns:
423
- str: Full GraphQL "get episodes" query.
456
+ (str): GraphQL `GetTitleNode` query with a list of episods.
424
457
 
425
458
  """
426
459
  return (
@@ -435,23 +468,25 @@ def graphql_offers_for_countries_query(countries: set[str]) -> str:
435
468
  """
436
469
  Prepare GraphQL query with a list of offers from specified countries.
437
470
 
438
- The full query is GetTitleOffers query with a list of offers per country.
471
+ The full query is `GetTitleOffers` query with a list of offers per country.
439
472
  No additional information is returned, only offers.
440
473
  Can be used for all entry types - movies, shows, seasons, episodes.
441
474
 
442
- The input is a set of 2-letter country codes. This function assumes that codes are valid length,
443
- the set is not empty; it performs no additional verification.
475
+ The input is a set of 2-letter country codes. This function assumes that codes are
476
+ valid length and the set is not empty; it performs no verification on its own.
444
477
 
445
478
  Args:
446
479
  countries (set[str]): 2-letter country codes.
447
480
 
448
481
  Returns:
449
- str: Full GraphQL "get offers per country" query.
482
+ (str): GraphQL `GetTitleOffers` query with available offers per country code.
450
483
 
451
484
  """
452
485
  offer_requests = [
453
486
  _GRAPHQL_COUNTRY_OFFERS_ENTRY.format(country_code=country_code.upper())
454
487
  for country_code in countries
455
488
  ]
456
- main_query = _GRAPHQL_OFFERS_BY_COUNTRY_QUERY.format(country_entries="\n".join(offer_requests))
489
+ main_query = _GRAPHQL_OFFERS_BY_COUNTRY_QUERY.format(
490
+ country_entries="\n".join(offer_requests)
491
+ )
457
492
  return main_query + _GRAPHQL_OFFER_FRAGMENT + _GRAPHQL_PACKAGE_FRAGMENT