Skip to content

Commit 9a1c45e

Browse files
Initial implementation for "providers" function
The function returns data for all providers for a given country. The parsed data structure matches "OfferPackage" so it is reused.
1 parent 3463f2a commit 9a1c45e

6 files changed

Lines changed: 141 additions & 25 deletions

File tree

src/simplejustwatchapi/graphql.py

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,22 @@
142142
}
143143
"""
144144

145+
_GRAPHQL_PROVIDERS_QUERY = """
146+
query GetProviders(
147+
$country: Country!,
148+
$formatOfferIcon: ImageFormat
149+
) {
150+
packages(
151+
country: $country
152+
platform: WEB
153+
includeAddons: true
154+
) {
155+
... PackageDetails
156+
}
157+
__typename
158+
}
159+
"""
160+
145161
_GRAPHQL_OFFERS_BY_COUNTRY_QUERY = """
146162
query GetTitleOffers(
147163
$nodeId: ID!,
@@ -267,12 +283,7 @@
267283
lastChangeRetailPriceValue
268284
type
269285
package {
270-
id
271-
packageId
272-
clearName
273-
technicalName
274-
icon(profile: S100, format: $formatOfferIcon)
275-
__typename
286+
... PackageDetails
276287
}
277288
standardWebURL
278289
elementCount
@@ -286,6 +297,19 @@
286297
}
287298
"""
288299

300+
_GRAPHQL_PACKAGE_FRAGMENT = """
301+
fragment PackageDetails on Package {
302+
id
303+
packageId
304+
clearName
305+
technicalName
306+
shortName
307+
slug
308+
icon(profile: S100, format: $formatOfferIcon)
309+
__typename
310+
}
311+
"""
312+
289313
_GRAPHQL_COUNTRY_OFFERS_ENTRY = """
290314
{country_code}: offers(country: {country_code}, platform: WEB, filter: $filter) {{
291315
...TitleOffer
@@ -304,11 +328,25 @@ def graphql_search_query() -> str:
304328
str: Full GraphQL "search" query.
305329
306330
"""
307-
return _GRAPHQL_SEARCH_QUERY + _GRAPHQL_DETAILS_FRAGMENT + _GRAPHQL_OFFER_FRAGMENT
331+
return (
332+
_GRAPHQL_SEARCH_QUERY
333+
+ _GRAPHQL_DETAILS_FRAGMENT
334+
+ _GRAPHQL_OFFER_FRAGMENT
335+
+ _GRAPHQL_PACKAGE_FRAGMENT
336+
)
308337

309338

310339
def graphql_popular_query() -> str:
311-
return _GRAPHQL_POPULAR_QUERY + _GRAPHQL_DETAILS_FRAGMENT + _GRAPHQL_OFFER_FRAGMENT
340+
return (
341+
_GRAPHQL_POPULAR_QUERY
342+
+ _GRAPHQL_DETAILS_FRAGMENT
343+
+ _GRAPHQL_OFFER_FRAGMENT
344+
+ _GRAPHQL_PACKAGE_FRAGMENT
345+
)
346+
347+
348+
def graphql_providers_query() -> str:
349+
return _GRAPHQL_PROVIDERS_QUERY + _GRAPHQL_PACKAGE_FRAGMENT
312350

313351

314352
def graphql_details_query() -> str:
@@ -323,7 +361,12 @@ def graphql_details_query() -> str:
323361
str: Full GraphQL "get details" query.
324362
325363
"""
326-
return _GRAPHQL_DETAILS_QUERY + _GRAPHQL_DETAILS_FRAGMENT + _GRAPHQL_OFFER_FRAGMENT
364+
return (
365+
_GRAPHQL_DETAILS_QUERY
366+
+ _GRAPHQL_DETAILS_FRAGMENT
367+
+ _GRAPHQL_OFFER_FRAGMENT
368+
+ _GRAPHQL_PACKAGE_FRAGMENT
369+
)
327370

328371

329372
def graphql_seasons_query() -> str:
@@ -339,7 +382,12 @@ def graphql_seasons_query() -> str:
339382
str: Full GraphQL "get seasons" query.
340383
341384
"""
342-
return _GRAPHQL_SEASONS_QUERY + _GRAPHQL_DETAILS_FRAGMENT + _GRAPHQL_OFFER_FRAGMENT
385+
return (
386+
_GRAPHQL_SEASONS_QUERY
387+
+ _GRAPHQL_DETAILS_FRAGMENT
388+
+ _GRAPHQL_OFFER_FRAGMENT
389+
+ _GRAPHQL_PACKAGE_FRAGMENT
390+
)
343391

344392

345393
def graphql_episodes_query() -> str:
@@ -355,7 +403,12 @@ def graphql_episodes_query() -> str:
355403
str: Full GraphQL "get episodes" query.
356404
357405
"""
358-
return _GRAPHQL_EPISODES_QUERY + _GRAPHQL_DETAILS_FRAGMENT + _GRAPHQL_OFFER_FRAGMENT
406+
return (
407+
_GRAPHQL_EPISODES_QUERY
408+
+ _GRAPHQL_DETAILS_FRAGMENT
409+
+ _GRAPHQL_OFFER_FRAGMENT
410+
+ _GRAPHQL_PACKAGE_FRAGMENT
411+
)
359412

360413

361414
def graphql_offers_for_countries_query(countries: set[str]) -> str:
@@ -381,4 +434,4 @@ def graphql_offers_for_countries_query(countries: set[str]) -> str:
381434
for country_code in countries
382435
]
383436
main_query = _GRAPHQL_OFFERS_BY_COUNTRY_QUERY.format(country_entries="\n".join(offer_requests))
384-
return main_query + _GRAPHQL_OFFER_FRAGMENT
437+
return main_query + _GRAPHQL_OFFER_FRAGMENT + _GRAPHQL_PACKAGE_FRAGMENT

src/simplejustwatchapi/justwatch.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@
77
parse_episodes_response,
88
parse_offers_for_countries_response,
99
parse_popular_response,
10+
parse_providers_response,
1011
parse_search_response,
1112
parse_seasons_response,
1213
prepare_details_request,
1314
prepare_episodes_request,
1415
prepare_offers_for_countries_request,
1516
prepare_popular_request,
17+
prepare_providers_request,
1618
prepare_search_request,
1719
prepare_seasons_request,
1820
)
19-
from simplejustwatchapi.tuples import Episode, MediaEntry, Offer
21+
from simplejustwatchapi.tuples import Episode, MediaEntry, Offer, OfferPackage
2022

