imdbio 0.0.1__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.
imdbio/__init__.py ADDED
@@ -0,0 +1,57 @@
1
+ import logging
2
+
3
+ from .services import (
4
+ get_movie,
5
+ search_title,
6
+ get_name,
7
+ get_episodes,
8
+ get_all_episodes,
9
+ get_season_episodes,
10
+ get_akas,
11
+ get_reviews,
12
+ get_trivia,
13
+ get_parental_guide,
14
+ get_filmography,
15
+ get_all_interests,
16
+ get_media_gallery,
17
+ TitleType,
18
+ )
19
+ from .models import (
20
+ TitleMediaGallery,
21
+ TitleMediaItem,
22
+ )
23
+ from .exceptions import (
24
+ ImdbioError,
25
+ HTTPError,
26
+ WAFError,
27
+ GraphQLError,
28
+ ParseError,
29
+ )
30
+
31
+ __all__ = [
32
+ "get_movie",
33
+ "search_title",
34
+ "get_name",
35
+ "get_episodes",
36
+ "get_all_episodes",
37
+ "get_season_episodes",
38
+ "get_akas",
39
+ "get_reviews",
40
+ "get_trivia",
41
+ "get_parental_guide",
42
+ "get_filmography",
43
+ "get_all_interests",
44
+ "get_media_gallery",
45
+ "TitleMediaGallery",
46
+ "TitleMediaItem",
47
+ "TitleType",
48
+ # exceptions
49
+ "ImdbioError",
50
+ "HTTPError",
51
+ "WAFError",
52
+ "GraphQLError",
53
+ "ParseError",
54
+ ]
55
+
56
+ # setup library logging
57
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
imdbio/exceptions.py ADDED
@@ -0,0 +1,129 @@
1
+ """
2
+ imdbio exception hierarchy
3
+ ============================
4
+
5
+ ImdbioError
6
+ ├── HTTPError — any non-200 HTTP response (status_code, url, response_text)
7
+ │ └── WAFError — HTTP 202 from AWS WAF enforcement
8
+ ├── GraphQLError — non-200 or {"errors": …} from the GraphQL endpoint
9
+ └── ParseError — __NEXT_DATA__ script not found in HTML response
10
+ """
11
+
12
+ from typing import Optional, List, Dict, Any
13
+
14
+
15
+ class ImdbioError(Exception):
16
+ """Base class for all imdbio exceptions."""
17
+
18
+
19
+ class HTTPError(ImdbioError):
20
+ """Raised when the IMDb HTML endpoint returns a non-200 status code.
21
+
22
+ Attributes
23
+ ----------
24
+ status_code : int
25
+ The HTTP status code returned by the server.
26
+ url : str
27
+ The full URL that was requested.
28
+ response_text : str
29
+ The first 500 characters of the response body (may be empty).
30
+ """
31
+
32
+ def __init__(
33
+ self, message: str, status_code: int, url: str, response_text: str = ""
34
+ ):
35
+ super().__init__(message)
36
+ self.status_code: int = status_code
37
+ self.url: str = url
38
+ self.response_text: str = response_text
39
+
40
+ def __repr__(self) -> str:
41
+ return (
42
+ f"{self.__class__.__name__}("
43
+ f"status_code={self.status_code!r}, "
44
+ f"url={self.url!r}, "
45
+ f"message={str(self)!r})"
46
+ )
47
+
48
+
49
+ class WAFError(HTTPError):
50
+ """Raised when AWS WAF returns HTTP 202, blocking the request.
51
+
52
+ This is a subclass of :class:`HTTPError` so callers that catch
53
+ ``HTTPError`` will also catch ``WAFError``. Callers that want to handle
54
+ WAF enforcement specifically (e.g. to rotate proxies or back off) can
55
+ catch ``WAFError`` directly.
56
+
57
+ Attributes
58
+ ----------
59
+ status_code : int
60
+ Always ``202`` for WAF-blocked responses.
61
+ url : str
62
+ The full URL that was blocked.
63
+ response_text : str
64
+ The first 500 characters of the WAF challenge response body.
65
+ """
66
+
67
+
68
+ class GraphQLError(ImdbioError):
69
+ """Raised when the IMDb GraphQL API returns a non-200 status or
70
+ an ``{"errors": […]}`` payload.
71
+
72
+ Attributes
73
+ ----------
74
+ url : str
75
+ The GraphQL endpoint URL.
76
+ query_term : str
77
+ The search term / entity ID used in the query.
78
+ status_code : Optional[int]
79
+ HTTP status code when the failure is transport-level; ``None`` when
80
+ the response was 200 but contained GraphQL ``errors``.
81
+ errors : List[Dict[str, Any]]
82
+ Parsed GraphQL error objects from the response body (empty list for
83
+ transport-level failures).
84
+ response_text : str
85
+ First 500 characters of the raw response body.
86
+ """
87
+
88
+ def __init__(
89
+ self,
90
+ message: str,
91
+ url: str,
92
+ query_term: str,
93
+ status_code: Optional[int] = None,
94
+ errors: Optional[List[Dict[str, Any]]] = None,
95
+ response_text: str = "",
96
+ ):
97
+ super().__init__(message)
98
+ self.url: str = url
99
+ self.query_term: str = query_term
100
+ self.status_code: Optional[int] = status_code
101
+ self.errors: List[Dict[str, Any]] = errors or []
102
+ self.response_text: str = response_text
103
+
104
+ def __repr__(self) -> str:
105
+ return (
106
+ f"{self.__class__.__name__}("
107
+ f"status_code={self.status_code!r}, "
108
+ f"url={self.url!r}, "
109
+ f"query_term={self.query_term!r}, "
110
+ f"message={str(self)!r})"
111
+ )
112
+
113
+
114
+ class ParseError(ImdbioError):
115
+ """Raised when the expected ``__NEXT_DATA__`` JSON script tag is absent
116
+ from the IMDb HTML response.
117
+
118
+ Attributes
119
+ ----------
120
+ url : str
121
+ The URL whose response could not be parsed.
122
+ """
123
+
124
+ def __init__(self, message: str, url: str = ""):
125
+ super().__init__(message)
126
+ self.url: str = url
127
+
128
+ def __repr__(self) -> str:
129
+ return f"{self.__class__.__name__}(url={self.url!r}, message={str(self)!r})"
imdbio/locale.py ADDED
@@ -0,0 +1,68 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger(__name__)
4
+
5
+ SUPPORTED_LOCALES = ("en", "fr-ca", "fr", "hi", "de", "it", "es", "pt", "es-es")
6
+ LOCALE_TO_COUNTRY_CODE = {
7
+ "en": "EN",
8
+ "fr-ca": "FR",
9
+ "fr": "FR",
10
+ "hi": "IN",
11
+ "de": "DE",
12
+ "it": "IT",
13
+ "es": "ES",
14
+ "pt": "PT",
15
+ "es-es": "ES",
16
+ }
17
+ DEFAULT_LOCALE = "en"
18
+ _configured_locale = None
19
+
20
+
21
+ def set_locale(locale: str):
22
+ global _configured_locale
23
+ # accept only a single supported locale string
24
+ if not isinstance(locale, str):
25
+ logger.warning(
26
+ "Invalid locale type: %r. Locale must be a string. Falling back to default '%s'.",
27
+ locale,
28
+ DEFAULT_LOCALE,
29
+ )
30
+ _configured_locale = DEFAULT_LOCALE
31
+ return
32
+
33
+ l = locale.strip()
34
+ if l not in SUPPORTED_LOCALES:
35
+ logger.warning(
36
+ "Locale '%s' is not supported. Falling back to default '%s'.",
37
+ l,
38
+ DEFAULT_LOCALE,
39
+ )
40
+ _configured_locale = DEFAULT_LOCALE
41
+ return
42
+
43
+ _configured_locale = l
44
+
45
+
46
+ def _normalize_locale(lcl: str):
47
+ if lcl not in SUPPORTED_LOCALES:
48
+ logger.warning("Locale '%s' is not supported. Using '%s'", lcl, DEFAULT_LOCALE)
49
+ return DEFAULT_LOCALE
50
+ return lcl
51
+
52
+
53
+ def get_locale():
54
+ lcl = _configured_locale or DEFAULT_LOCALE
55
+ lcl = _normalize_locale(lcl)
56
+ return "" if lcl == DEFAULT_LOCALE else lcl
57
+
58
+
59
+ def _retrieve_url_lang(locale=None):
60
+ lcl = locale or _configured_locale or DEFAULT_LOCALE
61
+ lcl = _normalize_locale(lcl)
62
+ return "" if lcl == DEFAULT_LOCALE else lcl
63
+
64
+
65
+ def _get_country_code_from_lang_locale(locale=None):
66
+ lcl = locale or _configured_locale or DEFAULT_LOCALE
67
+ lcl = _normalize_locale(lcl)
68
+ return LOCALE_TO_COUNTRY_CODE.get(lcl, LOCALE_TO_COUNTRY_CODE[DEFAULT_LOCALE])