From 272f55c879abe23f0ac5c944b8b1d194935114c5 Mon Sep 17 00:00:00 2001 From: Electronic Mango <78230210+Electronic-Mango@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:14:49 +0200 Subject: [PATCH 1/6] Add filtering by release year and object type --- src/simplejustwatchapi/justwatch.py | 51 +++++++++++++++++++++++++++-- src/simplejustwatchapi/query.py | 35 ++++++++++++++++++-- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/simplejustwatchapi/justwatch.py b/src/simplejustwatchapi/justwatch.py index 58db0e0..e23b90f 100644 --- a/src/simplejustwatchapi/justwatch.py +++ b/src/simplejustwatchapi/justwatch.py @@ -79,6 +79,9 @@ def search( best_only: bool = True, offset: int = 0, providers: list[str] | str | None = None, + min_year: int | None = None, + max_year: int | None = None, + object_types: list[str] | str | None = None, ) -> list[MediaEntry]: """ Search JustWatch for the given title. @@ -148,6 +151,18 @@ def search( You can look up values through [`providers`] [simplejustwatchapi.justwatch.providers] function. + min_year (int | None): Minimum release year of returned titles. + If `None` (the default value), no filtering is done. + + max_year (int | None): Maximum release year of returned titles. + If `None` (the default value), no filtering is done. + + object_types (list[str] | str | None): Types of objects to filter for, it seems + that only `SHOW` and `MOVIE` are useful, but it's not strictly enforced. + Types like `SHOW_EPISODE`, or `SHOW_SEASON` can be used but they seem to + return shows, the same as `SHOW` type. If `None` (the default value), no + filtering is done. + Returns: (list[MediaEntry]): List of tuples with details of search results. @@ -159,7 +174,16 @@ def search( """ request = prepare_search_request( - title, country, language, count, best_only, offset, providers + title, + country, + language, + count, + best_only, + offset, + providers, + min_year, + max_year, + object_types, ) response = _post_to_jw_graphql_api(request) return parse_search_response(response) @@ -172,6 +196,9 @@ def popular( best_only: bool = True, offset: int = 0, providers: list[str] | str | None = None, + min_year: int | None = None, + max_year: int | None = None, + object_types: list[str] | str | None = None, ) -> list[MediaEntry]: """ Look up all currently popular titles on JustWatch. @@ -233,6 +260,18 @@ def popular( You can look up values through [`providers`] [simplejustwatchapi.justwatch.providers] function. + min_year (int | None): Minimum release year of returned titles. + If `None` (the default value), no filtering is done. + + max_year (int | None): Maximum release year of returned titles. + If `None` (the default value), no filtering is done. + + object_types (list[str] | str | None): Types of objects to filter for, it seems + that only `SHOW` and `MOVIE` are useful, but it's not strictly enforced. + Types like `SHOW_EPISODE`, or `SHOW_SEASON` can be used but they seem to + return shows, the same as `SHOW` type. If `None` (the default value), no + filtering is done. + Returns: (list[MediaEntry]): List of tuples with details of popular titles. @@ -244,7 +283,15 @@ def popular( """ request = prepare_popular_request( - country, language, count, best_only, offset, providers + country, + language, + count, + best_only, + offset, + providers, + min_year, + max_year, + object_types, ) response = _post_to_jw_graphql_api(request) return parse_popular_response(response) diff --git a/src/simplejustwatchapi/query.py b/src/simplejustwatchapi/query.py index f90ad71..a7bae11 100644 --- a/src/simplejustwatchapi/query.py +++ b/src/simplejustwatchapi/query.py @@ -44,6 +44,9 @@ def prepare_search_request( best_only: bool, offset: int, providers: list[str] | str | None, + min_year: int | None, + max_year: int | None, + object_types: list[str] | str | None, ) -> dict[str, Any]: """ Prepare search request for JustWatch GraphQL API. @@ -66,6 +69,10 @@ def prepare_search_request( offset (int): Search results offset. providers (list[str] | str | None): 3-letter service identifier(s), or `None` for all providers. + min_year (int | None): Minimum release year of returned titles. + max_year (int | None): Maximum release year of returned titles. + object_types (list[str] | str | None): Types of objects to filter for, it seems + that only "SHOW" and "MOVIE" make sense. Returns: (dict[str, Any]): JSON with GraphQL POST body. @@ -75,7 +82,11 @@ def prepare_search_request( "operationName": "GetSearchTitles", "variables": { "first": count, - "searchTitlesFilter": {"searchQuery": title, "packages": providers}, + "searchTitlesFilter": { + "searchQuery": title, + "packages": providers, + **_list_variables(min_year, max_year, object_types), + }, **_common_variables(best_only), **_locale_variables(country, language), "offset": offset or None, @@ -118,6 +129,9 @@ def prepare_popular_request( best_only: bool, offset: int, providers: list[str] | str | None, + min_year: int | None, + max_year: int | None, + object_types: list[str] | str | None, ) -> dict[str, Any]: """ Prepare "get popular" request for JustWatch GraphQL API. @@ -139,6 +153,10 @@ def prepare_popular_request( offset (int): Search results offset. providers (list[str] | str | None): 3-letter service identifier(s), or `None` for all providers. + min_year (int | None): Minimum release year of returned titles. + max_year (int | None): Maximum release year of returned titles. + object_types (list[str] | str | None): Types of objects to filter for, it seems + that only "SHOW" and "MOVIE" make sense. Returns: (dict[str, Any]): JSON with GraphQL POST body. @@ -148,7 +166,10 @@ def prepare_popular_request( "operationName": "GetPopularTitles", "variables": { "first": count, - "popularTitlesFilter": {"packages": providers}, + "popularTitlesFilter": { + "packages": providers, + **_list_variables(min_year, max_year, object_types), + }, **_common_variables(best_only), **_locale_variables(country, language), "offset": offset or None, @@ -507,6 +528,16 @@ def _locale_variables(country: str, language: str) -> dict[str, str]: return {"country": country.upper(), "language": language} +def _list_variables( + min_year: int | None, max_year: int | None, object_types: list[str] | str | None +) -> dict[str, Any]: + """Return dict with variables related to looking up lists of titles.""" + return { + "objectTypes": object_types, + "releaseYear": {"min": min_year, "max": max_year}, + } + + def _raise_for_errors_in_response(json: dict[str, Any]) -> None: """Raise JustWatchApiError if given JSON contains `errors` key.""" if "errors" in json: From 77824a4558ecea3d061b6bdb459f9b87d48b081a Mon Sep 17 00:00:00 2001 From: Electronic Mango <78230210+Electronic-Mango@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:15:07 +0200 Subject: [PATCH 2/6] Update unit tests for new filtering options --- test/simplejustwatchapi/test_justwatch.py | 25 +++++- test/simplejustwatchapi/test_request.py | 102 ++++++++++++++++++---- 2 files changed, 110 insertions(+), 17 deletions(-) diff --git a/test/simplejustwatchapi/test_justwatch.py b/test/simplejustwatchapi/test_justwatch.py index d7c0089..e3ce9a8 100644 --- a/test/simplejustwatchapi/test_justwatch.py +++ b/test/simplejustwatchapi/test_justwatch.py @@ -16,8 +16,29 @@ JUSTWATCH_GRAPHQL_URL = "https://apis.justwatch.com/graphql" -SEARCH_INPUT = ("TITLE", "COUNTRY", "LANGUAGE", 5, True, 10, ["prov1", "prov2"]) -POPULAR_INPUT = ("COUNTRY", "LANGUAGE", 5, True, 10, ["prov1", "prov2"]) +SEARCH_INPUT = ( + "TITLE", + "COUNTRY", + "LANGUAGE", + 5, + True, + 10, + ["prov1", "prov2"], + 1990, + 2000, + ["SHOW", "MOVIE"], +) +POPULAR_INPUT = ( + "COUNTRY", + "LANGUAGE", + 5, + True, + 10, + ["prov1", "prov2"], + 2000, + None, + "SHOW", +) DETAILS_INPUT = ("NODE ID", "COUNTRY", "LANGUAGE", False) OFFERS_COUNTRIES_INPUT = {"COUNTRY1", "COUNTRY2", "COUNTRY3"} OFFERS_INPUT = ("NODE ID", OFFERS_COUNTRIES_INPUT, "LANGUAGE", True) diff --git a/test/simplejustwatchapi/test_request.py b/test/simplejustwatchapi/test_request.py index f159306..fd2ccc6 100644 --- a/test/simplejustwatchapi/test_request.py +++ b/test/simplejustwatchapi/test_request.py @@ -39,6 +39,13 @@ def locale_variables(country, language): } +def list_variables(min_year, max_year, object_types): + return { + "objectTypes": object_types, + "releaseYear": {"min": min_year, "max": max_year}, + } + + @patch("simplejustwatchapi.query.GRAPHQL_SEARCH_QUERY", DUMMY_SEARCH_QUERY) @mark.parametrize( argnames=( @@ -49,13 +56,38 @@ def locale_variables(country, language): "best_only", "offset", "providers", + "min_year", + "max_year", + "object_types", ), argvalues=[ - ("TITLE 1", "US", "en", 5, True, 0, ""), - ("TITLE 2", "gb", "fr", 10, False, 20, ["provider1", "provider2"]), - ("TITLE 3", "fr", "de-SWITZ123", 20, True, 20, "provider3"), - ("TITLE 4", "it", "ro-HELLO123", 30, True, 30, []), - ("TITLE 5", "dk", "us", 40, True, 40, None), + ("TITLE 1", "US", "en", 5, True, 0, "", 1990, 2000, ["SHOW", "MOVIE"]), + ( + "TITLE 2", + "gb", + "fr", + 10, + False, + 20, + ["provider1", "provider2"], + 2000, + None, + "MOVIE", + ), + ( + "TITLE 3", + "fr", + "de-SWITZ123", + 20, + True, + 20, + "provider3", + None, + 2020, + ["SHOW"], + ), + ("TITLE 4", "it", "ro-HELLO123", 30, True, 30, [], None, None, "SHOW"), + ("TITLE 5", "dk", "us", 40, True, 40, None, 2030, 2040, ["MOVIE"]), ], ) def test_prepare_search_request( @@ -66,12 +98,19 @@ def test_prepare_search_request( best_only, offset, providers, + min_year, + max_year, + object_types, ): expected_request = { "operationName": "GetSearchTitles", "variables": { "first": count, - "searchTitlesFilter": {"searchQuery": title, "packages": providers}, + "searchTitlesFilter": { + "searchQuery": title, + "packages": providers, + **list_variables(min_year, max_year, object_types), + }, **common_variables(best_only), **locale_variables(country, language), "offset": offset or None, @@ -79,20 +118,39 @@ def test_prepare_search_request( "query": DUMMY_SEARCH_QUERY, } request = prepare_search_request( - title, country, language, count, best_only, offset, providers + title, + country, + language, + count, + best_only, + offset, + providers, + min_year, + max_year, + object_types, ) assert expected_request == request @patch("simplejustwatchapi.query.GRAPHQL_POPULAR_QUERY", DUMMY_POPULAR_QUERY) @mark.parametrize( - argnames=("country", "language", "count", "best_only", "offset", "providers"), + argnames=( + "country", + "language", + "count", + "best_only", + "offset", + "providers", + "min_year", + "max_year", + "object_types", + ), argvalues=[ - ("US", "en-123ASD", 5, True, 0, ""), - ("gb", "fr", 10, False, 20, ["provider1", "provider2"]), - ("fr", "de-FGH76", 20, True, 20, "provider3"), - ("it", "ro", 30, True, 30, []), - ("dk", "us", 40, True, 40, None), + ("US", "en-123ASD", 5, True, 0, "", 1990, 2000, ["SHOW", "MOVIE"]), + ("gb", "fr", 10, False, 20, ["provider1", "provider2"], 2000, 2010, "MOVIE"), + ("fr", "de-FGH76", 20, True, 20, "provider3", None, 2020, ["SHOW"]), + ("it", "ro", 30, True, 30, [], 2020, None, "SHOW"), + ("dk", "us", 40, True, 40, None, None, None, ["MOVIE"]), ], ) def test_prepare_popular_request( @@ -102,12 +160,18 @@ def test_prepare_popular_request( best_only, offset, providers, + min_year, + max_year, + object_types, ): expected_request = { "operationName": "GetPopularTitles", "variables": { "first": count, - "popularTitlesFilter": {"packages": providers}, + "popularTitlesFilter": { + "packages": providers, + **list_variables(min_year, max_year, object_types), + }, **common_variables(best_only), **locale_variables(country, language), "offset": offset or None, @@ -115,7 +179,15 @@ def test_prepare_popular_request( "query": DUMMY_POPULAR_QUERY, } request = prepare_popular_request( - country, language, count, best_only, offset, providers + country, + language, + count, + best_only, + offset, + providers, + min_year, + max_year, + object_types, ) assert expected_request == request From eaaa3a8376f0ee3310a5756aaa340b89800b71ff Mon Sep 17 00:00:00 2001 From: Electronic Mango <78230210+Electronic-Mango@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:40:00 +0200 Subject: [PATCH 3/6] Update documentation with new filtering options --- docs/caveats.md | 17 +++++++++++++++++ docs/usage.md | 26 ++++++++++++++++++++++++++ src/simplejustwatchapi/justwatch.py | 8 +++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/caveats.md b/docs/caveats.md index 473fba1..21c82db 100644 --- a/docs/caveats.md +++ b/docs/caveats.md @@ -296,3 +296,20 @@ popular_results = popular("US", providers=list(name_to_code_dict.values())) !!! tip "When this actually might be useful" This only makes sense if you are already using other functions. To get **just** the codes use the [`providers`](#providers-function) function instead. + + + +## Filtering by type + +Functions [`search`](#search-for-a-title) and [`popular`](#popular-titles) allow for +filtering of returned entries by "types". The types are list of strings (or just a single +string), like `SHOW`, `MOVIE`. You can check +[`examples/`](https://github.com/Electronic-Mango/simple-justwatch-python-api/tree/main/examples) +for field `object_type`, however the only "useful" ones seem `SHOW` and `MOVIE`. You can +use types like `SHOW_EPISODE`, or `SHOW_SEASON`, however they seem to default to "TV +shows" - the same as for type `SHOW`. + +!!! warning "Types are not enforced, but must be valid" + While possible types are not enforced by functions in this API, they still **must** + be valid types in JustWatch API. Using unexpected type will result in HTTP error with + code 422. diff --git a/docs/usage.md b/docs/usage.md index 218c150..2dd2245 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -502,3 +502,29 @@ while results := popular(count=page, offset=i): this method. Check [Maximum number of entries](caveats.md#maximum-number-of-entries) page for more details. + + + +### Additional filtering in [`search`](#search-for-a-title) and [`popular`](#popular-titles) + +[`search`](#search-for-a-title) and [`popular`](#popular-titles) functions allow for +additional filtering of returned entries. + +| Field | Description | +| ------| ----------- | +| `object_types` | Types like `SHOW` or `MOVIE`. Values are not enforced, but types like `SHOW_EPISODE` or `SHOW_SEASON` return shows anyway; like `SHOW`. While not enforced they **must** be valid types. It can be either a list of strings, or a single string. | +| `min_year` | Minimum release year. | +| `max_year` | Maximum release year. | + +All filters are optional; when not configured (or set to `None`) the filtering is disabled. + +```python +from simplejustwatchapi import popular, search + +# Get only currently popular TV shows: +popular_shows = popular(object_types=["SHOW"]) + +# Search for movies between 1990 and 2010: +movies = search("The Matrix", min_year=1990, max_year=2010, object_types="MOVIE") +# object_types with single element can be either a list, or just a string. +``` diff --git a/src/simplejustwatchapi/justwatch.py b/src/simplejustwatchapi/justwatch.py index e23b90f..94a12d5 100644 --- a/src/simplejustwatchapi/justwatch.py +++ b/src/simplejustwatchapi/justwatch.py @@ -34,7 +34,13 @@ | `providers` | Providers (like Netflix, Amazon Prime Video) for which offers should \ returned. Requires 3-letter "short name". Check \ [`providers`][simplejustwatchapi.justwatch.providers] for an example \ - of how you can get that value. + of how you can get that value. | +| `min_year` | Minimum release year of returned titles. | +| `max_year` | Maximum release year of returned titles. | +| `object_types` | Types of objects to filter for, it seems that only `SHOW` and \ + `MOVIE` are useful, but it's not strictly enforced. Types like \ + `SHOW_EPISODE`, or `SHOW_SEASON` can be used but they seem to \ + return shows, the same as `SHOW` type. | Each function can raise two exceptions: From 3c39e0aadf25abc154f27912715f6b3403522084 Mon Sep 17 00:00:00 2001 From: Electronic Mango <78230210+Electronic-Mango@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:44:13 +0200 Subject: [PATCH 4/6] Update release year filter argument names "min_year" -> "min_release_year" "max_year" -> "max_release_year" --- docs/usage.md | 11 ++++++--- src/simplejustwatchapi/justwatch.py | 28 +++++++++++----------- src/simplejustwatchapi/query.py | 26 ++++++++++---------- test/simplejustwatchapi/test_request.py | 32 ++++++++++++------------- 4 files changed, 52 insertions(+), 45 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 2dd2245..44100dc 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -513,8 +513,8 @@ additional filtering of returned entries. | Field | Description | | ------| ----------- | | `object_types` | Types like `SHOW` or `MOVIE`. Values are not enforced, but types like `SHOW_EPISODE` or `SHOW_SEASON` return shows anyway; like `SHOW`. While not enforced they **must** be valid types. It can be either a list of strings, or a single string. | -| `min_year` | Minimum release year. | -| `max_year` | Maximum release year. | +| `min_release_year` | Minimum release year. | +| `max_release_year` | Maximum release year. | All filters are optional; when not configured (or set to `None`) the filtering is disabled. @@ -525,6 +525,11 @@ from simplejustwatchapi import popular, search popular_shows = popular(object_types=["SHOW"]) # Search for movies between 1990 and 2010: -movies = search("The Matrix", min_year=1990, max_year=2010, object_types="MOVIE") +movies = search( + "The Matrix", + min_release_year=1990, + max_release_year=2010, + object_types="MOVIE" +) # object_types with single element can be either a list, or just a string. ``` diff --git a/src/simplejustwatchapi/justwatch.py b/src/simplejustwatchapi/justwatch.py index 94a12d5..98357f5 100644 --- a/src/simplejustwatchapi/justwatch.py +++ b/src/simplejustwatchapi/justwatch.py @@ -35,8 +35,8 @@ returned. Requires 3-letter "short name". Check \ [`providers`][simplejustwatchapi.justwatch.providers] for an example \ of how you can get that value. | -| `min_year` | Minimum release year of returned titles. | -| `max_year` | Maximum release year of returned titles. | +| `min_release_year` | Minimum release year of returned titles. | +| `max_release_year` | Maximum release year of returned titles. | | `object_types` | Types of objects to filter for, it seems that only `SHOW` and \ `MOVIE` are useful, but it's not strictly enforced. Types like \ `SHOW_EPISODE`, or `SHOW_SEASON` can be used but they seem to \ @@ -85,8 +85,8 @@ def search( best_only: bool = True, offset: int = 0, providers: list[str] | str | None = None, - min_year: int | None = None, - max_year: int | None = None, + min_release_year: int | None = None, + max_release_year: int | None = None, object_types: list[str] | str | None = None, ) -> list[MediaEntry]: """ @@ -157,10 +157,10 @@ def search( You can look up values through [`providers`] [simplejustwatchapi.justwatch.providers] function. - min_year (int | None): Minimum release year of returned titles. + min_release_year (int | None): Minimum release year of returned titles. If `None` (the default value), no filtering is done. - max_year (int | None): Maximum release year of returned titles. + max_release_year (int | None): Maximum release year of returned titles. If `None` (the default value), no filtering is done. object_types (list[str] | str | None): Types of objects to filter for, it seems @@ -187,8 +187,8 @@ def search( best_only, offset, providers, - min_year, - max_year, + min_release_year, + max_release_year, object_types, ) response = _post_to_jw_graphql_api(request) @@ -202,8 +202,8 @@ def popular( best_only: bool = True, offset: int = 0, providers: list[str] | str | None = None, - min_year: int | None = None, - max_year: int | None = None, + min_release_year: int | None = None, + max_release_year: int | None = None, object_types: list[str] | str | None = None, ) -> list[MediaEntry]: """ @@ -266,10 +266,10 @@ def popular( You can look up values through [`providers`] [simplejustwatchapi.justwatch.providers] function. - min_year (int | None): Minimum release year of returned titles. + min_release_year (int | None): Minimum release year of returned titles. If `None` (the default value), no filtering is done. - max_year (int | None): Maximum release year of returned titles. + max_release_year (int | None): Maximum release year of returned titles. If `None` (the default value), no filtering is done. object_types (list[str] | str | None): Types of objects to filter for, it seems @@ -295,8 +295,8 @@ def popular( best_only, offset, providers, - min_year, - max_year, + min_release_year, + max_release_year, object_types, ) response = _post_to_jw_graphql_api(request) diff --git a/src/simplejustwatchapi/query.py b/src/simplejustwatchapi/query.py index a7bae11..b86f17c 100644 --- a/src/simplejustwatchapi/query.py +++ b/src/simplejustwatchapi/query.py @@ -44,8 +44,8 @@ def prepare_search_request( best_only: bool, offset: int, providers: list[str] | str | None, - min_year: int | None, - max_year: int | None, + min_release_year: int | None, + max_release_year: int | None, object_types: list[str] | str | None, ) -> dict[str, Any]: """ @@ -69,8 +69,8 @@ def prepare_search_request( offset (int): Search results offset. providers (list[str] | str | None): 3-letter service identifier(s), or `None` for all providers. - min_year (int | None): Minimum release year of returned titles. - max_year (int | None): Maximum release year of returned titles. + min_release_year (int | None): Minimum release year of returned titles. + max_release_year (int | None): Maximum release year of returned titles. object_types (list[str] | str | None): Types of objects to filter for, it seems that only "SHOW" and "MOVIE" make sense. @@ -85,7 +85,7 @@ def prepare_search_request( "searchTitlesFilter": { "searchQuery": title, "packages": providers, - **_list_variables(min_year, max_year, object_types), + **_list_variables(min_release_year, max_release_year, object_types), }, **_common_variables(best_only), **_locale_variables(country, language), @@ -129,8 +129,8 @@ def prepare_popular_request( best_only: bool, offset: int, providers: list[str] | str | None, - min_year: int | None, - max_year: int | None, + min_release_year: int | None, + max_release_year: int | None, object_types: list[str] | str | None, ) -> dict[str, Any]: """ @@ -153,8 +153,8 @@ def prepare_popular_request( offset (int): Search results offset. providers (list[str] | str | None): 3-letter service identifier(s), or `None` for all providers. - min_year (int | None): Minimum release year of returned titles. - max_year (int | None): Maximum release year of returned titles. + min_release_year (int | None): Minimum release year of returned titles. + max_release_year (int | None): Maximum release year of returned titles. object_types (list[str] | str | None): Types of objects to filter for, it seems that only "SHOW" and "MOVIE" make sense. @@ -168,7 +168,7 @@ def prepare_popular_request( "first": count, "popularTitlesFilter": { "packages": providers, - **_list_variables(min_year, max_year, object_types), + **_list_variables(min_release_year, max_release_year, object_types), }, **_common_variables(best_only), **_locale_variables(country, language), @@ -529,12 +529,14 @@ def _locale_variables(country: str, language: str) -> dict[str, str]: def _list_variables( - min_year: int | None, max_year: int | None, object_types: list[str] | str | None + min_release_year: int | None, + max_release_year: int | None, + object_types: list[str] | str | None, ) -> dict[str, Any]: """Return dict with variables related to looking up lists of titles.""" return { "objectTypes": object_types, - "releaseYear": {"min": min_year, "max": max_year}, + "releaseYear": {"min": min_release_year, "max": max_release_year}, } diff --git a/test/simplejustwatchapi/test_request.py b/test/simplejustwatchapi/test_request.py index fd2ccc6..575a444 100644 --- a/test/simplejustwatchapi/test_request.py +++ b/test/simplejustwatchapi/test_request.py @@ -39,10 +39,10 @@ def locale_variables(country, language): } -def list_variables(min_year, max_year, object_types): +def list_variables(min_release_year, max_release_year, object_types): return { "objectTypes": object_types, - "releaseYear": {"min": min_year, "max": max_year}, + "releaseYear": {"min": min_release_year, "max": max_release_year}, } @@ -56,8 +56,8 @@ def list_variables(min_year, max_year, object_types): "best_only", "offset", "providers", - "min_year", - "max_year", + "min_release_year", + "max_release_year", "object_types", ), argvalues=[ @@ -98,8 +98,8 @@ def test_prepare_search_request( best_only, offset, providers, - min_year, - max_year, + min_release_year, + max_release_year, object_types, ): expected_request = { @@ -109,7 +109,7 @@ def test_prepare_search_request( "searchTitlesFilter": { "searchQuery": title, "packages": providers, - **list_variables(min_year, max_year, object_types), + **list_variables(min_release_year, max_release_year, object_types), }, **common_variables(best_only), **locale_variables(country, language), @@ -125,8 +125,8 @@ def test_prepare_search_request( best_only, offset, providers, - min_year, - max_year, + min_release_year, + max_release_year, object_types, ) assert expected_request == request @@ -141,8 +141,8 @@ def test_prepare_search_request( "best_only", "offset", "providers", - "min_year", - "max_year", + "min_release_year", + "max_release_year", "object_types", ), argvalues=[ @@ -160,8 +160,8 @@ def test_prepare_popular_request( best_only, offset, providers, - min_year, - max_year, + min_release_year, + max_release_year, object_types, ): expected_request = { @@ -170,7 +170,7 @@ def test_prepare_popular_request( "first": count, "popularTitlesFilter": { "packages": providers, - **list_variables(min_year, max_year, object_types), + **list_variables(min_release_year, max_release_year, object_types), }, **common_variables(best_only), **locale_variables(country, language), @@ -185,8 +185,8 @@ def test_prepare_popular_request( best_only, offset, providers, - min_year, - max_year, + min_release_year, + max_release_year, object_types, ) assert expected_request == request From d739fab8fd67f3441574806465d523266bfc62d1 Mon Sep 17 00:00:00 2001 From: Electronic Mango <78230210+Electronic-Mango@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:17:30 +0200 Subject: [PATCH 5/6] Include titles without JW URL in search/popular Add "'includeTitlesWithoutUrl': True" to search/popular filters. This will include entries without dedicated JW page (and its URL). Other data (like offers) seems OK. "MediaEntry.url" marked as optional to accomodate. --- src/simplejustwatchapi/query.py | 7 ++++--- src/simplejustwatchapi/tuples.py | 5 +++-- test/simplejustwatchapi/test_request.py | 7 ++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/simplejustwatchapi/query.py b/src/simplejustwatchapi/query.py index b86f17c..0efda10 100644 --- a/src/simplejustwatchapi/query.py +++ b/src/simplejustwatchapi/query.py @@ -85,7 +85,7 @@ def prepare_search_request( "searchTitlesFilter": { "searchQuery": title, "packages": providers, - **_list_variables(min_release_year, max_release_year, object_types), + **_filter_variables(min_release_year, max_release_year, object_types), }, **_common_variables(best_only), **_locale_variables(country, language), @@ -168,7 +168,7 @@ def prepare_popular_request( "first": count, "popularTitlesFilter": { "packages": providers, - **_list_variables(min_release_year, max_release_year, object_types), + **_filter_variables(min_release_year, max_release_year, object_types), }, **_common_variables(best_only), **_locale_variables(country, language), @@ -528,13 +528,14 @@ def _locale_variables(country: str, language: str) -> dict[str, str]: return {"country": country.upper(), "language": language} -def _list_variables( +def _filter_variables( min_release_year: int | None, max_release_year: int | None, object_types: list[str] | str | None, ) -> dict[str, Any]: """Return dict with variables related to looking up lists of titles.""" return { + "includeTitlesWithoutUrl": True, "objectTypes": object_types, "releaseYear": {"min": min_release_year, "max": max_release_year}, } diff --git a/src/simplejustwatchapi/tuples.py b/src/simplejustwatchapi/tuples.py index a608185..38e5cd8 100644 --- a/src/simplejustwatchapi/tuples.py +++ b/src/simplejustwatchapi/tuples.py @@ -222,7 +222,8 @@ class MediaEntry(NamedTuple): object_id (int): Object ID, the numeric part of full entry ID. object_type (str): Type of entry, e.g. `MOVIE`, `SHOW`. title (str): Full title. - url (str): URL to JustWatch with details for this entry. + url (str | None): URL to JustWatch with details for this entry. Some entries + are missing dedicated JustWatch pages, for them this field is `None`. release_year (int): Release year as a number. release_date (str): Full release date as a string, e.g. `2013-12-16`. runtime_minutes (int): Runtime in minutes. @@ -255,7 +256,7 @@ class MediaEntry(NamedTuple): object_id: int object_type: str title: str - url: str + url: str | None release_year: int release_date: str runtime_minutes: int diff --git a/test/simplejustwatchapi/test_request.py b/test/simplejustwatchapi/test_request.py index 575a444..dc60950 100644 --- a/test/simplejustwatchapi/test_request.py +++ b/test/simplejustwatchapi/test_request.py @@ -39,8 +39,9 @@ def locale_variables(country, language): } -def list_variables(min_release_year, max_release_year, object_types): +def filter_variables(min_release_year, max_release_year, object_types): return { + "includeTitlesWithoutUrl": True, "objectTypes": object_types, "releaseYear": {"min": min_release_year, "max": max_release_year}, } @@ -109,7 +110,7 @@ def test_prepare_search_request( "searchTitlesFilter": { "searchQuery": title, "packages": providers, - **list_variables(min_release_year, max_release_year, object_types), + **filter_variables(min_release_year, max_release_year, object_types), }, **common_variables(best_only), **locale_variables(country, language), @@ -170,7 +171,7 @@ def test_prepare_popular_request( "first": count, "popularTitlesFilter": { "packages": providers, - **list_variables(min_release_year, max_release_year, object_types), + **filter_variables(min_release_year, max_release_year, object_types), }, **common_variables(best_only), **locale_variables(country, language), From cc5618635715b0a4875b2792cbfd9ba5f0e75dbf Mon Sep 17 00:00:00 2001 From: Electronic Mango <78230210+Electronic-Mango@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:53:19 +0200 Subject: [PATCH 6/6] Update docs regarding new filtering options --- docs/caveats.md | 17 ------- docs/usage.md | 49 +++++++++++++------- src/simplejustwatchapi/justwatch.py | 70 +++++++++++++++++++---------- 3 files changed, 78 insertions(+), 58 deletions(-) diff --git a/docs/caveats.md b/docs/caveats.md index 21c82db..473fba1 100644 --- a/docs/caveats.md +++ b/docs/caveats.md @@ -296,20 +296,3 @@ popular_results = popular("US", providers=list(name_to_code_dict.values())) !!! tip "When this actually might be useful" This only makes sense if you are already using other functions. To get **just** the codes use the [`providers`](#providers-function) function instead. - - - -## Filtering by type - -Functions [`search`](#search-for-a-title) and [`popular`](#popular-titles) allow for -filtering of returned entries by "types". The types are list of strings (or just a single -string), like `SHOW`, `MOVIE`. You can check -[`examples/`](https://github.com/Electronic-Mango/simple-justwatch-python-api/tree/main/examples) -for field `object_type`, however the only "useful" ones seem `SHOW` and `MOVIE`. You can -use types like `SHOW_EPISODE`, or `SHOW_SEASON`, however they seem to default to "TV -shows" - the same as for type `SHOW`. - -!!! warning "Types are not enforced, but must be valid" - While possible types are not enforced by functions in this API, they still **must** - be valid types in JustWatch API. Using unexpected type will result in HTTP error with - code 422. diff --git a/docs/usage.md b/docs/usage.md index 44100dc..319f346 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -47,23 +47,25 @@ You can check [Exceptions](API Reference/exceptions.md) page for more details. Most functions have a number of common arguments (in addition to function-specific ones, like `title` to search for): -| Name | Description | -|-------------|-------------| -| `country` | 2-letter country code for which offers will be returned, e.g., `US`, `GB`, `DE`. | -| `language` | Code for language in responses. It consists of 2 lowercase letters with optional uppercase alphanumeric suffix (e.g., `en`, `en-US`, `de`, `de-CH1901`). | +| Name | Description | +|------|-------------| +| `country` | 2-letter country code for which offers will be returned, e.g., `US`, `GB`, `DE`. | +| `language` | Code for language in responses. It consists of 2 lowercase letters with optional uppercase alphanumeric suffix (e.g., `en`, `en-US`, `de`, `de-CH1901`). | | `best_only` | Whether to return only "best" offers for each provider instead of, e.g., separate offers for SD, HD, and 4K. | Functions returning data for multiple titles ([`search`][simplejustwatchapi.justwatch.search], [`popular`][simplejustwatchapi.justwatch.popular]) -also allow for specifying number of elements, basic pagination, and filtering for -specific providers: +also allow for specifying number of elements, basic pagination, and optional filtering: -| Name | Description | -|-------------|-------------| -| `count` | How many entries should be returned. | -| `offset` | Basic "pagination". Offset for the first returned result, i.e. how many first entries should be skipped. Everything is handled on API side, this library isn't doing any filtering. | -| `providers` | Providers (like Netflix, Amazon Prime Video) for which offers should returned. Requires 3-letter "short name". Check [Provider codes](caveats.md#provider-codes) page for an example of how you can get that value. +| Name | Description | +|------|-------------| +| `count` | How many entries should be returned. | +| `offset` | Basic "pagination". Offset for the first returned result, i.e. how many first entries should be skipped. Everything is handled on API side, this library isn't doing any filtering. | +| `providers` | Providers (like Netflix, Amazon Prime Video) for which offers should returned. Requires 3-letter "short name". Check [Provider codes](caveats.md#provider-codes) page for an example of how you can get that value. | +| `min_release_year` | Minimum release year for a title. | +| `max_release_year` | Maximum release year for a title. | +| `object_types` | Types of objects to filter for, such as `SHOW` or `MOVIE`. Check [Additional filtering](#additional-filtering-in-search-and-popular) section for more details. | ### Search for a title @@ -505,18 +507,31 @@ while results := popular(count=page, offset=i): -### Additional filtering in [`search`](#search-for-a-title) and [`popular`](#popular-titles) +## Additional filtering in [`search`](#search-for-a-title) and [`popular`](#popular-titles) [`search`](#search-for-a-title) and [`popular`](#popular-titles) functions allow for additional filtering of returned entries. -| Field | Description | -| ------| ----------- | -| `object_types` | Types like `SHOW` or `MOVIE`. Values are not enforced, but types like `SHOW_EPISODE` or `SHOW_SEASON` return shows anyway; like `SHOW`. While not enforced they **must** be valid types. It can be either a list of strings, or a single string. | +| Argument name | Description | +|---------------|-------------| | `min_release_year` | Minimum release year. | | `max_release_year` | Maximum release year. | +| `object_types` | Media types, like `SHOW` or `MOVIE`. | -All filters are optional; when not configured (or set to `None`) the filtering is disabled. +All filters are optional; when not configured (or set to `None`) the filtering is +disabled. + +`object_types` is a list of strings (or for one type just a single string), +like `SHOW`, or `MOVIE`. You can check +[`examples/`](https://github.com/Electronic-Mango/simple-justwatch-python-api/tree/main/examples) +for field `object_type`, however the only "useful" ones seem to be `SHOW` and `MOVIE`. +You can use types like `SHOW_EPISODE`, or `SHOW_SEASON`, however they seem to default +to "TV shows" - same as for `SHOW`. + +!!! warning "Type values are not enforced, but must be valid" + Possible values for types are not enforced by functions in this API, but they still + **must** be valid types in JustWatch API. Using unexpected type will result in HTTP + error with code 422. ```python from simplejustwatchapi import popular, search @@ -531,5 +546,5 @@ movies = search( max_release_year=2010, object_types="MOVIE" ) -# object_types with single element can be either a list, or just a string. +# "object_types" with single type can be either a list, or just a string. ``` diff --git a/src/simplejustwatchapi/justwatch.py b/src/simplejustwatchapi/justwatch.py index 98357f5..ae6cc9f 100644 --- a/src/simplejustwatchapi/justwatch.py +++ b/src/simplejustwatchapi/justwatch.py @@ -9,11 +9,11 @@ Most functions have a number of common arguments (in addition to function-specific ones, like `title` to search for): -| Name | Description | -|-------------|-------------| -| `country` | 2-letter country code for which offers are selected, (e.g., `US`, \ +| Name | Description | +|------|-------------| +| `country` | 2-letter country code for which offers are selected, (e.g., `US`, \ `GB`, `DE`). | -| `language` | Code for language in responses. It consists of 2 lowercase letters \ +| `language` | Code for language in responses. It consists of 2 lowercase letters \ with optional uppercase alphanumeric suffix (e.g., `en`, `en-US`, \ `de`, `de-CH1901`). | | `best_only` | Whether to return only "best" offers for each provider instead of, \ @@ -22,13 +22,13 @@ Functions returning data for multiple titles ([`search`][simplejustwatchapi.justwatch.search], [`popular`][simplejustwatchapi.justwatch.popular]) -also allow for specifying number of elements, basic pagination, and filtering for -specific providers: +also allow for specifying number of elements, basic pagination, and additional +filtering: -| Name | Description | -|-------------|-------------| -| `count` | How many entries should be returned. | -| `offset` | Basic "pagination". Offset for the first returned result, i.e. how \ +| Name | Description | +|------|-------------| +| `count` | How many entries should be returned. | +| `offset` | Basic "pagination". Offset for the first returned result, i.e. how \ many first entries should be skipped. Everything is handled on API \ side, this library isn't doing any filtering. | | `providers` | Providers (like Netflix, Amazon Prime Video) for which offers should \ @@ -37,10 +37,8 @@ of how you can get that value. | | `min_release_year` | Minimum release year of returned titles. | | `max_release_year` | Maximum release year of returned titles. | -| `object_types` | Types of objects to filter for, it seems that only `SHOW` and \ - `MOVIE` are useful, but it's not strictly enforced. Types like \ - `SHOW_EPISODE`, or `SHOW_SEASON` can be used but they seem to \ - return shows, the same as `SHOW` type. | +| `object_types` | Types of objects to filter for. It seems that only `SHOW` and \ + `MOVIE` are useful, but it's not strictly enforced. | Each function can raise two exceptions: @@ -158,16 +156,26 @@ def search( [simplejustwatchapi.justwatch.providers] function. min_release_year (int | None): Minimum release year of returned titles. + If `None` (the default value), no filtering is done. max_release_year (int | None): Maximum release year of returned titles. + If `None` (the default value), no filtering is done. - object_types (list[str] | str | None): Types of objects to filter for, it seems - that only `SHOW` and `MOVIE` are useful, but it's not strictly enforced. - Types like `SHOW_EPISODE`, or `SHOW_SEASON` can be used but they seem to - return shows, the same as `SHOW` type. If `None` (the default value), no - filtering is done. + object_types (list[str] | str | None): Types of objects to filter for, like + `SHOW` or `MOVIE`. + + It seems that only `SHOW` and `MOVIE` are useful, but it's not strictly + enforced. Types like `SHOW_EPISODE`, or `SHOW_SEASON` can be used, but they + seem to return TV shows, same as `SHOW`. + + While the type value is not enforced, it **must** be a valid type, otherwise + API will respond with HTTP status code 422. + + For single type it can be a single string, or a list with one string. + + If `None` (the default value), no filtering is done. Returns: (list[MediaEntry]): List of tuples with details of search results. @@ -175,6 +183,7 @@ def search( Raises: exceptions.JustWatchApiError: JSON response from API has internal errors, e.g., due to invalid language or country code. + exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API responded with non-`2xx` status code. @@ -272,11 +281,19 @@ def popular( max_release_year (int | None): Maximum release year of returned titles. If `None` (the default value), no filtering is done. - object_types (list[str] | str | None): Types of objects to filter for, it seems - that only `SHOW` and `MOVIE` are useful, but it's not strictly enforced. - Types like `SHOW_EPISODE`, or `SHOW_SEASON` can be used but they seem to - return shows, the same as `SHOW` type. If `None` (the default value), no - filtering is done. + object_types (list[str] | str | None): Types of objects to filter for, like + `SHOW` or `MOVIE`. + + It seems that only `SHOW` and `MOVIE` are useful, but it's not strictly + enforced. Types like `SHOW_EPISODE`, or `SHOW_SEASON` can be used, but they + seem to return TV shows, same as `SHOW`. + + While the type value is not enforced, it **must** be a valid type, otherwise + API will respond with HTTP status code 422. + + For single type it can be a single string, or a list with one string. + + If `None` (the default value), no filtering is done. Returns: (list[MediaEntry]): List of tuples with details of popular titles. @@ -284,6 +301,7 @@ def popular( Raises: exceptions.JustWatchApiError: JSON response from API has internal errors, e.g., due to invalid language or country code. + exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API responded with non-`2xx` status code. @@ -362,6 +380,7 @@ def details( Raises: exceptions.JustWatchApiError: JSON response from API has internal errors, e.g., due to invalid language or country code. + exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API responded with non-`2xx` status code. @@ -411,6 +430,7 @@ def seasons( Raises: exceptions.JustWatchApiError: JSON response from API has internal errors, e.g., due to invalid language or country code. + exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API responded with non-`2xx` status code. @@ -461,6 +481,7 @@ def episodes( Raises: exceptions.JustWatchApiError: JSON response from API has internal errors, e.g., due to invalid language or country code. + exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API responded with non-`2xx` status code. @@ -530,6 +551,7 @@ def offers_for_countries( Raises: exceptions.JustWatchApiError: JSON response from API has internal errors, e.g., due to invalid language or country code. + exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API responded with non-`2xx` status code.