2123
_GRAPHQL_API_URL = "https://apis.justwatch.com/graphql"
2224

@@ -233,3 +235,10 @@ def offers_for_countries(
233235
response = post(_GRAPHQL_API_URL, json=request)
234236
response.raise_for_status()
235237
return parse_offers_for_countries_response(response.json(), countries)
238+
239+
240+
def providers(country: str) -> list[OfferPackage]:
241+
request = prepare_providers_request(country)
242+
response = post(_GRAPHQL_API_URL, json=request)
243+
response.raise_for_status()
244+
return parse_providers_response(response.json())

src/simplejustwatchapi/query.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
graphql_episodes_query,
1212
graphql_offers_for_countries_query,
1313
graphql_popular_query,
14+
graphql_providers_query,
1415
graphql_search_query,
1516
graphql_seasons_query,
1617
)
@@ -388,6 +389,23 @@ def parse_offers_for_countries_response(json: dict, countries: set[str]) -> dict
388389
}
389390

390391

392+
def prepare_providers_request(country: str) -> dict:
393+
_assert_country_code_is_valid(country)
394+
return {
395+
"operationName": "GetProviders",
396+
"variables": {
397+
"country": country.upper(),
398+
"formatOfferIcon": "PNG",
399+
"fullPath": "/us/tv-show/the-madison",
400+
},
401+
"query": graphql_providers_query(),
402+
}
403+
404+
405+
def parse_providers_response(json: dict) -> list[OfferPackage]:
406+
return [_parse_package(package) for package in json["data"]["packages"]]
407+
408+
391409
def _assert_country_code_is_valid(code: str) -> None:
392410
# TODO: Convert assert to regular exception
393411
code_has_valid_length = len(code) == _COUNTRY_CODE_LENGTH
@@ -583,5 +601,6 @@ def _parse_package(json: Any) -> OfferPackage:
583601
package_id = json.get("packageId")
584602
name = json.get("clearName")
585603
technical_name = json.get("technicalName")
604+
short_name = json.get("shortName")
586605
icon = _IMAGES_URL + json.get("icon")
587-
return OfferPackage(platform_id, package_id, name, technical_name, icon)
606+
return OfferPackage(platform_id, package_id, name, technical_name, short_name, icon)

src/simplejustwatchapi/tuples.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class OfferPackage(NamedTuple):
2424
package_id: int
2525
name: str
2626
technical_name: str
27+
short_name: str
2728
icon: str
2829

2930

