Skip to content

Commit 0b86681

Browse files
Modify files structure
Extract GraphQL queries/fragments and tuples to dedicated files.
1 parent 4cecc11 commit 0b86681

5 files changed

Lines changed: 670 additions & 566 deletions

File tree

src/simplejustwatchapi/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
from simplejustwatchapi.justwatch import offers_for_countries as offers_for_countries
66
from simplejustwatchapi.justwatch import search as search
77
from simplejustwatchapi.justwatch import seasons as seasons
8-
from simplejustwatchapi.query import Episode as Episode
9-
from simplejustwatchapi.query import Interactions as Interactions
10-
from simplejustwatchapi.query import MediaEntry as MediaEntry
11-
from simplejustwatchapi.query import Offer as Offer
12-
from simplejustwatchapi.query import OfferPackage as OfferPackage
13-
from simplejustwatchapi.query import Scoring as Scoring
14-
from simplejustwatchapi.query import StreamingCharts as StreamingCharts
8+
from simplejustwatchapi.tuples import Episode as Episode
9+
from simplejustwatchapi.tuples import Interactions as Interactions
10+
from simplejustwatchapi.tuples import MediaEntry as MediaEntry
11+
from simplejustwatchapi.tuples import Offer as Offer
12+
from simplejustwatchapi.tuples import OfferPackage as OfferPackage
13+
from simplejustwatchapi.tuples import Scoring as Scoring
14+
from simplejustwatchapi.tuples import StreamingCharts as StreamingCharts

