Skip to content

Commit c60d0d4

Browse files
authored
Add paginated User.get_watched_movies extraction from #113
1 parent ad370dc commit c60d0d4

3 files changed

Lines changed: 101 additions & 6 deletions

File tree

tests/test_users.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# -*- coding: utf-8 -*-
2+
from copy import deepcopy
3+
from urllib.parse import parse_qs, urlsplit
4+
5+
import pytest
6+
import trakt
27
from trakt.movies import Movie
38
from trakt.people import Person
49
from trakt.tv import TVEpisode, TVSeason, TVShow
@@ -132,6 +137,67 @@ def test_get_watched_movies():
132137
assert all([isinstance(m, Movie) for m in watched_movies])
133138

134139

140+
def test_watched_movies_pagination():
141+
sean = User('sean')
142+
client = trakt.core.api()
143+
request_calls = []
144+
original_request = client.request
145+
146+
def request(method, uri, data=None):
147+
request_calls.append((method, uri, data))
148+
if uri.startswith('users/sean/watched/movies?'):
149+
query = parse_qs(urlsplit(f'https://api.trakt.tv/{uri}').query)
150+
if query.get('limit') == ['1']:
151+
response = original_request('GET', 'users/sean/watched/movies')
152+
return deepcopy(response[:1])
153+
if uri == 'users/sean/watched/movies':
154+
response = original_request('GET', 'users/sean/watched/movies')
155+
return deepcopy(response)
156+
return original_request(method, uri, data)
157+
158+
client.request = request
159+
try:
160+
expected_uri = 'users/sean/watched/movies?page=2&limit=1'
161+
watched_movies = sean.get_watched_movies(page=2, limit=1)
162+
assert request_calls[-1] == ('get', expected_uri, None)
163+
assert len(watched_movies) == 1
164+
assert all([isinstance(movie, Movie) for movie in watched_movies])
165+
166+
watched_movies = sean.get_watched_movies(limit=1)
167+
expected_uri = 'users/sean/watched/movies?limit=1'
168+
assert request_calls[-1] == ('get', expected_uri, None)
169+
assert len(watched_movies) == 1
170+
assert all([isinstance(movie, Movie) for movie in watched_movies])
171+
172+
watched_movies = sean.watched_movies
173+
assert request_calls[-1] == ('get', 'users/sean/watched/movies', None)
174+
assert all([isinstance(movie, Movie) for movie in watched_movies])
175+
finally:
176+
client.request = original_request
177+
178+
179+
def test_watched_movies_pagination_validation():
180+
sean = User('sean')
181+
182+
with pytest.raises(ValueError, match='page must be a positive integer'):
183+
sean.get_watched_movies(page=0)
184+
185+
with pytest.raises(ValueError, match='page must be a positive integer'):
186+
sean.get_watched_movies(page=-1)
187+
188+
with pytest.raises(ValueError, match='page must be a valid integer'):
189+
sean.get_watched_movies(page='invalid')
190+
191+
with pytest.raises(ValueError, match='limit must be a positive integer'):
192+
sean.get_watched_movies(limit=0)
193+
194+
with pytest.raises(ValueError, match='limit must be a positive integer'):
195+
sean.get_watched_movies(limit=-1)
196+
197+
with pytest.raises(ValueError, match='limit must be a valid integer'):
198+
sean.get_watched_movies(limit='invalid')
199+
200+
135201
def test_stats():
136202
sean = User('sean')
137203
assert isinstance(sean.get_stats(), dict)

trakt/users.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

44
from dataclasses import dataclass, fields
55
from typing import Any, NamedTuple, Optional, Union
6+
from urllib.parse import urlencode
67

78
from trakt.core import delete, get, post
89
from trakt.mixins import DataClassMixin, IdsMixin
910
from trakt.movies import Movie
1011
from trakt.people import Person
1112
from trakt.tv import TVEpisode, TVSeason, TVShow
12-
from trakt.utils import slugify
13+
from trakt.utils import (slugify,
14+
validate_pagination_param)
1315

1416
__author__ = 'Jon Nappi'
1517
__all__ = ['User', 'UserList', 'PublicList', 'Request', 'follow', 'get_all_requests',
@@ -523,15 +525,22 @@ def _build_watched_movies(self, data):
523525
return watched_movies
524526

525527
@get
526-
def get_watched_movies(self):
528+
def get_watched_movies(self, page=None, limit=None):
527529
"""Watched progress for all :class:`Movie` objects for this
528530
:class:`User`.
529531
530532
:return: List of :class:`Movie` instances
531533
"""
532-
data = yield 'users/{user}/watched/movies'.format(
533-
user=slugify(self.username)
534-
)
534+
uri = 'users/{user}/watched/movies'.format(user=slugify(self.username))
535+
params = {}
536+
if page is not None:
537+
params['page'] = validate_pagination_param('page', page)
538+
if limit is not None:
539+
params['limit'] = validate_pagination_param('limit', limit)
540+
if params:
541+
uri += '?' + urlencode(params)
542+
543+
data = yield uri
535544
yield self._build_watched_movies(data)
536545

537546
@property

trakt/utils.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from datetime import datetime, timezone
55

66
__author__ = 'Jon Nappi'
7-
__all__ = ['slugify', 'airs_date', 'now', 'timestamp', 'extract_ids']
7+
__all__ = ['slugify', 'airs_date', 'now', 'timestamp', 'extract_ids',
8+
'validate_pagination_param']
89

910

1011
def slugify(value):
@@ -56,3 +57,22 @@ def extract_ids(id_dict):
5657
"""
5758
id_dict.update(id_dict.pop('ids', {}))
5859
return id_dict
60+
61+
62+
def validate_pagination_param(name, value):
63+
"""Validate and coerce a pagination parameter to a positive integer.
64+
65+
:param name: Parameter name used in error messages.
66+
:param value: Value to validate.
67+
:return: The validated integer value.
68+
:raises ValueError: If value is not a valid positive integer.
69+
"""
70+
try:
71+
value = int(value)
72+
except (TypeError, ValueError):
73+
raise ValueError(f'{name} must be a valid integer')
74+
75+
if value < 1:
76+
raise ValueError(f'{name} must be a positive integer')
77+
78+
return value

0 commit comments

Comments
 (0)