test/simplejustwatchapi/test_graphql.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,7 @@
231231
lastChangeRetailPriceValue
232232
type
233233
package {
234-
id
235-
packageId
236-
clearName
237-
technicalName
238-
icon(profile: S100, format: $formatOfferIcon)
239-
__typename
234+
... PackageDetails
240235
}
241236
standardWebURL
242237
elementCount
@@ -250,6 +245,19 @@
250245
}
251246
"""
252247

248+
GRAPHQL_PACKAGE_FRAGMENT = """
249+
fragment PackageDetails on Package {
250+
id
251+
packageId
252+
clearName
253+
technicalName
254+
shortName
255+
slug
256+
icon(profile: S100, format: $formatOfferIcon)
257+
__typename
258+
}
259+
"""
260+
253261
GRAPHQL_COUNTRY_OFFERS_ENTRY = """
254262
{country_code}: offers(country: {country_code}, platform: WEB, filter: $filter) {{
255263
...TitleOffer
@@ -259,25 +267,45 @@
259267

260268

261269
def test_graphql_search_query():
262-
expected_query = GRAPHQL_SEARCH_QUERY + GRAPHQL_DETAILS_FRAGMENT + GRAPHQL_OFFER_FRAGMENT
270+
expected_query = (
271+
GRAPHQL_SEARCH_QUERY
272+
+ GRAPHQL_DETAILS_FRAGMENT
273+
+ GRAPHQL_OFFER_FRAGMENT
274+
+ GRAPHQL_PACKAGE_FRAGMENT
275+
)
263276
query = graphql_search_query()
264277
assert expected_query == query
265278

266279

267280
def test_graphql_details_query():
268-
expected_query = GRAPHQL_DETAILS_QUERY + GRAPHQL_DETAILS_FRAGMENT + GRAPHQL_OFFER_FRAGMENT
281+
expected_query = (
282+
GRAPHQL_DETAILS_QUERY
283+
+ GRAPHQL_DETAILS_FRAGMENT
284+
+ GRAPHQL_OFFER_FRAGMENT
285+
+ GRAPHQL_PACKAGE_FRAGMENT
286+
)
269287
query = graphql_details_query()
270288
assert expected_query == query
271289

272290

273291
def test_graphql_seasons_query():
274-
expected_query = GRAPHQL_SEASONS_QUERY + GRAPHQL_DETAILS_FRAGMENT + GRAPHQL_OFFER_FRAGMENT
292+
expected_query = (
293+
GRAPHQL_SEASONS_QUERY
294+
+ GRAPHQL_DETAILS_FRAGMENT
295+
+ GRAPHQL_OFFER_FRAGMENT
296+
+ GRAPHQL_PACKAGE_FRAGMENT
297+
)
275298
query = graphql_seasons_query()
276299
assert expected_query == query
277300

278301

279302
def test_graphql_episodes_query():
280-
expected_query = GRAPHQL_EPISODES_QUERY + GRAPHQL_DETAILS_FRAGMENT + GRAPHQL_OFFER_FRAGMENT
303+
expected_query = (
304+
GRAPHQL_EPISODES_QUERY
305+
+ GRAPHQL_DETAILS_FRAGMENT
306+
+ GRAPHQL_OFFER_FRAGMENT
307+
+ GRAPHQL_PACKAGE_FRAGMENT
308+
)
281309
query = graphql_episodes_query()
282310
assert expected_query == query
283311

@@ -289,6 +317,6 @@ def test_graphql_offers_for_countries_query(country_codes):
289317
for country_code in country_codes
290318
]
291319
main_body = GRAPHQL_OFFERS_BY_COUNTRY_QUERY.format(country_entries="\n".join(offer_requests))
292-
expected_query = main_body + GRAPHQL_OFFER_FRAGMENT
320+
expected_query = main_body + GRAPHQL_OFFER_FRAGMENT + GRAPHQL_PACKAGE_FRAGMENT
293321
query = graphql_offers_for_countries_query(country_codes)
294322
assert expected_query == query

test/simplejustwatchapi/test_parser.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"packageId": 1,
3636
"clearName": "Service 1",
3737
"technicalName": "service1",
38+
"shortName": "sr1",
3839
"icon": "/icon/url/service1",
3940
},
4041
"standardWebURL": "www.service1.com/offer/url/1/",
@@ -60,6 +61,7 @@
6061
"packageId": 2,
6162
"clearName": "Service 2",
6263
"technicalName": "service2",
64+
"shortName": "sr2",
6365
"icon": "/icon/url/service2",
6466
},
6567
"standardWebURL": "www.service1.com/offer/url/2/",
@@ -87,6 +89,7 @@
8789
1,
8890
"Service 1",
8991
"service1",
92+
"sr1",
9093
IMAGES_URL + "/icon/url/service1",
9194
),
9295
"www.service1.com/offer/url/1/",
@@ -112,6 +115,7 @@
112115
2,
113116
"Service 2",
114117
"service2",
118+
"sr2",
115119
IMAGES_URL + "/icon/url/service2",
116120
),
117121
"www.service1.com/offer/url/2/",
@@ -140,6 +144,7 @@
140144
"packageId": 3,
141145
"clearName": "Service 3",
142146
"technicalName": "service3",
147+
"shortName": "sr3",
143148
"icon": "/icon/url/service3",
144149
},
145150
"standardWebURL": "www.service3.com/offer/url/1/",
@@ -167,6 +172,7 @@
167172
3,
168173
"Service 3",
169174
"service3",
175+
"sr3",
170176
IMAGES_URL + "/icon/url/service3",
171177
),
172178
"www.service3.com/offer/url/1/",

0 commit comments

Comments
 (0)