Skip to content

Commit 88c001a

Browse files
Add "offset" parameter to "search"
"offset" allows skipping first "x" elements from search results. It allows for getting more data from JustWatch, without creating a single large query.
1 parent 92b788e commit 88c001a

7 files changed

Lines changed: 28 additions & 7 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ ignore = [
6161
"D212", # Conflicts with D213
6262
"FBT",
6363
"FIX002",
64+
"PLR0913", # I think it makes sense for functions here to have more arguments
6465
"S101", # Remove once asserts are converted to exceptions
6566
"TD002",
6667
"TD003",

src/simplejustwatchapi/graphql.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
$profile: PosterProfile,
2323
$backdropProfile: BackdropProfile,
2424
$filter: OfferFilter!,
25+
$offset: Int = 0,
2526
) {
2627
popularTitles(
2728
country: $country
2829
filter: $searchTitlesFilter
2930
first: $first
3031
sortBy: POPULAR
3132
sortRandomSeed: 0
33+
offset: $offset
3234
) {
3335
edges {
3436
node {

src/simplejustwatchapi/justwatch.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,22 @@ def search(
2525
language: str = "en",
2626
count: int = 4,
2727
best_only: bool = True,
28+
offset: int = 0,
2829
) -> list[MediaEntry]:
2930
"""
3031
Search JustWatch for given title.
3132
3233
Returns a list of entries up to ``count``.
3334
35+
``offset`` specifies how many first entries should be skipped. This is done on API side,
36+
not the library side; the returned list is still directly parsed from API response.
37+
I'm not sure if it guarantees stability of results - whether repeated calls to this function
38+
with increasing offset will guarantee that you won't get repeats.
39+
40+
JustWatch API won't allow for getting more than 2000 responses, either through ``count``, or
41+
when ``count + offset`` is equal or greater than 2000 - it will return an empty list instead
42+
(ALWAYS an empty list, if ``offset`` is lower than 2000 it won't include entries up to 2000).
43+
3444
``best_only`` allows filtering out redundant offers, e.g. when service provides offers
3545
in 4K, HD and SD, using ``best_only = True`` returns only 4K option, ``best_only = False``
3646
returns all three.
@@ -41,6 +51,7 @@ def search(
4151
language (str): Language of responses, ``en`` by default.
4252
count (int): How many responses should be returned.
4353
best_only (bool): Return only best offers if ``True``, return all offers if ``False``.
54+
offset (int): Search results offset.
4455
4556
Returns:
4657
list[MediaEntry]: List of ``MediaEntry`` NamedTuples parsed from JustWatch response.
@@ -49,7 +60,7 @@ def search(
4960
httpx.HTTPStatusError: If JustWatch API doesn't respond with success code.
5061
5162
"""
52-
request = prepare_search_request(title, country, language, count, best_only)
63+
request = prepare_search_request(title, country, language, count, best_only, offset)
5364
response = post(_GRAPHQL_API_URL, json=request)
5465
response.raise_for_status()
5566
return parse_search_response(response.json())

src/simplejustwatchapi/query.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def prepare_search_request(
3434
language: str,
3535
count: int,
3636
best_only: bool,
37+
offset: int,
3738
) -> dict:
3839
"""
3940
Prepare search request for JustWatch GraphQL API.
@@ -50,6 +51,7 @@ def prepare_search_request(
5051
language (str): Language of responses.
5152
count (int): How many responses should be returned.
5253
best_only (bool): Return only best offers if ``True``, return all offers if ``False``.
54+
offset (int): Search results offset.
5355
5456
Returns:
5557
dict: JSON/dict with GraphQL POST body.
@@ -68,6 +70,7 @@ def prepare_search_request(
6870
"profile": "S718",
6971
"backdropProfile": "S1920",
7072
"filter": {"bestOnly": best_only},
73+
"offset": offset or None,
7174
},
7275
"query": graphql_search_query(),
7376
}

test/simplejustwatchapi/test_graphql.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
$profile: PosterProfile,
2020
$backdropProfile: BackdropProfile,
2121
$filter: OfferFilter!,
22+
$offset: Int = 0,
2223
) {
2324
popularTitles(
2425
country: $country
2526
filter: $searchTitlesFilter
2627
first: $first
2728
sortBy: POPULAR
2829
sortRandomSeed: 0
30+
offset: $offset
2931
) {
3032
edges {
3133
node {

test/simplejustwatchapi/test_justwatch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
JUSTWATCH_GRAPHQL_URL = "https://apis.justwatch.com/graphql"
88

9-
SEARCH_INPUT = ("TITLE", "COUNTRY", "LANGUAGE", 5, True)
9+
SEARCH_INPUT = ("TITLE", "COUNTRY", "LANGUAGE", 5, True, 10)
1010
DETAILS_INPUT = ("NODE ID", "COUNTRY", "LANGUAGE", False)
1111
OFFERS_COUNTRIES_INPUT = {"COUNTRY1", "COUNTRY2", "COUNTRY3"}
1212
OFFERS_INPUT = ("NODE ID", OFFERS_COUNTRIES_INPUT, "LANGUAGE", True)

test/simplejustwatchapi/test_request.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020

2121
@patch("simplejustwatchapi.query.graphql_search_query", return_value=DUMMY_SEARCH_QUERY)
2222
@mark.parametrize(
23-
argnames=("title", "country", "language", "count", "best_only"),
23+
argnames=("title", "country", "language", "count", "best_only", "offset"),
2424
argvalues=[
25-
("TITLE 1", "US", "language 1", 5, True),
26-
("TITLE 2", "gb", "language 2", 10, False),
25+
("TITLE 1", "US", "language 1", 5, True, 0),
26+
("TITLE 2", "gb", "language 2", 10, False, 20),
2727
],
2828
)
2929
def test_prepare_search_request(
@@ -33,6 +33,7 @@ def test_prepare_search_request(
3333
language: str,
3434
count: int,
3535
best_only: bool,
36+
offset: int,
3637
):
3738
expected_request = {
3839
"operationName": "GetSearchTitles",
@@ -46,10 +47,11 @@ def test_prepare_search_request(
4647
"profile": "S718",
4748
"backdropProfile": "S1920",
4849
"filter": {"bestOnly": best_only},
50+
"offset": offset or None,
4951
},
5052
"query": DUMMY_SEARCH_QUERY,
5153
}
52-
request = prepare_search_request(title, country, language, count, best_only)
54+
request = prepare_search_request(title, country, language, count, best_only, offset)
5355
assert expected_request == request
5456

5557

@@ -67,7 +69,7 @@ def test_prepare_search_request_asserts_on_invalid_country_code(
6769
):
6870
expected_error_message = f"Invalid country code: {invalid_code}, code must be 2 characters long"
6971
with raises(AssertionError) as error:
70-
prepare_search_request("", invalid_code, "", 1, True)
72+
prepare_search_request("", invalid_code, "", 1, True, 2)
7173
assert str(error.value) == expected_error_message
7274
query_mock.assert_not_called()
7375

0 commit comments

Comments
 (0)