# coding: utf-8
"""Utility module to connect and retrieve information from TMDb."""
import os
import typing
import logging
import json
import datetime
import urllib.parse
import requests
[docs]
class MovieSearch(typing.NamedTuple):
"""Basic information on a movie."""
title: str
original_title: str
release_date: typing.Union[datetime.date, None]
overview: typing.Optional[str]
poster_path: typing.Optional[str]
id: int
[docs]
class Tmdb:
"""Main class to request data on TMDb API v3."""
_log: logging.Logger = logging.getLogger(__file__)
__api_version: int = 3
__base_url: str = 'https://api.themoviedb.org/' + str(__api_version)
def __build_request_url(self,
route: str,
**kwargs: typing.Optional[str]) -> str:
kwargs['api_key'] = self.__apikey
kwargs['language'] = self.__language
api_url = (
self.__base_url + route + '?' + urllib.parse.urlencode(kwargs)
)
self._log.debug(api_url)
return api_url
def __init__(self,
apikey: str,
lang: typing.Optional[str] = None):
"""
Build a new TMDb client instance.
:param apikey: TMDb API key. A new API key could be obtains through
https://www.themoviedb.org/settings/api.
:param lang: Optional request language. If not set, class will
fallback to language as described by *LANG* environment variable.
"""
self.__apikey = apikey
if lang is not None:
self.__language = lang
else:
self.__language = os.environ.get('LANG', default='en_US.utf-8')
self.__language = self.__language.split('.')[0].replace('_', '-')
self.__configuration = requests.get(
self.__build_request_url('/configuration'),
headers={
'Content-Type': 'Application/json; charset=utf-8'
}
).json()
self._log.debug(json.dumps(self.__configuration, indent=4))
def __repr__(self) -> str:
"""Get a representation of instance."""
return '{}({!r}, lang={!r})'.format(
type(self).__name__,
self.__apikey,
self.__language
)
[docs]
def search_movie(
self,
title: str,
year: typing.Union[int, None] = None,
page: int = 1
) -> typing.Generator[MovieSearch, None, None]:
"""
Search a movie, given its title and optionally its release year.
:param title: Movie title, could be original title or localized title.
Could also be a partial title.
:param year: Movie release date.
:param page: Requested page.
:return: List generator with all matched movie. Each result consists in
a named tuple with the following keys:
- *title*: localized movie title
- *original_title*: original movie title
- *release_date*: movie release date
- *overview*: short movie summary
- *poster_path*: URL to retrieve small poster
- *id*: TMDb movie ID, useful to retrieve full movie details
"""
results = requests.get(
self.__build_request_url('/search/movie', query=title, page=page),
headers={
'Content-Type': 'Application/json; charset=utf-8'
}
).json()
movie: typing.Mapping[str, str]
for movie in results['results']:
self._log.debug(json.dumps(movie, indent=4))
# Compute release date and filter result if needed
release_date: typing.Union[datetime.date, None]
try:
release_date = datetime.date.fromisoformat(
movie.get('release_date', '0000-00-00')
)
except ValueError:
self._log.warning('No release_date given, ignore')
release_date = None
if (year is not None and (
release_date is None or
year != release_date.year
)):
continue
# Compute title
out_title: str
out_title = movie.get('title', '')
# Compute original title
original_title: str
original_title = movie.get('original_title', title)
# Compute poster path
self._update_path(movie, 'poster_path', 'poster_sizes')
poster: typing.Optional[str]
poster = movie.get('poster_path', None)
# Compute overview
overview: typing.Optional[str]
overview = movie.get('overview', None)
# Compute movie id
movie_id: int
movie_id = int(movie.get('id', 0))
yield MovieSearch(
out_title,
original_title,
release_date,
overview,
poster,
movie_id
)
if page < results['total_pages']:
yield from self.search_movie(title, year, page + 1)
def _update_path(self, data, key, base):
path: typing.Optional[str]
path = data.get(key, None)
if path is not None:
data[key] = (
self.__configuration['images']['secure_base_url'] +
self.__configuration['images'][base][-1] +
path
)
[docs]
def get_movie(self, movie_id: int):
"""Get all information about a given movie."""
result = requests.get(
self.__build_request_url('/movie/{}'.format(movie_id)),
headers={
'Content-Type': 'Application/json; charset=utf-8'
}
).json()
self._update_path(result, 'poster_path', 'poster_sizes')
self._log.debug(json.dumps(result, indent=4))
return result
[docs]
def get_credits(self, movie_id: int):
"""Get cast and crew for a movie."""
result = requests.get(
self.__build_request_url('/movie/{}/credits'.format(movie_id)),
headers={
'Content-Type': 'Application/json; charset=utf-8'
}
).json()
for cast in result['cast']:
self._update_path(cast, 'profile_path', 'profile_sizes')
self._log.debug(json.dumps(result, indent=4))
return result