From 6b86097ce87ad384e32842ed178e5e194ccfdcec Mon Sep 17 00:00:00 2001 From: moogar0880 Date: Sat, 22 Aug 2015 23:23:25 -0400 Subject: [PATCH 1/4] Initial pass at fixing rate and history argument packing for #21 --- trakt/movies.py | 1 + trakt/sync.py | 30 ++++++++++++++++++++---------- trakt/tv.py | 2 ++ trakt/utils.py | 6 ++++++ 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/trakt/movies.py b/trakt/movies.py index 00e3aa74..a16ff215 100644 --- a/trakt/movies.py +++ b/trakt/movies.py @@ -79,6 +79,7 @@ class Movie(object): """A Class representing a Movie object""" def __init__(self, title, year=None, **kwargs): super(Movie, self).__init__() + self.media_type = 'movie' self.title = title self.year = int(year) if year is not None else year if self.year is not None: diff --git a/trakt/sync.py b/trakt/sync.py index b7c516d4..cd06febd 100644 --- a/trakt/sync.py +++ b/trakt/sync.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- """This module contains Trakt.tv sync endpoint support functions""" +from datetime import datetime from .core import get, post -from .utils import slugify, extract_ids +from .utils import slugify, extract_ids, timestamp __author__ = 'Jon Nappi' __all__ = ['Scrobbler', 'comment', 'rate', 'add_to_history', @@ -32,29 +33,38 @@ def comment(media, comment_body, spoiler=False, review=False): @post -def rate(media, rating): +def rate(media, rating, rated_at=None): """Add a rating from 1 to 10 to a :class:`Movie`, :class:`TVShow`, or :class:`TVEpisode` :param media: The media object to post a rating to :param rating: A rating from 1 to 10 for the media item + :param rated_at: A `datetime.datetime` object indicating the time at which + this rating was created """ - data = dict(rating=rating) - data.update(media.to_json()) - yield 'sync/ratings', data + if rated_at is None: + rated_at = datetime.now() + + data = dict(rating=rating, rated_at=timestamp(rated_at)) + data.update(media.ids) + yield 'sync/ratings', {media.media_type + 's': [data]} @post -def add_to_history(media, watched_at): +def add_to_history(media, watched_at=None): """Add a :class:`Movie`, :class:`TVShow`, or :class:`TVEpisode` to your watched history :param media: The media object to add to your history - :param watched_at: A trakt formatted timestampt that *media* was watched at + :param watched_at: A `datetime.datetime` object indicating the time at + which this media item was viewed """ - data = dict(watched_at=watched_at) - data.update(media.to_json()) - yield 'sync/history', data + if watched_at is None: + watched_at = datetime.now() + + data = dict(watched_at=timestamp(watched_at)) + data.update(media.ids) + yield 'sync/history', {media.media_type + 's': [data]} @post diff --git a/trakt/tv.py b/trakt/tv.py index c2d0cb12..0d3441c4 100644 --- a/trakt/tv.py +++ b/trakt/tv.py @@ -88,6 +88,7 @@ class TVShow(object): def __init__(self, title='', **kwargs): super(TVShow, self).__init__() + self.media_type = 'show' self.top_watchers = self.top_episodes = self.year = self.tvdb_id = None self.imdb_id = self.genres = self.certification = self.network = None self.trakt_id = self.tmdb_id = self._aliases = self._comments = None @@ -452,6 +453,7 @@ class TVEpisode(object): def __init__(self, show, season, number=-1, **kwargs): super(TVEpisode, self).__init__() + self.media_type = 'episode' self.show = show self.season = season self.number = number diff --git a/trakt/utils.py b/trakt/utils.py index 986f5a58..ecc02bf0 100644 --- a/trakt/utils.py +++ b/trakt/utils.py @@ -42,6 +42,12 @@ def now(): return '{}-{}-{}'.format(year, month, day) +def timestamp(date_object): + """Generate a trakt formatted timestamp from the given date object""" + fmt = '%Y-%m-%d:T%H:%M:%S.000Z' + return date_object.strftime(fmt) + + def extract_ids(id_dict): """Extract the inner `ids` dict out of trakt JSON responses and insert them into the containing `dict`. Then return the input `dict` From 5559068c6eef073cc6539ecfcb2d3503601e11d2 Mon Sep 17 00:00:00 2001 From: moogar0880 Date: Sun, 23 Aug 2015 19:29:25 -0400 Subject: [PATCH 2/4] Updated media types --- trakt/movies.py | 2 +- trakt/sync.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/trakt/movies.py b/trakt/movies.py index a16ff215..06664d75 100644 --- a/trakt/movies.py +++ b/trakt/movies.py @@ -79,7 +79,7 @@ class Movie(object): """A Class representing a Movie object""" def __init__(self, title, year=None, **kwargs): super(Movie, self).__init__() - self.media_type = 'movie' + self.media_type = 'movies' self.title = title self.year = int(year) if year is not None else year if self.year is not None: diff --git a/trakt/sync.py b/trakt/sync.py index cd06febd..dd451a0e 100644 --- a/trakt/sync.py +++ b/trakt/sync.py @@ -47,7 +47,7 @@ def rate(media, rating, rated_at=None): data = dict(rating=rating, rated_at=timestamp(rated_at)) data.update(media.ids) - yield 'sync/ratings', {media.media_type + 's': [data]} + yield 'sync/ratings', {media.media_type: [data]} @post @@ -64,7 +64,7 @@ def add_to_history(media, watched_at=None): data = dict(watched_at=timestamp(watched_at)) data.update(media.ids) - yield 'sync/history', {media.media_type + 's': [data]} + yield 'sync/history', {media.media_type: [data]} @post From a6e8a2d24f59a3f631c9dbabc74e7a9b295042a9 Mon Sep 17 00:00:00 2001 From: moogar0880 Date: Sun, 23 Aug 2015 19:33:45 -0400 Subject: [PATCH 3/4] Seasons can now list all of their episodes --- trakt/tv.py | 52 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/trakt/tv.py b/trakt/tv.py index 0d3441c4..740a7831 100644 --- a/trakt/tv.py +++ b/trakt/tv.py @@ -2,6 +2,7 @@ from datetime import datetime, timedelta from .core import Airs, Alias, Comment, Genre, Translation, delete, get +from .errors import NotFoundException from .sync import (Scrobbler, rate, comment, add_to_collection, add_to_watchlist, add_to_history, remove_from_collection, remove_from_watchlist, remove_from_history, search) @@ -88,7 +89,7 @@ class TVShow(object): def __init__(self, title='', **kwargs): super(TVShow, self).__init__() - self.media_type = 'show' + self.media_type = 'shows' self.top_watchers = self.top_episodes = self.year = self.tvdb_id = None self.imdb_id = self.genres = self.certification = self.network = None self.trakt_id = self.tmdb_id = self._aliases = self._comments = None @@ -344,7 +345,7 @@ def __init__(self, show, season=1, slug=None, **kwargs): self.show = show self.season = season self.slug = slug or slugify(show) - self.episodes = [] + self._episodes = None self._comments = [] self._ratings = [] self.ext = 'shows/{id}/seasons/{season}'.format(id=self.slug, @@ -364,14 +365,8 @@ def _get(self): def _build(self, data): """Build this :class:`Movie` object with the data in *data*""" - if isinstance(data, list): - for episode in data: - if self.season == episode.pop('season'): - extract_ids(episode) - self.episodes.append(TVEpisode(self.show, self.season, - episode_data=episode)) - else: - self.episodes.append(TVEpisode(self.show, self.season, **data)) + for key, val in data.items(): + setattr(self, key, val) @property @get @@ -389,6 +384,41 @@ def comments(self): self._comments.append(Comment(user=user, **com)) yield self._comments + @property + def episodes(self): + """A list of :class:`TVEpisode` objects representing all of the + Episodes in this :class:`TVSeason`. Because there is no "Get all + episodes for a season" endpoint on the trakt api + """ + if self._episodes is None: + self._episodes = [] + index = 1 + while True: # Dangerous? Perhaps, but it works + try: + ep = self._episode_getter(index) + self._episodes.append(ep) + except NotFoundException: + break + index += 1 + return self._episodes + + @get + def _episode_getter(self, episode): + """Recursive episode getter generator. Will attempt to get the + speicifed episode for this season, and if the requested episode wasn't + found, then we return :const:`None` to indicate to the `episodes` + property that we've already yielded all valid episodes for this season. + + :param episode: An int corresponding to the number of the episode + we're trying to retrieve + """ + episode_extension = '/episodes/{}?extended=full'.format(episode) + try: + data = yield (self.ext + episode_extension) + yield TVEpisode(show=self.show, **data) + except NotFoundException: + yield None + @property @get def ratings(self): @@ -453,7 +483,7 @@ class TVEpisode(object): def __init__(self, show, season, number=-1, **kwargs): super(TVEpisode, self).__init__() - self.media_type = 'episode' + self.media_type = 'episodes' self.show = show self.season = season self.number = number From fd5606b22564871ca87925fdcd8c0c3f8d558554 Mon Sep 17 00:00:00 2001 From: moogar0880 Date: Sun, 23 Aug 2015 19:40:48 -0400 Subject: [PATCH 4/4] Version 2.2.0 bump. Closes #21 --- HISTORY.rst | 7 +++++++ trakt/__init__.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index c381ec09..216eb000 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,5 +1,12 @@ Release History ^^^^^^^^^^^^^^^ +2.2.0 (2015-08-23) +++++++++++++++++++ + +* A TVSeason's `episodes` attribute is now dynamically generated from all episodes in that season +* `sync.rate` and `sync.add_to_history` now properly make valid requests (#21) +* Note: `sync.add_to_history`'s `watched_at` argument is now expected to be a datetime object, in order to match `sync.rate` + 2.1.0 (2015-07-19) ++++++++++++++++++ diff --git a/trakt/__init__.py b/trakt/__init__.py index 1853d757..8be3f41d 100644 --- a/trakt/__init__.py +++ b/trakt/__init__.py @@ -4,6 +4,6 @@ except ImportError: pass -version_info = (2, 1, 0) +version_info = (2, 2, 0) __author__ = 'Jon Nappi' __version__ = '.'.join([str(i) for i in version_info])