src/simplejustwatchapi/graphql.py

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
"""
2+
Module responsible for preparing full GraphQL queries.
3+
4+
Queries are usually prepared as main query + details fragment + offers fragment.
5+
In the long term these queries should be moved to dedicated GraphQL resource files
6+
to allow for formatting and syntax checking.
7+
"""
8+
9+
# TODO: Convert these strings into resources, e.g.:
10+
# https://docs.python.org/3/library/importlib.resources.html
11+
12+
_GRAPHQL_SEARCH_QUERY = """
13+
query GetSearchTitles(
14+
$searchTitlesFilter: TitleFilter!,
15+
$country: Country!,
16+
$language: Language!,
17+
$first: Int!,
18+
$formatPoster: ImageFormat,
19+
$formatOfferIcon: ImageFormat,
20+
$profile: PosterProfile,
21+
$backdropProfile: BackdropProfile,
22+
$filter: OfferFilter!,
23+
) {
24+
popularTitles(
25+
country: $country
26+
filter: $searchTitlesFilter
27+
first: $first
28+
sortBy: POPULAR
29+
sortRandomSeed: 0
30+
) {
31+
edges {
32+
node {
33+
...TitleDetails
34+
__typename
35+
}
36+
__typename
37+
}
38+
__typename
39+
}
40+
}
41+
"""
42+
43+
_GRAPHQL_DETAILS_QUERY = """
44+
query GetTitleNode(
45+
$nodeId: ID!,
46+
$language: Language!,
47+
$country: Country!,
48+
$formatPoster: ImageFormat,
49+
$formatOfferIcon: ImageFormat,
50+
$profile: PosterProfile,
51+
$backdropProfile: BackdropProfile,
52+
$filter: OfferFilter!,
53+
) {
54+
node(id: $nodeId) {
55+
...TitleDetails
56+
__typename
57+
}
58+
__typename
59+
}
60+
"""
61+
62+
_GRAPHQL_SEASONS_QUERY = """
63+
query GetTitleNode(
64+
$nodeId: ID!,
65+
$language: Language!,
66+
$country: Country!,
67+
$formatPoster: ImageFormat,
68+
$formatOfferIcon: ImageFormat,
69+
$profile: PosterProfile,
70+
$backdropProfile: BackdropProfile,
71+
$filter: OfferFilter!,
72+
) {
73+
node(id: $nodeId) {
74+
...on Show {
75+
seasons(sortDirection: ASC) {
76+
...TitleDetails
77+
}
78+
}
79+
__typename
80+
}
81+
__typename
82+
}
83+
"""
84+
85+
_GRAPHQL_EPISODES_QUERY = """
86+
query GetTitleNode(
87+
$nodeId: ID!,
88+
$language: Language!,
89+
$country: Country!,
90+
$formatPoster: ImageFormat,
91+
$formatOfferIcon: ImageFormat,
92+
$profile: PosterProfile,
93+
$backdropProfile: BackdropProfile,
94+
$filter: OfferFilter!,
95+
) {
96+
node(id: $nodeId) {
97+
...on Season {
98+
episodes(sortDirection: ASC) {
99+
...TitleDetails
100+
}
101+
}
102+
__typename
103+
}
104+
__typename
105+
}
106+
"""
107+
108+
_GRAPHQL_OFFERS_BY_COUNTRY_QUERY = """
109+
query GetTitleOffers(
110+
$nodeId: ID!,
111+
$language: Language!,
112+
$formatOfferIcon: ImageFormat,
113+
$filter: OfferFilter!,
114+
) {{
115+
node(id: $nodeId) {{
116+
... on MovieOrShowOrSeasonOrEpisode {{
117+
{country_entries}
118+
__typename
119+
}}
120+
__typename
121+
}}
122+
__typename
123+
}}
124+
"""
125+
126+
_GRAPHQL_DETAILS_FRAGMENT = """
127+
fragment TitleDetails on MovieOrShowOrSeasonOrEpisode {
128+
id
129+
objectId
130+
objectType
131+
content(country: $country, language: $language) {
132+
...ContentDetails
133+
__typename
134+
}
135+
...StreamingChartInfoFragment
136+
...on Show {
137+
totalSeasonCount
138+
}
139+
...on Season {
140+
totalEpisodeCount
141+
}
142+
offers(country: $country, platform: WEB, filter: $filter) {
143+
...TitleOffer
144+
}
145+
__typename
146+
}
147+
148+
fragment StreamingChartInfoFragment on MovieOrShowOrSeason {
149+
streamingCharts(country: $country) {
150+
edges {
151+
streamingChartInfo {
152+
rank
153+
trend
154+
trendDifference
155+
daysInTop3
156+
daysInTop10
157+
daysInTop100
158+
daysInTop1000
159+
topRank
160+
updatedAt
161+
__typename
162+
}
163+
__typename
164+
}
165+
__typename
166+
}
167+
}
168+
169+
fragment ContentDetails on MovieOrShowOrSeasonOrEpisodeContent {
170+
title
171+
originalReleaseYear
172+
originalReleaseDate
173+
runtime
174+
shortDescription
175+
...FullContentDetails
176+
...on MovieOrShowContent {
177+
ageCertification
178+
}
179+
...on SeasonContent {
180+
seasonNumber
181+
}
182+
...on EpisodeContent {
183+
seasonNumber
184+
episodeNumber
185+
}
186+
}
187+
188+
fragment FullContentDetails on MovieOrShowOrSeasonContent {
189+
fullPath
190+
genres {
191+
shortName
192+
__typename
193+
}
194+
externalIds {
195+
imdbId
196+
tmdbId
197+
__typename
198+
}
199+
posterUrl(profile: $profile, format: $formatPoster)
200+
backdrops(profile: $backdropProfile, format: $formatPoster) {
201+
backdropUrl
202+
__typename
203+
}
204+
scoring {
205+
imdbScore
206+
imdbVotes
207+
tmdbPopularity
208+
tmdbScore
209+
tomatoMeter
210+
certifiedFresh
211+
jwRating
212+
__typename
213+
}
214+
interactions {
215+
likelistAdditions
216+
dislikelistAdditions
217+
__typename
218+
}
219+
}
220+
"""
221+
222+
_GRAPHQL_OFFER_FRAGMENT = """
223+
fragment TitleOffer on Offer {
224+
id
225+
monetizationType
226+
presentationType
227+
retailPrice(language: $language)
228+
retailPriceValue
229+
currency
230+
lastChangeRetailPriceValue
231+
type
232+
package {
233+
id
234+
packageId
235+
clearName
236+
technicalName
237+
icon(profile: S100, format: $formatOfferIcon)
238+
__typename
239+
}
240+
standardWebURL
241+
elementCount
242+
availableTo
243+
deeplinkRoku: deeplinkURL(platform: ROKU_OS)
244+
subtitleLanguages
245+
videoTechnology
246+
audioTechnology
247+
audioLanguages
248+
__typename
249+
}
250+
"""
251+
252+
_GRAPHQL_COUNTRY_OFFERS_ENTRY = """
253+
{country_code}: offers(country: {country_code}, platform: WEB, filter: $filter) {{
254+
...TitleOffer
255+
__typename
256+
}}
257+
"""
258+
259+
260+
def graphql_search_query() -> str:
261+
"""
262+
Prepare GraphQL query used for searching for entries.
263+
264+
The query is GetSearchTitles query + details fragment + offers fragment.
265+
266+
Returns:
267+
str with full GraphQL "search" query
268+
269+
"""
270+
return _GRAPHQL_SEARCH_QUERY + _GRAPHQL_DETAILS_FRAGMENT + _GRAPHQL_OFFER_FRAGMENT
271+
272+
273+
def graphql_details_query() -> str:
274+
"""
275+
Prepare GraphQL query used for getting details regarding a single entry.
276+
277+
The full query is GetTitleNode query + details fragment + offers fragment.
278+
It is meant for movies and shows, but can be used for seasons and episodes as well,
279+
it just won't return full season/episodes list.
280+
281+
Returns:
282+
str with full GraphQL "get details" query
283+
284+
"""
285+
return _GRAPHQL_DETAILS_QUERY + _GRAPHQL_DETAILS_FRAGMENT + _GRAPHQL_OFFER_FRAGMENT
286+
287+
288+
def graphql_seasons_query() -> str:
289+
"""
290+
Prepare GraphQL query used for getting a list of seasons for a single show.
291+
292+
The full query is GetTitleNode query (with seasons list) + details fragment + offers fragment.
293+
It will only return data for shows with a list of all available seasons, ascending.
294+
Details query itself matches :func:`graphql_details_query`, its conditions will return all
295+
relevant data for seasons.
296+
297+
Returns:
298+
str with full GraphQL "get seasons" query
299+
300+
"""
301+
return _GRAPHQL_SEASONS_QUERY + _GRAPHQL_DETAILS_FRAGMENT + _GRAPHQL_OFFER_FRAGMENT
302+
303+
304+
def graphql_episodes_query() -> str:
305+
"""
306+
Prepare GraphQL query used for getting a list of episodes for a single show season.
307+
308+
The full query is GetTitleNode query (with episodes list) + details fragment + offers fragment.
309+
It will only return data for show seasons with a list of all available episodes, ascending.
310+
Details query itself matches :func:`graphql_details_query`, its conditions will return all
311+
relevant data for episodes.
312+
313+
Returns:
314+
str with full GraphQL "get episodes" query
315+
316+
"""
317+
return _GRAPHQL_EPISODES_QUERY + _GRAPHQL_DETAILS_FRAGMENT + _GRAPHQL_OFFER_FRAGMENT
318+
319+
320+
def graphql_offers_for_countries_query(countries: set[str]) -> str:
321+
"""
322+
Prepare GraphQL query with a list of offers from specified countries.
323+
324+
The full query is GetTitleOffers query with a list of offers per country.
325+
No additional information is returned, only offers.
326+
Can be used for all entry types - movies, shows, seasons, episodes.
327+
328+
The input is a set of 2-letter country codes. This function assumes that codes are valid length,
329+
the set is not empty; it performs no additional verification.
330+
331+
Args:
332+
countries: set of 2-letter country codes
333+
334+
Returns:
335+
str with full GraphQL "get offers per country" query
336+
337+
"""
338+
offer_requests = [
339+
_GRAPHQL_COUNTRY_OFFERS_ENTRY.format(country_code=country_code.upper())
340+
for country_code in countries
341+
]
342+
main_query = _GRAPHQL_OFFERS_BY_COUNTRY_QUERY.format(country_entries="\n".join(offer_requests))
343+
return main_query + _GRAPHQL_OFFER_FRAGMENT

0 commit comments

Comments
 (0)