Skip to content

Commit af5dba1

Browse files
committed
Rework search result paging with a more generic base class.
1 parent bfa84c5 commit af5dba1

File tree

3 files changed

+123
-67
lines changed

3 files changed

+123
-67
lines changed

tmdb3/pager.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
#-----------------------
4+
# Name: pager.py List-like structure designed for handling paged results
5+
# Python Library
6+
# Author: Raymond Wagner
7+
#-----------------------
8+
9+
from collections import Sequence, Iterator
10+
11+
class PagedIterator( Iterator ):
12+
def __init__(self, parent):
13+
self._parent = parent
14+
self._index = -1
15+
self._len = len(parent)
16+
17+
def __iter__(self):
18+
return self
19+
20+
def next(self):
21+
self._index += 1
22+
if self._index == self._len:
23+
raise StopIteration
24+
return self._parent[self._index]
25+
26+
class UnpagedData( object ):
27+
def copy(self):
28+
return self.__class__()
29+
30+
def __mul__(self, other):
31+
return (self.copy() for a in range(other))
32+
33+
def __rmul__(self, other):
34+
return (self.copy() for a in range(other))
35+
36+
class PagedList( Sequence ):
37+
"""
38+
List-like object, with support for automatically grabbing additional
39+
pages from a data source.
40+
"""
41+
_iter_class = None
42+
43+
def __iter__(self):
44+
if self._iter_class is None:
45+
self._iter_class = type(self.__class__.__name__ + 'Iterator',
46+
(PagedIterator,), {})
47+
return self._iter_class(self)
48+
49+
def __len__(self):
50+
try:
51+
return self._len
52+
except:
53+
return len(self._data)
54+
55+
def __init__(self, iterable, pagesize=20):
56+
self._data = list(iterable)
57+
self._pagesize = pagesize
58+
59+
def __getitem__(self, index):
60+
if index >= len(self):
61+
raise IndexError("list index outside range")
62+
if (index >= len(self._data)) \
63+
or isinstance(self._data[index], UnpagedData):
64+
self._populatepage(index/self._pagesize + 1)
65+
return self._data[index]
66+
67+
def __setitem__(self, index, value):
68+
raise NotImplementedError
69+
70+
def __delitem__(self, index):
71+
raise NotImplementedError
72+
73+
def __contains__(self, item):
74+
raise NotImplementedError
75+
76+
def _populatepage(self, page):
77+
pagestart = (page-1) * self._pagesize
78+
if len(self._data) < pagestart:
79+
self._data.extend(UnpagedData()*(pagestart-len(self._data)))
80+
if len(self._data) == pagestart:
81+
self._data.extend(self._getpage(page))
82+
else:
83+
for data in self._getpage(page):
84+
self._data[pagestart] = data
85+
pagestart += 1
86+
87+
def _getpage(self, page):
88+
raise NotImplementedError("PagedList._getpage() must be provided "+\
89+
"by subclass")
90+
91+
class PagedRequest( PagedList ):
92+
"""
93+
Derived PageList that provides a list-like object with automatic paging
94+
intended for use with search requests.
95+
"""
96+
def __init__(self, request, handler=None):
97+
self._request = request
98+
if handler: self._handler = handler
99+
super(PagedRequest, self).__init__(self._getpage(1), 20)
100+
101+
def _getpage(self, page):
102+
req = self._request.new(page=page)
103+
res = req.readJSON()
104+
self._len = res['total_results']
105+
for item in res['results']:
106+
yield self._handler(item)
107+

tmdb3/tmdb_api.py

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
# 0.3.4 Re-enable search paging
3434

3535
from request import set_key, Request
36-
from util import PagedList, Datapoint, Datalist, Datadict, Element
36+
from util import Datapoint, Datalist, Datadict, Element
37+
from pager import PagedRequest
3738
from tmdb_exceptions import *
3839

3940
import json
@@ -50,47 +51,31 @@ def _populate(self):
5051
Configuration = Configuration()
5152

5253
def searchMovie(query, language='en', adult=False):
53-
return MovieSearchResults(
54+
return MovieSearchResult(
5455
Request('search/movie', query=query, include_adult=adult),
5556
language=language)
5657

57-
class MovieSearchResults( PagedList ):
58+
class MovieSearchResult( PagedRequest ):
5859
"""Stores a list of search matches."""
59-
def _process(self, data):
60-
for item in data['results']:
61-
yield Movie(raw=item, language=self.language)
62-
63-
def _getpage(self, page):
64-
self.request = self.request.new(page=page)
65-
return list(self._process(self.request.readJSON()))
66-
6760
def __init__(self, request, language=None):
68-
self.language=language
6961
if language:
70-
self.request = request.new(language=language)
71-
res = self.request.readJSON()
72-
super(MovieSearchResults, self).__init__(res, res['total_results'], 20)
73-
62+
super(MovieSearchResult, self).__init__(
63+
request.new(language=language),
64+
lambda x: Movie(raw=x, language=language))
65+
else:
66+
super(MovieSearchResult, self).__init__(res,
67+
lambda x: Movie(raw=x))
68+
7469
def __repr__(self):
75-
return u"<Search Results: {0}>".format(self.request._kwargs['query'])
70+
return u"<Search Results: {0}>".format(self._request._kwargs['query'])
7671

7772
def searchPerson(query):
78-
return PeopleSearchResults(Request('search/person', query=query))
79-
80-
class PeopleSearchResults( PagedList ):
81-
@staticmethod
82-
def _process(data):
83-
for item in data['results']:
84-
yield Person(raw=item)
85-
86-
def _getpage(self, page):
87-
self.request = self.request.new(page=page)
88-
return list(self._process(self.request.readJSON()))
73+
return PeopleSearchResult(Request('search/person', query=query))
8974

75+
class PeopleSearchResult( PagedRequest ):
76+
"""Stores a list of search matches."""
9077
def __init__(self, request):
91-
self.request = request
92-
res = self.request.readJSON()
93-
super(PeopleSearchResults, self).__init__(res, res['total_results'], 20)
78+
super(PeopleSearchResults, self).__init__(res)
9479

9580
def __repr__(self):
9681
return u"<Search Results: {0}>".format(self.request._kwargs['query'])

tmdb3/util.py

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,42 +8,6 @@
88

99
from copy import copy
1010

11-
class PagedList( list ):
12-
"""
13-
List-like object, with support for automatically grabbing additional
14-
pages from a data source.
15-
"""
16-
def __init__(self, iterable, count, perpage=20):
17-
self._perpage = perpage
18-
if count:
19-
super(PagedList, self).__init__(self._process(iterable))
20-
super(PagedList, self).extend([None]*(count-len(self)))
21-
else:
22-
super(PagedList, self).__init__()
23-
24-
def _getpage(self, page):
25-
raise NotImplementedError("PagedList._getpage() must be provided "+\
26-
"by subclass")
27-
28-
def _populateindex(self, index):
29-
self._populatepage(int(index/self._perpage)+1)
30-
31-
def _populatepage(self, page):
32-
offset = (page-1)*self._perpage
33-
for i,data in enumerate(self._getpage(page)):
34-
super(PagedList, self).__setitem__(offset+i, data)
35-
36-
def __getitem__(self, index):
37-
item = super(PagedList, self).__getitem__(index)
38-
if item is None:
39-
self._populateindex(index)
40-
item = super(PagedList, self).__getitem__(index)
41-
return item
42-
43-
def __iter__(self):
44-
for i in range(len(self)):
45-
yield self[i]
46-
4711
class Poller( object ):
4812
"""
4913
Wrapper for an optional callable to populate an Element derived class

0 commit comments

Comments
 (0)