simple-justwatch-python-api 1.0.3__py3-none-any.whl → 1.2.0__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simple-justwatch-python-api
3
- Version: 1.0.3
3
+ Version: 1.2.0
4
4
  Summary: A simple JustWatch Python API
5
5
  Keywords: justwatch,api,graphql
6
6
  Author: Electronic Mango
@@ -91,8 +91,8 @@ for entry in results:
91
91
 
92
92
  ## License
93
93
 
94
- This library is licensed under **MIT license** (
95
- [LICENSE](https://github.com/Electronic-Mango/simple-justwatch-python-api/blob/main/LICENSE)
94
+ This library is licensed under **MIT license**
95
+ ([LICENSE](https://github.com/Electronic-Mango/simple-justwatch-python-api/blob/main/LICENSE)
96
96
  or <https://opensource.org/license/MIT>).
97
97
 
98
98
 
@@ -0,0 +1,10 @@
1
+ simplejustwatchapi/__init__.py,sha256=Tqj7re00Qk8_OjZPM-jdUbKQPEido1rAUORWDOH5YSw,816
2
+ simplejustwatchapi/exceptions.py,sha256=3YqWgyB7ebPd9NPA0okezKuColuOaCZSfNl2um-TmAY,1914
3
+ simplejustwatchapi/graphql.py,sha256=Kpl0XA6dIVPVZ4QVQwxamGxJxgIzIvg_ufhAvzALGDA,8340
4
+ simplejustwatchapi/justwatch.py,sha256=NAm5-PITfbbPcOcFbVK_P0gfSmy9nm1RxW_pG8Q6mFk,22164
5
+ simplejustwatchapi/query.py,sha256=P6b0pJD7SZVgsC7iqkbFU93Pbrsane2Cr0IfioCPZUo,23177
6
+ simplejustwatchapi/tuples.py,sha256=LkGm6s1y-4nDvfAnpMVtB4DD8U3XYZRel7IjpR66ijI,10418
7
+ simple_justwatch_python_api-1.2.0.dist-info/licenses/LICENSE,sha256=4RylkfuLsHZh0xqHJeDHm0QoxPSIbfeHKwHiVoNsyxE,1073
8
+ simple_justwatch_python_api-1.2.0.dist-info/WHEEL,sha256=WvwXFgRajeoYkfRVmDhkP4Qlqo31Mk687zIO2QQoFmw,80
9
+ simple_justwatch_python_api-1.2.0.dist-info/METADATA,sha256=vmG7agxtqXxv7DC6GWRKUaJuW3Sq4s8Yu0HZXiShdgY,4478
10
+ simple_justwatch_python_api-1.2.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.11.6
2
+ Generator: uv 0.11.7
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -4,14 +4,7 @@ Exceptions raised by this library.
4
4
  All exceptions inherit from [`JustWatchError`]
5
5
  [simplejustwatchapi.exceptions.JustWatchError] for easier catching.
6
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
-
7
+ Specific exceptions are raised for HTTP-related errors and GraphQL API response errors.
15
8
  """
16
9
 
17
10
 
@@ -23,7 +16,7 @@ class JustWatchApiError(JustWatchError):
23
16
  """
24
17
  Raised when JustWatch API returned errors in JSON response.
25
18
 
26
- If this error is raised, then API responded with status code `2xx`, but there are
19
+ If this error is raised, then no HTTP-related error occurred, but there are
27
20
  listed errors in the internal JSON response. It can happen for too high complexity
28
21
  of request, invalid node ID in functions like [`details`]
29
22
  [simplejustwatchapi.justwatch.details], or invalid country or language codes.
@@ -43,18 +36,19 @@ class JustWatchApiError(JustWatchError):
43
36
 
44
37
  class JustWatchHttpError(JustWatchError):
45
38
  """
46
- Raised when JustWatch API returned a non-`2xx` status code.
39
+ Raised when HTTP-related error occurs.
47
40
 
48
- Any additional verification is not performed, ony the status code is checked.
41
+ This is a general exception for any HTTP-related errors, such as non-`2xx` status
42
+ codes, network errors, timeouts, etc.
49
43
 
50
44
  Attributes:
51
- code (int): HTTP status code returned by the API.
52
- message (str): HTTP message response from the API.
45
+ msg (str): Error message describing the HTTP error.
46
+ response (str | None): Optional text of the HTTP response, if available.
47
+ Usucally contains JSON with error responses from the API.
53
48
 
54
49
  """
55
50
 
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
51
+ def __init__(self, msg: str, response: str | None = None) -> None:
52
+ """Init JustWatchHttpError with error message and optional response text."""
53
+ super().__init__(msg)
54
+ self.response = response
@@ -4,464 +4,351 @@ Module responsible for preparing full GraphQL queries.
4
4
  Queries are usually prepared as main query + needed fragments.
5
5
  Specific details are stored as separate GraphQL fragments / Python strings for easier
6
6
  reuse and maintainability.
7
-
8
- In the long term these queries should be moved to dedicated GraphQL resource files
9
- to allow for formatting and syntax checking. However, the functions used for
10
- constructing full queries shouldn't change.
11
7
  """
12
8
 
13
- # TODO: Convert these strings into resources, e.g.,:
14
- # https://docs.python.org/3/library/importlib.resources.html
15
-
16
9
  _GRAPHQL_SEARCH_QUERY = """
17
10
  query GetSearchTitles(
18
- $searchTitlesFilter: TitleFilter!,
19
- $country: Country!,
20
- $language: Language!,
21
- $first: Int!,
22
- $formatPoster: ImageFormat,
23
- $formatOfferIcon: ImageFormat,
24
- $profile: PosterProfile,
25
- $backdropProfile: BackdropProfile,
26
- $filter: OfferFilter!,
27
- $offset: Int = 0,
11
+ $searchTitlesFilter: TitleFilter!,
12
+ $country: Country!,
13
+ $language: Language!,
14
+ $first: Int!,
15
+ $formatPoster: ImageFormat,
16
+ $formatOfferIcon: ImageFormat,
17
+ $profile: PosterProfile,
18
+ $backdropProfile: BackdropProfile,
19
+ $filter: OfferFilter!,
20
+ $offset: Int = 0,
28
21
  ) {
29
- popularTitles(
30
- country: $country
31
- filter: $searchTitlesFilter
32
- first: $first
33
- sortBy: POPULAR
34
- sortRandomSeed: 0
35
- offset: $offset
36
- ) {
37
- edges {
38
- node {
39
- ...TitleDetails
22
+ popularTitles(
23
+ country: $country
24
+ filter: $searchTitlesFilter
25
+ first: $first
26
+ sortBy: POPULAR
27
+ sortRandomSeed: 0
28
+ offset: $offset
29
+ ) {
30
+ edges {
31
+ node {
32
+ ...TitleDetails
33
+ __typename
34
+ }
35
+ __typename
36
+ }
40
37
  __typename
41
- }
42
- __typename
43
38
  }
44
- __typename
45
- }
46
39
  }
47
40
  """
48
41
 
49
42
  _GRAPHQL_POPULAR_QUERY = """
50
43
  query GetPopularTitles(
51
- $popularTitlesFilter: TitleFilter
52
- $country: Country!
53
- $language: Language!
54
- $first: Int! = 70
55
- $formatPoster: ImageFormat,
56
- $formatOfferIcon: ImageFormat,
57
- $profile: PosterProfile
58
- $backdropProfile: BackdropProfile,
59
- $filter: OfferFilter!,
60
- $offset: Int = 0
44
+ $popularTitlesFilter: TitleFilter
45
+ $country: Country!
46
+ $language: Language!
47
+ $first: Int! = 70
48
+ $formatPoster: ImageFormat,
49
+ $formatOfferIcon: ImageFormat,
50
+ $profile: PosterProfile
51
+ $backdropProfile: BackdropProfile,
52
+ $filter: OfferFilter!,
53
+ $offset: Int = 0
61
54
  ) {
62
- popularTitles(
63
- country: $country
64
- filter: $popularTitlesFilter
65
- first: $first
66
- sortBy: POPULAR
67
- sortRandomSeed: 0
68
- offset: $offset
69
- ) {
70
- __typename
71
- edges {
72
- node {
73
- ...TitleDetails
55
+ popularTitles(
56
+ country: $country
57
+ filter: $popularTitlesFilter
58
+ first: $first
59
+ sortBy: POPULAR
60
+ sortRandomSeed: 0
61
+ offset: $offset
62
+ ) {
74
63
  __typename
75
- }
76
- __typename
64
+ edges {
65
+ node {
66
+ ...TitleDetails
67
+ __typename
68
+ }
69
+ __typename
70
+ }
77
71
  }
78
- }
79
72
  }
80
73
  """
81
74
 
82
75
  _GRAPHQL_DETAILS_QUERY = """
83
76
  query GetTitleNode(
84
- $nodeId: ID!,
85
- $language: Language!,
86
- $country: Country!,
87
- $formatPoster: ImageFormat,
88
- $formatOfferIcon: ImageFormat,
89
- $profile: PosterProfile,
90
- $backdropProfile: BackdropProfile,
91
- $filter: OfferFilter!,
77
+ $nodeId: ID!,
78
+ $language: Language!,
79
+ $country: Country!,
80
+ $formatPoster: ImageFormat,
81
+ $formatOfferIcon: ImageFormat,
82
+ $profile: PosterProfile,
83
+ $backdropProfile: BackdropProfile,
84
+ $filter: OfferFilter!,
92
85
  ) {
93
- node(id: $nodeId) {
94
- ...TitleDetails
86
+ node(id: $nodeId) {
87
+ ...TitleDetails
88
+ __typename
89
+ }
95
90
  __typename
96
- }
97
- __typename
98
91
  }
99
92
  """
100
93
 
101
94
  _GRAPHQL_SEASONS_QUERY = """
102
95
  query GetTitleNode(
103
- $nodeId: ID!,
104
- $language: Language!,
105
- $country: Country!,
106
- $formatPoster: ImageFormat,
107
- $formatOfferIcon: ImageFormat,
108
- $profile: PosterProfile,
109
- $backdropProfile: BackdropProfile,
110
- $filter: OfferFilter!,
96
+ $nodeId: ID!,
97
+ $language: Language!,
98
+ $country: Country!,
99
+ $formatPoster: ImageFormat,
100
+ $formatOfferIcon: ImageFormat,
101
+ $profile: PosterProfile,
102
+ $backdropProfile: BackdropProfile,
103
+ $filter: OfferFilter!,
111
104
  ) {
112
- node(id: $nodeId) {
113
- ...on Show {
114
- seasons(sortDirection: ASC) {
115
- ...TitleDetails
116
- }
105
+ node(id: $nodeId) {
106
+ ... on Show {
107
+ seasons(sortDirection: ASC) {
108
+ ...TitleDetails
109
+ }
110
+ }
111
+ __typename
117
112
  }
118
113
  __typename
119
- }
120
- __typename
121
114
  }
122
115
  """
123
116
 
124
117
  _GRAPHQL_EPISODES_QUERY = """
125
118
  query GetTitleNode(
126
- $nodeId: ID!,
127
- $language: Language!,
128
- $country: Country!,
129
- $formatPoster: ImageFormat,
130
- $formatOfferIcon: ImageFormat,
131
- $profile: PosterProfile,
132
- $backdropProfile: BackdropProfile,
133
- $filter: OfferFilter!,
119
+ $nodeId: ID!,
120
+ $language: Language!,
121
+ $country: Country!,
122
+ $formatPoster: ImageFormat,
123
+ $formatOfferIcon: ImageFormat,
124
+ $profile: PosterProfile,
125
+ $backdropProfile: BackdropProfile,
126
+ $filter: OfferFilter!,
134
127
  ) {
135
- node(id: $nodeId) {
136
- ...on Season {
137
- episodes(sortDirection: ASC) {
138
- ...TitleDetails
139
- }
128
+ node(id: $nodeId) {
129
+ ... on Season {
130
+ episodes(sortDirection: ASC) {
131
+ ...TitleDetails
132
+ }
133
+ }
134
+ __typename
140
135
  }
141
136
  __typename
142
- }
143
- __typename
144
137
  }
145
138
  """
146
139
 
147
140
  _GRAPHQL_PROVIDERS_QUERY = """
148
141
  query GetProviders(
149
- $country: Country!,
150
- $formatOfferIcon: ImageFormat
142
+ $country: Country!,
143
+ $formatOfferIcon: ImageFormat
151
144
  ) {
152
- packages(
153
- country: $country
154
- platform: WEB
155
- includeAddons: true
156
- ) {
157
- ... PackageDetails
158
- }
159
- __typename
145
+ packages(
146
+ country: $country
147
+ platform: WEB
148
+ includeAddons: true
149
+ ) {
150
+ ...PackageDetails
151
+ }
152
+ __typename
160
153
  }
161
154
  """
162
155
 
163
156
  _GRAPHQL_OFFERS_BY_COUNTRY_QUERY = """
164
157
  query GetTitleOffers(
165
- $nodeId: ID!,
166
- $language: Language!,
167
- $formatOfferIcon: ImageFormat,
168
- $filter: OfferFilter!,
158
+ $nodeId: ID!,
159
+ $language: Language!,
160
+ $formatOfferIcon: ImageFormat,
161
+ $filter: OfferFilter!,
169
162
  ) {{
170
- node(id: $nodeId) {{
171
- ... on MovieOrShowOrSeasonOrEpisode {{
172
- {country_entries}
173
- __typename
163
+ node(id: $nodeId) {{
164
+ ... on MovieOrShowOrSeasonOrEpisode {{
165
+ {country_entries}
166
+ __typename
167
+ }}
168
+ __typename
174
169
  }}
175
170
  __typename
176
- }}
177
- __typename
178
171
  }}
179
172
  """
180
173
 
181
174
  _GRAPHQL_DETAILS_FRAGMENT = """
182
175
  fragment TitleDetails on MovieOrShowOrSeasonOrEpisode {
183
- id
184
- objectId
185
- objectType
186
- content(country: $country, language: $language) {
187
- ...ContentDetails
176
+ id
177
+ objectId
178
+ objectType
179
+ content(country: $country, language: $language) {
180
+ ...ContentDetails
181
+ __typename
182
+ }
183
+ ...StreamingChartInfoFragment
184
+ ... on Show {
185
+ totalSeasonCount
186
+ }
187
+ ... on Season {
188
+ totalEpisodeCount
189
+ }
190
+ offers(country: $country, platform: WEB, filter: $filter) {
191
+ ...TitleOffer
192
+ }
188
193
  __typename
189
- }
190
- ...StreamingChartInfoFragment
191
- ...on Show {
192
- totalSeasonCount
193
- }
194
- ...on Season {
195
- totalEpisodeCount
196
- }
197
- offers(country: $country, platform: WEB, filter: $filter) {
198
- ...TitleOffer
199
- }
200
- __typename
201
194
  }
202
195
 
203
196
  fragment StreamingChartInfoFragment on MovieOrShowOrSeason {
204
- streamingCharts(country: $country) {
205
- edges {
206
- streamingChartInfo {
207
- rank
208
- trend
209
- trendDifference
210
- daysInTop3
211
- daysInTop10
212
- daysInTop100
213
- daysInTop1000
214
- topRank
215
- updatedAt
197
+ streamingCharts(country: $country) {
198
+ edges {
199
+ streamingChartInfo {
200
+ rank
201
+ trend
202
+ trendDifference
203
+ daysInTop3
204
+ daysInTop10
205
+ daysInTop100
206
+ daysInTop1000
207
+ topRank
208
+ updatedAt
209
+ __typename
210
+ }
211
+ __typename
212
+ }
216
213
  __typename
217
- }
218
- __typename
219
214
  }
220
- __typename
221
- }
222
215
  }
223
216
 
224
217
  fragment ContentDetails on MovieOrShowOrSeasonOrEpisodeContent {
225
- title
226
- originalReleaseYear
227
- originalReleaseDate
228
- runtime
229
- shortDescription
230
- ...FullContentDetails
231
- ...on MovieOrShowContent {
232
- ageCertification
233
- }
234
- ...on SeasonContent {
235
- seasonNumber
236
- }
237
- ...on EpisodeContent {
238
- seasonNumber
239
- episodeNumber
240
- }
218
+ title
219
+ originalReleaseYear
220
+ originalReleaseDate
221
+ runtime
222
+ shortDescription
223
+ ...FullContentDetails
224
+ ... on MovieOrShowContent {
225
+ ageCertification
226
+ }
227
+ ... on SeasonContent {
228
+ seasonNumber
229
+ }
230
+ ... on EpisodeContent {
231
+ seasonNumber
232
+ episodeNumber
233
+ }
241
234
  }
242
235
 
243
236
  fragment FullContentDetails on MovieOrShowOrSeasonContent {
244
- fullPath
245
- genres {
246
- shortName
247
- __typename
248
- }
249
- externalIds {
250
- imdbId
251
- tmdbId
252
- __typename
253
- }
254
- posterUrl(profile: $profile, format: $formatPoster)
255
- backdrops(profile: $backdropProfile, format: $formatPoster) {
256
- backdropUrl
257
- __typename
258
- }
259
- scoring {
260
- imdbScore
261
- imdbVotes
262
- tmdbPopularity
263
- tmdbScore
264
- tomatoMeter
265
- certifiedFresh
266
- jwRating
267
- __typename
268
- }
269
- interactions {
270
- likelistAdditions
271
- dislikelistAdditions
272
- __typename
273
- }
237
+ fullPath
238
+ genres {
239
+ shortName
240
+ __typename
241
+ }
242
+ externalIds {
243
+ imdbId
244
+ tmdbId
245
+ __typename
246
+ }
247
+ posterUrl(profile: $profile, format: $formatPoster)
248
+ backdrops(profile: $backdropProfile, format: $formatPoster) {
249
+ backdropUrl
250
+ __typename
251
+ }
252
+ scoring {
253
+ imdbScore
254
+ imdbVotes
255
+ tmdbPopularity
256
+ tmdbScore
257
+ tomatoMeter
258
+ certifiedFresh
259
+ jwRating
260
+ __typename
261
+ }
262
+ interactions {
263
+ likelistAdditions
264
+ dislikelistAdditions
265
+ __typename
266
+ }
274
267
  }
275
268
  """
276
269
 
277
270
  _GRAPHQL_OFFER_FRAGMENT = """
278
271
  fragment TitleOffer on Offer {
279
- id
280
- monetizationType
281
- presentationType
282
- retailPrice(language: $language)
283
- retailPriceValue
284
- currency
285
- lastChangeRetailPriceValue
286
- type
287
- package {
288
- ... PackageDetails
289
- }
290
- standardWebURL
291
- elementCount
292
- availableTo
293
- deeplinkRoku: deeplinkURL(platform: ROKU_OS)
294
- subtitleLanguages
295
- videoTechnology
296
- audioTechnology
297
- audioLanguages
298
- __typename
272
+ id
273
+ monetizationType
274
+ presentationType
275
+ retailPrice(language: $language)
276
+ retailPriceValue
277
+ currency
278
+ lastChangeRetailPriceValue
279
+ type
280
+ package {
281
+ ...PackageDetails
282
+ }
283
+ standardWebURL
284
+ elementCount
285
+ availableTo
286
+ deeplinkRoku: deeplinkURL(platform: ROKU_OS)
287
+ subtitleLanguages
288
+ videoTechnology
289
+ audioTechnology
290
+ audioLanguages
291
+ __typename
299
292
  }
300
293
  """
301
294
 
302
295
  _GRAPHQL_PACKAGE_FRAGMENT = """
303
296
  fragment PackageDetails on Package {
304
- id
305
- packageId
306
- clearName
307
- technicalName
308
- shortName
309
- slug
310
- icon(profile: S100, format: $formatOfferIcon)
311
- __typename
297
+ id
298
+ packageId
299
+ clearName
300
+ technicalName
301
+ shortName
302
+ slug
303
+ monetizationTypes
304
+ icon(profile: S100, format: $formatOfferIcon)
305
+ __typename
312
306
  }
313
307
  """
314
308
 
315
309
  _GRAPHQL_COUNTRY_OFFERS_ENTRY = """
316
- {country_code}: offers(country: {country_code}, platform: WEB, filter: $filter) {{
317
- ...TitleOffer
318
- __typename
319
- }}
310
+ {country_code}: offers(country: {country_code}, platform: WEB, filter: $filter) {{
311
+ ...TitleOffer
312
+ __typename
313
+ }}
320
314
  """
321
315
 
322
-
323
- def graphql_search_query() -> str:
324
- """
325
- Prepare GraphQL query used for searching for entries.
326
-
327
- The full query is:
328
-
329
- - `GetSearchTitles` query
330
- - `TitleDetails` fragment
331
- - `TitleOffer` fragment
332
- - `PackageDetails` fragment
333
-
334
- Returns:
335
- (str): Full GraphQL `GetSearchTitles` query.
336
-
337
- """
338
- return (
339
- _GRAPHQL_SEARCH_QUERY
340
- + _GRAPHQL_DETAILS_FRAGMENT
341
- + _GRAPHQL_OFFER_FRAGMENT
342
- + _GRAPHQL_PACKAGE_FRAGMENT
343
- )
344
-
345
-
346
- def graphql_popular_query() -> str:
347
- """
348
- Prepare GraphQL query used for looking up currently popular titles.
349
-
350
- The full query is:
351
-
352
- - `GetPopularTitles` query
353
- - `TitleDetails` fragment
354
- - `TitleOffer` fragment
355
- - `PackageDetails` fragment
356
-
357
- Returns:
358
- (str): Full GraphQL `GetPopularTitles` query.
359
-
360
- """
361
- return (
362
- _GRAPHQL_POPULAR_QUERY
363
- + _GRAPHQL_DETAILS_FRAGMENT
364
- + _GRAPHQL_OFFER_FRAGMENT
365
- + _GRAPHQL_PACKAGE_FRAGMENT
366
- )
367
-
368
-
369
- def graphql_providers_query() -> str:
370
- """
371
- Prepare GraphQL query used for looking up all providers for a given country.
372
-
373
- The full query is:
374
-
375
- - `GetProviders` query
376
- - `PackageDetails` fragment
377
-
378
- Returns:
379
- (str): Full GraphQL `GetProviders` query.
380
-
381
- """
382
- return _GRAPHQL_PROVIDERS_QUERY + _GRAPHQL_PACKAGE_FRAGMENT
383
-
384
-
385
- def graphql_details_query() -> str:
386
- """
387
- Prepare GraphQL query used for getting details regarding a single entry.
388
-
389
- The full query is:
390
-
391
- - `GetTitleNode` query
392
- - `TitleDetails` fragment
393
- - `TitleOffer` fragment
394
- - `PackageDetails` fragment
395
-
396
- It is meant for movies and shows, but can be used for seasons and episodes as well,
397
- it just won't return full season/episodes list.
398
-
399
- Returns:
400
- (str): Full GraphQL `GetTitleNode` query.
401
-
402
- """
403
- return (
404
- _GRAPHQL_DETAILS_QUERY
405
- + _GRAPHQL_DETAILS_FRAGMENT
406
- + _GRAPHQL_OFFER_FRAGMENT
407
- + _GRAPHQL_PACKAGE_FRAGMENT
408
- )
409
-
410
-
411
- def graphql_seasons_query() -> str:
412
- """
413
- Prepare GraphQL query used for getting a list of seasons for a single show.
414
-
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
-
422
- It will only return data for shows with a list of all available seasons, ascending.
423
- `TitleDetails` query itself matches [`graphql_details_query`]
424
- [simplejustwatchapi.graphql.graphql_details_query], its conditions will return all
425
- relevant data for seasons.
426
-
427
- Returns:
428
- (str): GraphQL `GetTitleNode` query with a list of seasons.
429
-
430
- """
431
- return (
432
- _GRAPHQL_SEASONS_QUERY
433
- + _GRAPHQL_DETAILS_FRAGMENT
434
- + _GRAPHQL_OFFER_FRAGMENT
435
- + _GRAPHQL_PACKAGE_FRAGMENT
436
- )
437
-
438
-
439
- def graphql_episodes_query() -> str:
440
- """
441
- Prepare GraphQL query used for getting a list of episodes for a single show season.
442
-
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
453
- relevant data for episodes.
454
-
455
- Returns:
456
- (str): GraphQL `GetTitleNode` query with a list of episods.
457
-
458
- """
459
- return (
460
- _GRAPHQL_EPISODES_QUERY
461
- + _GRAPHQL_DETAILS_FRAGMENT
462
- + _GRAPHQL_OFFER_FRAGMENT
463
- + _GRAPHQL_PACKAGE_FRAGMENT
464
- )
316
+ GRAPHQL_SEARCH_QUERY = (
317
+ _GRAPHQL_SEARCH_QUERY
318
+ + _GRAPHQL_DETAILS_FRAGMENT
319
+ + _GRAPHQL_OFFER_FRAGMENT
320
+ + _GRAPHQL_PACKAGE_FRAGMENT
321
+ )
322
+
323
+ GRAPHQL_POPULAR_QUERY = (
324
+ _GRAPHQL_POPULAR_QUERY
325
+ + _GRAPHQL_DETAILS_FRAGMENT
326
+ + _GRAPHQL_OFFER_FRAGMENT
327
+ + _GRAPHQL_PACKAGE_FRAGMENT
328
+ )
329
+
330
+ GRAPHQL_PROVIDERS_QUERY = _GRAPHQL_PROVIDERS_QUERY + _GRAPHQL_PACKAGE_FRAGMENT
331
+
332
+ GRAPHQL_DETAILS_QUERY = (
333
+ _GRAPHQL_DETAILS_QUERY
334
+ + _GRAPHQL_DETAILS_FRAGMENT
335
+ + _GRAPHQL_OFFER_FRAGMENT
336
+ + _GRAPHQL_PACKAGE_FRAGMENT
337
+ )
338
+
339
+ GRAPHQL_SEASONS_QUERY = (
340
+ _GRAPHQL_SEASONS_QUERY
341
+ + _GRAPHQL_DETAILS_FRAGMENT
342
+ + _GRAPHQL_OFFER_FRAGMENT
343
+ + _GRAPHQL_PACKAGE_FRAGMENT
344
+ )
345
+
346
+ GRAPHQL_EPISODES_QUERY = (
347
+ _GRAPHQL_EPISODES_QUERY
348
+ + _GRAPHQL_DETAILS_FRAGMENT
349
+ + _GRAPHQL_OFFER_FRAGMENT
350
+ + _GRAPHQL_PACKAGE_FRAGMENT
351
+ )
465
352
 
466
353
 
467
354
  def graphql_offers_for_countries_query(countries: set[str]) -> str:
@@ -41,13 +41,13 @@ Each function can raise two exceptions:
41
41
  | Exception | Cause |
42
42
  |-----------|-------|
43
43
  | [`JustWatchHttpError`][simplejustwatchapi.exceptions.JustWatchHttpError] | \
44
- JustWatch API responded with non-`2xx` code. |
44
+ HTTP error occurred, e.g., JustWatch API responded with non-`2xx` status code. |
45
45
  | [`JustWatchApiError`][simplejustwatchapi.exceptions.JustWatchApiError] | \
46
- JSON response from JustWatch API contains errors (e.g., due to invalid language or \
47
- country code). If this exception is raised, then API responded with `2xx` code. |
46
+ JSON response from JustWatch API contains errors, e.g., due to invalid language or \
47
+ country code. |
48
48
  """
49
49
 
50
- from httpx import post
50
+ from httpx import HTTPError, HTTPStatusError, post
51
51
 
52
52
  from simplejustwatchapi.exceptions import JustWatchHttpError
53
53
  from simplejustwatchapi.query import (
@@ -154,7 +154,8 @@ def search(
154
154
  Raises:
155
155
  exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
156
156
  due to invalid language or country code.
157
- exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
157
+ exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
158
+ responded with non-`2xx` status code.
158
159
 
159
160
  """
160
161
  request = prepare_search_request(
@@ -238,7 +239,8 @@ def popular(
238
239
  Raises:
239
240
  exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
240
241
  due to invalid language or country code.
241
- exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
242
+ exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
243
+ responded with non-`2xx` status code.
242
244
 
243
245
  """
244
246
  request = prepare_popular_request(
@@ -307,7 +309,8 @@ def details(
307
309
  Raises:
308
310
  exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
309
311
  due to invalid language or country code.
310
- exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
312
+ exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
313
+ responded with non-`2xx` status code.
311
314
 
312
315
  """
313
316
  request = prepare_details_request(node_id, country, language, best_only)
@@ -355,7 +358,8 @@ def seasons(
355
358
  Raises:
356
359
  exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
357
360
  due to invalid language or country code.
358
- exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
361
+ exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
362
+ responded with non-`2xx` status code.
359
363
 
360
364
  """
361
365
  request = prepare_seasons_request(show_id, country, language, best_only)
@@ -404,7 +408,8 @@ def episodes(
404
408
  Raises:
405
409
  exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
406
410
  due to invalid language or country code.
407
- exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
411
+ exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
412
+ responded with non-`2xx` status code.
408
413
 
409
414
  """
410
415
  request = prepare_episodes_request(season_id, country, language, best_only)
@@ -472,7 +477,8 @@ def offers_for_countries(
472
477
  Raises:
473
478
  exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
474
479
  due to invalid language or country code.
475
- exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
480
+ exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
481
+ responded with non-`2xx` status code.
476
482
 
477
483
  """
478
484
  if not countries:
@@ -506,7 +512,8 @@ def providers(country: str = "US") -> list[OfferPackage]:
506
512
  Raises:
507
513
  exceptions.JustWatchApiError: JSON response from API has internal errors, e.g.,
508
514
  due to invalid language or country code.
509
- exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
515
+ exceptions.JustWatchHttpError: HTTP error occurred, e.g., JustWatch API
516
+ responded with non-`2xx` status code.
510
517
 
511
518
  """
512
519
  request = prepare_providers_request(country)
@@ -525,10 +532,14 @@ def _post_to_jw_graphql_api(request_json: dict) -> dict:
525
532
  (dict): JSON response from the API.
526
533
 
527
534
  Raises:
528
- exceptions.JustWatchHttpError: JustWatch API didn't respond with `2xx` code.
535
+ exceptions.JustWatchHttpError: HTTP-related error occurred.
529
536
 
530
537
  """
531
- response = post(_GRAPHQL_API_URL, json=request_json)
532
- if not response.is_success:
533
- raise JustWatchHttpError(response.status_code, response.text)
534
- return response.json()
538
+ try:
539
+ response = post(_GRAPHQL_API_URL, json=request_json)
540
+ response.raise_for_status()
541
+ return response.json()
542
+ except HTTPStatusError as e:
543
+ raise JustWatchHttpError(str(e), e.response.text) from e
544
+ except HTTPError as e:
545
+ raise JustWatchHttpError(str(e)) from e
@@ -4,7 +4,7 @@ Module responsible for creating requests to and parsing responses from JustWatch
4
4
  Functions are prepared in pairs - prepare request and parse response for specific
5
5
  operation. "Request" functions do no verification of input data; "parse" functions check
6
6
  if returned JSON/`dict` contain `error` key. In such case a [`JustWatchApiError`]
7
- [simplejustwatchapi.erxceptions.JustWatchApiError] is raised.
7
+ [simplejustwatchapi.exceptions.JustWatchApiError] is raised.
8
8
 
9
9
  All "parse" functions convert JSON returned by API into request-specific Python
10
10
  [`NamedTuple`][typing.NamedTuple].
@@ -14,13 +14,13 @@ from typing import Any
14
14
 
15
15
  from simplejustwatchapi.exceptions import JustWatchApiError, JustWatchError
16
16
  from simplejustwatchapi.graphql import (
17
- graphql_details_query,
18
- graphql_episodes_query,
17
+ GRAPHQL_DETAILS_QUERY,
18
+ GRAPHQL_EPISODES_QUERY,
19
+ GRAPHQL_POPULAR_QUERY,
20
+ GRAPHQL_PROVIDERS_QUERY,
21
+ GRAPHQL_SEARCH_QUERY,
22
+ GRAPHQL_SEASONS_QUERY,
19
23
  graphql_offers_for_countries_query,
20
- graphql_popular_query,
21
- graphql_providers_query,
22
- graphql_search_query,
23
- graphql_seasons_query,
24
24
  )
25
25
  from simplejustwatchapi.tuples import (
26
26
  Episode,
@@ -84,7 +84,7 @@ def prepare_search_request(
84
84
  **_locale_variables(country, language),
85
85
  "offset": offset or None,
86
86
  },
87
- "query": graphql_search_query(),
87
+ "query": GRAPHQL_SEARCH_QUERY,
88
88
  }
89
89
 
90
90
 
@@ -157,7 +157,7 @@ def prepare_popular_request(
157
157
  **_locale_variables(country, language),
158
158
  "offset": offset or None,
159
159
  },
160
- "query": graphql_popular_query(),
160
+ "query": GRAPHQL_POPULAR_QUERY,
161
161
  }
162
162
 
163
163
 
@@ -220,7 +220,7 @@ def prepare_details_request(
220
220
  **_common_variables(best_only),
221
221
  **_locale_variables(country, language),
222
222
  },
223
- "query": graphql_details_query(),
223
+ "query": GRAPHQL_DETAILS_QUERY,
224
224
  }
225
225
 
226
226
 
@@ -279,7 +279,7 @@ def prepare_seasons_request(
279
279
  **_common_variables(best_only),
280
280
  **_locale_variables(country, language),
281
281
  },
282
- "query": graphql_seasons_query(),
282
+ "query": GRAPHQL_SEASONS_QUERY,
283
283
  }
284
284
 
285
285
 
@@ -338,7 +338,7 @@ def prepare_episodes_request(
338
338
  **_common_variables(best_only),
339
339
  **_locale_variables(country, language),
340
340
  },
341
- "query": graphql_episodes_query(),
341
+ "query": GRAPHQL_EPISODES_QUERY,
342
342
  }
343
343
 
344
344
 
@@ -469,7 +469,7 @@ def prepare_providers_request(country: str) -> dict[str, Any]:
469
469
  "country": country.upper(),
470
470
  "formatOfferIcon": "PNG",
471
471
  },
472
- "query": graphql_providers_query(),
472
+ "query": GRAPHQL_PROVIDERS_QUERY,
473
473
  }
474
474
 
475
475
 
@@ -711,5 +711,14 @@ def _parse_package(json: Any) -> OfferPackage:
711
711
  name = json.get("clearName")
712
712
  technical_name = json.get("technicalName")
713
713
  short_name = json.get("shortName")
714
+ monetization_types = json.get("monetizationTypes", [])
714
715
  icon = _IMAGES_URL + icon if (icon := json.get("icon")) else ""
715
- return OfferPackage(platform_id, package_id, name, technical_name, short_name, icon)
716
+ return OfferPackage(
717
+ platform_id,
718
+ package_id,
719
+ name,
720
+ technical_name,
721
+ short_name,
722
+ monetization_types,
723
+ icon,
724
+ )
@@ -24,12 +24,14 @@ class OfferPackage(NamedTuple):
24
24
  function to return data about all available providers.
25
25
 
26
26
  Attributes:
27
- id (str): ID of the provider/plaform for this offer.
27
+ id (str): ID of the provider/platform for this offer.
28
28
  package_id (int): Package ID. I'm not sure how it's different from regular `id`.
29
29
  name (str): Name of the platform in format suited to display for users.
30
30
  technical_name (str): Technical name of the platform,
31
31
  usually all lowercase with no whitespaces.
32
32
  short_name (str): 3-letter provider name.
33
+ monetization_types (list[str]): List of monetization types available for this
34
+ provider (e.g., `ADS`, `FLATRATE` (streaming), `RENT`).
33
35
  icon (str): Platform icon URL.
34
36
 
35
37
  """
@@ -39,6 +41,7 @@ class OfferPackage(NamedTuple):
39
41
  name: str
40
42
  technical_name: str
41
43
  short_name: str
44
+ monetization_types: list[str]
42
45
  icon: str
43
46
 
44
47
 
@@ -1,10 +0,0 @@
1
- simplejustwatchapi/__init__.py,sha256=Tqj7re00Qk8_OjZPM-jdUbKQPEido1rAUORWDOH5YSw,816
2
- simplejustwatchapi/exceptions.py,sha256=zvfDHe3lAG7XMFygFs8R0j4In1eK6xOtOtvDksIkhGs,2167
3
- simplejustwatchapi/graphql.py,sha256=Nf5K5Xw8OM0JzlYAGk3JeXedDhiqeSAvrd-MUbZ-WpI,10744
4
- simplejustwatchapi/justwatch.py,sha256=ZvNrdGRkUqLQF48fDEfGYIZom2W3X8pVuRcoWI6xxPk,21746
5
- simplejustwatchapi/query.py,sha256=C6qcz7--FYyrzwKZL_fEofGs163QZXr-zCD17f7pOEY,23048
6
- simplejustwatchapi/tuples.py,sha256=H4s_M2HKt-B9Mt5iNUDhJjZlk5JN7FGWuIa9xA1cueE,10229
7
- simple_justwatch_python_api-1.0.3.dist-info/licenses/LICENSE,sha256=4RylkfuLsHZh0xqHJeDHm0QoxPSIbfeHKwHiVoNsyxE,1073
8
- simple_justwatch_python_api-1.0.3.dist-info/WHEEL,sha256=s_zqWxHFEH8b58BCtf46hFCqPaISurdB9R1XJ8za6XI,80
9
- simple_justwatch_python_api-1.0.3.dist-info/METADATA,sha256=T5P-Lr3rRr4lvQgneHmZz8zqtxp_NdBj_J9TrQrccSY,4479
10
- simple_justwatch_python_api-1.0.3.dist-info/RECORD,,