diff --git a/lastfm/decorators.py b/lastfm/decorators.py index dcb1377..70d09e4 100644 --- a/lastfm/decorators.py +++ b/lastfm/decorators.py @@ -4,6 +4,7 @@ __author__ = "Abhinav Sarkar " __version__ = "0.2" __license__ = "GNU Lesser General Public License" +__package__ = "lastfm" def top_property(list_property_name): """ @@ -95,6 +96,26 @@ def authenticate(func): pass raise AuthenticationFailedError( "user '%s' does not have permissions to access the service" % username) + wrapper.__doc__ = func.__doc__ + return wrapper + +def depaginate(func): + from lastfm.lazylist import lazylist + def wrapper(*args, **kwargs): + @lazylist + def generator(lst): + gen = func(*args, **kwargs) + total_pages = gen.next() + for e in gen: + yield e + for page in xrange(2, total_pages+1): + kwargs['page'] = page + gen = func(*args, **kwargs) + gen.next() + for e in gen: + yield e + return generator() + wrapper.__doc__ = func.__doc__ return wrapper import copy diff --git a/lastfm/geo.py b/lastfm/geo.py index c570ef2..11837d6 100644 --- a/lastfm/geo.py +++ b/lastfm/geo.py @@ -8,17 +8,18 @@ __package__ = "lastfm" from lastfm.base import LastfmBase from lastfm.mixins import Cacheable -from lastfm.lazylist import lazylist -from lastfm.decorators import cached_property, top_property +from lastfm.decorators import cached_property, top_property, depaginate class Geo(object): """A class representing an geographic location""" @staticmethod + @depaginate def get_events(api, location, latitude = None, longitude = None, - distance = None): + distance = None, + page = None): """ Get the events for a location. @@ -52,26 +53,15 @@ class Geo(object): if latitude is not None and longitude is not None: params.update({'lat': latitude, 'long': longitude}) - - @lazylist - def gen(lst): - data = api._fetch_data(params).find('events') - total_pages = int(data.attrib['totalpages']) - - @lazylist - def gen2(lst, data): - for e in data.findall('event'): - yield Event.create_from_data(api, e) - - for e in gen2(data): - yield e - - for page in xrange(2, total_pages+1): - params.update({'page': page}) - data = api._fetch_data(params).find('events') - for e in gen2(data): - yield e - return gen() + if page is not None: + params.update({'page': page}) + + data = api._fetch_data(params).find('events') + total_pages = int(data.attrib['totalpages']) + yield total_pages + + for e in data.findall('event'): + yield Event.create_from_data(api, e) @staticmethod def get_top_artists(api, country): diff --git a/lastfm/group.py b/lastfm/group.py index 97d07b8..cce82ca 100644 --- a/lastfm/group.py +++ b/lastfm/group.py @@ -9,7 +9,7 @@ __package__ = "lastfm" from lastfm.base import LastfmBase from lastfm.mixins import Cacheable from lastfm.lazylist import lazylist -from lastfm.decorators import cached_property, top_property +from lastfm.decorators import cached_property, top_property, depaginate class Group(LastfmBase, Cacheable): """A class representing a group on last.fm.""" @@ -194,38 +194,26 @@ class Group(LastfmBase, Cacheable): return gen() @cached_property - def members(self): + @depaginate + def members(self, page = None): """ members of the group @rtype: L{lazylist} of L{User} """ params = self._default_params({'method': 'group.getMembers'}) - - @lazylist - def gen(lst): - data = self._api._fetch_data(params).find('members') - total_pages = int(data.attrib['totalPages']) - - @lazylist - def gen2(lst, data): - for u in data.findall('user'): - yield User( - self._api, - name = u.findtext('name'), - real_name = u.findtext('realname'), - image = dict([(i.get('size'), i.text) for i in u.findall('image')]), - url = u.findtext('url') - ) - - for u in gen2(data): - yield u - - for page in xrange(1, total_pages+1): - params.update({'page': page}) - data = self._api._fetch_data(params).find('members') - for u in gen2(data): - yield u - return gen() + if page is not None: + params.update({'page': page}) + data = self._api._fetch_data(params).find('members') + total_pages = int(data.attrib['totalPages']) + yield total_pages + for u in data.findall('user'): + yield User( + self._api, + name = u.findtext('name'), + real_name = u.findtext('realname'), + image = dict([(i.get('size'), i.text) for i in u.findall('image')]), + url = u.findtext('url') + ) def _default_params(self, extra_params = {}): if not self.name: diff --git a/lastfm/mixins/searchable.py b/lastfm/mixins/searchable.py index cfbc528..eb42a0c 100644 --- a/lastfm/mixins/searchable.py +++ b/lastfm/mixins/searchable.py @@ -5,14 +5,16 @@ __version__ = "0.2" __license__ = "GNU Lesser General Public License" __package__ = "lastfm.mixins" -from lastfm.lazylist import lazylist +from lastfm.decorators import depaginate class Searchable(object): @classmethod + @depaginate def search(cls, api, search_item, limit = None, + page = None, **kwds): from lastfm.api import Api cls_name = cls.__name__.lower() @@ -26,27 +28,15 @@ class Searchable(object): if limit: params.update({'limit': limit}) - - @lazylist - def gen(lst): - data = api._fetch_data(params).find('results') - total_pages = int(data.findtext("{%s}totalResults" % Api.SEARCH_XMLNS))/ \ + if page is not None: + params.update({'page': page}) + + data = api._fetch_data(params).find('results') + total_pages = int(data.findtext("{%s}totalResults" % Api.SEARCH_XMLNS))/ \ int(data.findtext("{%s}itemsPerPage" % Api.SEARCH_XMLNS)) + 1 - - @lazylist - def gen2(lst, data): - for a in data.findall('%smatches/%s'%(cls_name, cls_name)): - yield cls._search_yield_func(api, a) - - for a in gen2(data): - yield a - - for page in xrange(2, total_pages+1): - params.update({'page': page}) - data = api._fetch_data(params).find('results') - for a in gen2(data): - yield a - return gen() + yield total_pages + for a in data.findall('%smatches/%s'%(cls_name, cls_name)): + yield cls._search_yield_func(api, a) @staticmethod def _search_yield_func(api, search_term): diff --git a/lastfm/user.py b/lastfm/user.py index 4b4023d..85a3a35 100644 --- a/lastfm/user.py +++ b/lastfm/user.py @@ -9,7 +9,7 @@ from lastfm.base import LastfmBase from lastfm.mixins import Cacheable, Shoutable from lastfm.lazylist import lazylist import lastfm.playlist -from lastfm.decorators import cached_property, top_property, authenticate +from lastfm.decorators import cached_property, top_property, authenticate, depaginate class User(LastfmBase, Cacheable, Shoutable): """A class representing an user.""" @@ -110,62 +110,38 @@ class User(LastfmBase, Cacheable, Shoutable): Event.create_from_data(self._api, e) for e in data.findall('event') ] - - def get_past_events(self, - limit = None): + + @depaginate + def get_past_events(self, limit = None, page = None): params = self._default_params({'method': 'user.getPastEvents'}) if limit is not None: params.update({'limit': limit}) + if page is not None: + params.update({'page': page}) - @lazylist - def gen(lst): - data = self._api._fetch_data(params).find('events') - total_pages = int(data.attrib['totalPages']) - - @lazylist - def gen2(lst, data): - for e in data.findall('event'): - yield Event.create_from_data(self._api, e) - - for e in gen2(data): - yield e - - for page in xrange(2, total_pages+1): - params.update({'page': page}) - data = self._api._fetch_data(params).find('events') - for e in gen2(data): - yield e - return gen() + data = self._api._fetch_data(params).find('events') + total_pages = int(data.attrib['totalPages']) + yield total_pages + for e in data.findall('event'): + yield Event.create_from_data(self._api, e) @cached_property def past_events(self): return self.get_past_events() @authenticate - def get_recommended_events(self, limit = None): + @depaginate + def get_recommended_events(self, limit = None, page = None): params = {'method': 'user.getRecommendedEvents'} if limit is not None: params.update({'limit': limit}) - - @lazylist - def gen(lst): - data = self._api._fetch_data(params, sign = True, session = True).find('events') - total_pages = int(data.attrib['totalPages']) - - @lazylist - def gen2(lst, data): - for e in data.findall('event'): - yield Event.create_from_data(self._api, e) - - for e in gen2(data): - yield e - - for page in xrange(2, total_pages+1): - params.update({'page': page}) - data = self._api._fetch_data(params, sign = True, session = True).find('events') - for e in gen2(data): - yield e - return gen() + if page is not None: + params.update({'page': page}) + data = self._api._fetch_data(params, sign = True, session = True).find('events') + total_pages = int(data.attrib['totalPages']) + yield total_pages + for e in data.findall('event'): + yield Event.create_from_data(self._api, e) @cached_property def recommended_events(self): @@ -411,35 +387,25 @@ class User(LastfmBase, Cacheable, Shoutable): @cached_property @authenticate - def recommended_artists(self): + @depaginate + def recommended_artists(self, page = None): params = {'method': 'user.getRecommendedArtists'} - - @lazylist - def gen(lst): - data = self._api._fetch_data(params, sign = True, session = True).find('recommendations') - total_pages = int(data.attrib['totalPages']) + if page is not None: + params.update({'page': page}) + + data = self._api._fetch_data(params, sign = True, session = True).find('recommendations') + total_pages = int(data.attrib['totalPages']) + yield total_pages - @lazylist - def gen2(lst, data): - for a in data.findall('artist'): - yield Artist( - self._api, - name = a.findtext('name'), - mbid = a.findtext('mbid'), - url = a.findtext('url'), - streamable = (a.findtext('streamable') == "1"), - image = dict([(i.get('size'), i.text) for i in a.findall('image')]), - ) - - for a in gen2(data): - yield a - - for page in xrange(2, total_pages+1): - params.update({'page': page}) - data = self._api._fetch_data(params, sign = True, session = True).find('recommendations') - for a in gen2(data): - yield a - return gen() + for a in data.findall('artist'): + yield Artist( + self._api, + name = a.findtext('name'), + mbid = a.findtext('mbid'), + url = a.findtext('url'), + streamable = (a.findtext('streamable') == "1"), + image = dict([(i.get('size'), i.text) for i in a.findall('image')]), + ) def get_top_tracks(self, period = None): params = self._default_params({'method': 'user.getTopTracks'}) @@ -755,53 +721,41 @@ class User(LastfmBase, Cacheable, Shoutable): def user(self): return self._user - def get_albums(self, - limit = None): + @depaginate + def get_albums(self, limit = None, page = None): params = self._default_params({'method': 'library.getAlbums'}) if limit is not None: params.update({'limit': limit}) + if page is not None: + params.update({'page': page}) - @lazylist - def gen(lst): - data = self._api._fetch_data(params).find('albums') + try: + data = self._api._fetch_data(params).find('albums') total_pages = int(data.attrib['totalPages']) - - @lazylist - def gen2(lst, data): - for a in data.findall('album'): - yield Album( - self._api, - subject = self, - name = a.findtext('name'), - artist = Artist( - self._api, - subject = self, - name = a.findtext('artist/name'), - mbid = a.findtext('artist/mbid'), - url = a.findtext('artist/url'), - ), - mbid = a.findtext('mbid'), - url = a.findtext('url'), - image = dict([(i.get('size'), i.text) for i in a.findall('image')]), - stats = Stats( - subject = a.findtext('name'), - playcount = int(a.findtext('playcount')), - ) - ) - - - for a in gen2(data): - yield a - - for page in xrange(2, total_pages+1): - params.update({'page': page}) - try: - data = self._api._fetch_data(params).find('albums') - except LastfmError: - continue - for a in gen2(data): - yield a - return gen() + yield total_pages + + for a in data.findall('album'): + yield Album( + self._api, + subject = self, + name = a.findtext('name'), + artist = Artist( + self._api, + subject = self, + name = a.findtext('artist/name'), + mbid = a.findtext('artist/mbid'), + url = a.findtext('artist/url'), + ), + mbid = a.findtext('mbid'), + url = a.findtext('url'), + image = dict([(i.get('size'), i.text) for i in a.findall('image')]), + stats = Stats( + subject = a.findtext('name'), + playcount = int(a.findtext('playcount')), + ) + ) + except LastfmError: + return @cached_property def albums(self): @@ -825,47 +779,36 @@ class User(LastfmBase, Cacheable, Shoutable): self._api._post_data(params) self._albums = None - def get_artists(self, - limit = None): + @depaginate + def get_artists(self, limit = None, page = None): params = self._default_params({'method': 'library.getArtists'}) if limit is not None: params.update({'limit': limit}) + if page is not None: + params.update({'page': page}) - @lazylist - def gen(lst): + try: data = self._api._fetch_data(params).find('artists') total_pages = int(data.attrib['totalPages']) - - @lazylist - def gen2(lst, data): - for a in data.findall('artist'): - yield Artist( - self._api, - subject = self, - name = a.findtext('name'), - mbid = a.findtext('mbid'), - stats = Stats( - subject = a.findtext('name'), - playcount = a.findtext('playcount') and int(a.findtext('playcount')) or None, - tagcount = a.findtext('tagcount') and int(a.findtext('tagcount')) or None - ), - url = a.findtext('url'), - streamable = (a.findtext('streamable') == "1"), - image = dict([(i.get('size'), i.text) for i in a.findall('image')]), - ) - - for a in gen2(data): - yield a - - for page in xrange(2, total_pages+1): - params.update({'page': page}) - try: - data = self._api._fetch_data(params).find('artists') - except LastfmError: - continue - for a in gen2(data): - yield a - return gen() + yield total_pages + + for a in data.findall('artist'): + yield Artist( + self._api, + subject = self, + name = a.findtext('name'), + mbid = a.findtext('mbid'), + stats = Stats( + subject = a.findtext('name'), + playcount = a.findtext('playcount') and int(a.findtext('playcount')) or None, + tagcount = a.findtext('tagcount') and int(a.findtext('tagcount')) or None + ), + url = a.findtext('url'), + streamable = (a.findtext('streamable') == "1"), + image = dict([(i.get('size'), i.text) for i in a.findall('image')]), + ) + except LastfmError: + return @cached_property def artists(self): @@ -881,55 +824,43 @@ class User(LastfmBase, Cacheable, Shoutable): self._api._post_data(params) self._artists = None - def get_tracks(self, - limit = None): + @depaginate + def get_tracks(self, limit = None, page = None): params = self._default_params({'method': 'library.getTracks'}) if limit is not None: params.update({'limit': limit}) - - @lazylist - def gen(lst): + if page is not None: + params.update({'page': page}) + + try: data = self._api._fetch_data(params).find('tracks') total_pages = int(data.attrib['totalPages']) - - @lazylist - def gen2(lst, data): - for t in data.findall('track'): - yield Track( - self._api, - subject = self, - name = t.findtext('name'), - artist = Artist( - self._api, - subject = self, - name = t.findtext('artist/name'), - mbid = t.findtext('artist/mbid'), - url = t.findtext('artist/url'), - ), - mbid = t.findtext('mbid'), - stats = Stats( - subject = t.findtext('name'), - playcount = t.findtext('playcount') and int(t.findtext('playcount')) or None, - tagcount = t.findtext('tagcount') and int(t.findtext('tagcount')) or None - ), - streamable = (t.findtext('streamable') == '1'), - full_track = (t.find('streamable').attrib['fulltrack'] == '1'), - image = dict([(i.get('size'), i.text) for i in t.findall('image')]), - ) - - for t in gen2(data): - yield t - - for page in xrange(2, total_pages+1): - params.update({'page': page}) - data = None - try: - data = self._api._fetch_data(params).find('tracks') - except LastfmError: - continue - for t in gen2(data): - yield t - return gen() + yield total_pages + + for t in data.findall('track'): + yield Track( + self._api, + subject = self, + name = t.findtext('name'), + artist = Artist( + self._api, + subject = self, + name = t.findtext('artist/name'), + mbid = t.findtext('artist/mbid'), + url = t.findtext('artist/url'), + ), + mbid = t.findtext('mbid'), + stats = Stats( + subject = t.findtext('name'), + playcount = t.findtext('playcount') and int(t.findtext('playcount')) or None, + tagcount = t.findtext('tagcount') and int(t.findtext('tagcount')) or None + ), + streamable = (t.findtext('streamable') == '1'), + full_track = (t.find('streamable').attrib['fulltrack'] == '1'), + image = dict([(i.get('size'), i.text) for i in t.findall('image')]), + ) + except LastfmError: + return @cached_property def tracks(self): diff --git a/lastfm/venue.py b/lastfm/venue.py index e81ebb2..14a41f2 100644 --- a/lastfm/venue.py +++ b/lastfm/venue.py @@ -7,8 +7,7 @@ __package__ = "lastfm" from lastfm.base import LastfmBase from lastfm.mixins import Cacheable, Searchable -from lastfm.lazylist import lazylist -from lastfm.decorators import cached_property +from lastfm.decorators import cached_property, depaginate class Venue(LastfmBase, Cacheable, Searchable): """A class representing a venue of an event""" @@ -56,32 +55,21 @@ class Venue(LastfmBase, Cacheable, Searchable): Event.create_from_data(self._api, e) for e in data.findall('event') ] - - def get_past_events(self, - limit = None): + + @depaginate + def get_past_events(self, limit = None, page = None): params = self._default_params({'method': 'venue.getPastEvents'}) if limit is not None: params.update({'limit': limit}) + if page is not None: + params.update({'page': page}) - @lazylist - def gen(lst): - data = self._api._fetch_data(params).find('events') - total_pages = int(data.attrib['totalPages']) + data = self._api._fetch_data(params).find('events') + total_pages = int(data.attrib['totalPages']) + yield total_pages - @lazylist - def gen2(lst, data): - for e in data.findall('event'): - yield Event.create_from_data(self._api, e) - - for e in gen2(data): - yield e - - for page in xrange(2, total_pages+1): - params.update({'page': page}) - data = self._api._fetch_data(params).find('events') - for e in gen2(data): - yield e - return gen() + for e in data.findall('event'): + yield Event.create_from_data(self._api, e) @cached_property def past_events(self): @@ -96,6 +84,9 @@ class Venue(LastfmBase, Cacheable, Searchable): @staticmethod def _search_yield_func(api, venue): + latitude = venue.findtext('location/{%s}point/{%s}lat' % ((Location.XMLNS,)*2)) + longitude = venue.findtext('location/{%s}point/{%s}long' % ((Location.XMLNS,)*2)) + return Venue( api, id = int(venue.findtext('id')), @@ -109,13 +100,9 @@ class Venue(LastfmBase, Cacheable, Searchable): ), street = venue.findtext('location/street'), postal_code = venue.findtext('location/postalcode'), - latitude = float(venue.findtext( - 'location/{%s}point/{%s}lat' % ((Location.XMLNS,)*2) - )), - longitude = float(venue.findtext( - 'location/{%s}point/{%s}long' % ((Location.XMLNS,)*2) - )), - ), + latitude = (latitude.strip()!= '') and float(latitude) or None, + longitude = (longitude.strip()!= '') and float(longitude) or None, + ), url = venue.findtext('url') )