Source code for atmdb.models

"""Models representing TMDb resources."""
from datetime import date, datetime
import logging
from textwrap import dedent

from dateutil.relativedelta import relativedelta

logger = logging.getLogger(__name__)


[docs]class BaseModel: """Base TMDb model functionality. Arguments: id_ (:py:class:`int`): The TMDb ID of the object. image_path (:py:class:`str`): The short path to the image. Attributes: image_url (:py:class:`str`): The fully-qualified image URL. """ CONTAINS = None """:py:class:`dict`: Rules for what the model contains.""" IMAGE_TYPE = None """:py:class:`str`: The type of image to use.""" JSON_MAPPING = dict(id_='id') """:py:class:`dict`: The mapping between JSON keys and attributes.""" image_config = None """:py:class:`dict`: The API image configuration.""" def __init__(self, *, id_, image_path=None, **_): self.id_ = id_ self.image_path = image_path def __contains__(self, item): if self.CONTAINS is None: return False attr = self.CONTAINS['attr'] subclasses = {obj.__name__: obj for obj in BaseModel.__subclasses__()} # pylint: disable=no-member cls = subclasses[self.CONTAINS['type']] return isinstance(item, cls) and item in getattr(self, attr) def __eq__(self, other): return isinstance(other, type(self)) and self.id_ == other.id_ def __hash__(self): return hash(self.id_) def __repr__(self): return '{}({})'.format( self.__class__.__name__, ', '.join(['{}={!r}'.format(attr, getattr(self, attr)) for attr in self.JSON_MAPPING]), ) @property def image_url(self): return self._create_image_url(self.image_path, self.IMAGE_TYPE, 200) def _create_image_url(self, file_path, type_, target_size): """The the closest available size for specified image type. Arguments: file_path (:py:class:`str`): The image file path. type_ (:py:class:`str`): The type of image to create a URL for, (``'poster'`` or ``'profile'``). target_size (:py:class:`int`): The size of image to aim for (used as either width or height). """ if self.image_config is None: logger.warning('no image configuration available') return return ''.join([ self.image_config['secure_base_url'], self._image_size(self.image_config, type_, target_size), file_path, ]) @classmethod
[docs] def from_json(cls, json, image_config=None): """Create a model instance Arguments: json (:py:class:`dict`): The parsed JSON data. image_config (:py:class:`dict`): The API image configuration data. Returns: :py:class:`BaseModel`: The model instance. """ cls.image_config = image_config return cls(**{ attr: json.get(attr if key is None else key) for attr, key in cls.JSON_MAPPING.items() })
@staticmethod def _image_size(image_config, type_, target_size): """Find the closest available size for specified image type. Arguments: image_config (:py:class:`dict`): The image config data. type_ (:py:class:`str`): The type of image to create a URL for, (``'poster'`` or ``'profile'``). target_size (:py:class:`int`): The size of image to aim for (used as either width or height). """ return min( image_config['{}_sizes'.format(type_)], key=lambda size: (abs(target_size - int(size[1:])) if size.startswith('w') or size.startswith('h') else 999), )
[docs]class Movie(BaseModel): """Represents a movie. Arguments: title (:py:class:`str`): The title of the movie. cast (:py:class:`set`, optional): The movie's cast. synopsis (:py:class:`str`, optional): A synopsis of the movie. release_date (:py:class:`datetime.date`): The date of release. Attributes: release_year (:py:class:`int`) The year of release. url (:py:class:`str`): The URL to the movies's TMDb page. """ CONTAINS = dict(attr='cast', image_path='poster_path', type='Person') IMAGE_TYPE = 'poster' JSON_MAPPING = dict( cast=None, image_path='{}_path'.format(IMAGE_TYPE), release_date=None, synopsis='overview', title='original_title', **BaseModel.JSON_MAPPING, ) def __init__(self, *, title, cast=None, synopsis=None, release_date=None, **kwargs): super().__init__(**kwargs) self.cast = cast self.synopsis = synopsis self.title = title self.release_date = release_date def __str__(self): if self.synopsis is None: return "{0.title} [{0.url}]".format(self) return dedent(""" *{0.title}* {0.synopsis} For more information see: {0.url} """).strip().format(self) @property def url(self): return 'https://www.themoviedb.org/movie/{}'.format(self.id_) @property def release_year(self): return None if self.release_date is None else self.release_date.year @classmethod def from_json(cls, json, image_config=None): json['cast'] = { Person.from_json(person, image_config) for person in json.get('credits', {}).get('cast', []) } or None release = json.get('release_date') json['release_date'] = (None if not release else datetime.strptime(release, '%Y-%m-%d').date()) return super().from_json(json, image_config)
[docs]class Person(BaseModel): """Represents a person. Arguments: name (:py:class:`str`): The person's name. movie_credits (:py:class:`set`, optional): The person's movie credits. biography (:py:class:`str`, optional): A synopsis of the movie. known_for (:py:class:`list`, optional): A list of the three movies the person is best known for. birthday (:py:class:`datetime.date`, optional): The person's date of birth. birthday (:py:class:`datetime.date`, optional): The person's date of death. Attributes: age (:py:class:`int`): The person's age, in years (if they have died, their age at the time of their death). alive (:py:class:`bool`): Whether the person is currently alive. url (:py:class:`str`): The URL to the person's TMDb profile. """ CONTAINS = dict(attr='movie_credits', type='Movie') IMAGE_TYPE = 'profile' JSON_MAPPING = dict( biography=None, birthday=None, deathday=None, image_path='{}_path'.format(IMAGE_TYPE), movie_credits=None, known_for=None, name=None, **BaseModel.JSON_MAPPING, ) def __init__(self, name, biography=None, movie_credits=None, known_for=None, birthday=None, deathday=None, **kwargs): super().__init__(**kwargs) self.biography = biography self.movie_credits = movie_credits self.name = name self.known_for = known_for self.birthday = birthday self.deathday = deathday def __str__(self): if self.biography is None: return "{0.name} [{0.url}]".format(self) return dedent(""" *{0.name}* {0.biography} For more information see: {0.url} """).strip().format(self) @property def age(self): if self.birthday is None: return if self.deathday is None: return relativedelta(date.today(), self.birthday).years return relativedelta(self.deathday, self.birthday).years @property def alive(self): return self.deathday is None @property def url(self): return 'https://www.themoviedb.org/person/{}'.format(self.id_) @classmethod def from_json(cls, json, image_config=None): json['movie_credits'] = { Movie.from_json(movie, image_config) for movie in json.get('movie_credits', {}).get('cast', []) } or None json['known_for'] = { Movie.from_json(movie, image_config) for movie in json.get('known_for', []) if movie.get('media_type') == 'movie' } or None json['birthday'] = cls.extract_date(json.get('birthday')) json['deathday'] = cls.extract_date(json.get('deathday')) return super().from_json(json, image_config) @staticmethod def extract_date(date_str): if not date_str: return return datetime.strptime(date_str, '%Y-%m-%d').date()