diff --git a/lastfm/api.py b/lastfm/api.py index 2cdb7ae..2edde87 100644 --- a/lastfm/api.py +++ b/lastfm/api.py @@ -6,7 +6,7 @@ __version__ = "0.2" __license__ = "GNU Lesser General Public License" __package__ = "lastfm" -from lastfm.decorators import cached_property +from lastfm.decorators import cached_property, async_callback class Api(object): """The class representing the last.fm web services API.""" @@ -169,6 +169,7 @@ class Api(object): """ self._request_headers['User-Agent'] = user_agent + @async_callback def get_album(self, album = None, artist = None, @@ -196,6 +197,7 @@ class Api(object): artist = artist.name return Album.get_info(self, artist, album, mbid) + @async_callback def search_album(self, album, limit = None): """ Search for an album by name. @@ -212,6 +214,7 @@ class Api(object): """ return Album.search(self, search_item = album, limit = limit) + @async_callback def get_artist(self, artist = None, mbid = None): @@ -232,7 +235,8 @@ class Api(object): @see: L{Artist.get_info} """ return Artist.get_info(self, artist, mbid) - + + @async_callback def search_artist(self, artist, limit = None): @@ -251,6 +255,7 @@ class Api(object): """ return Artist.search(self, search_item = artist, limit = limit) + @async_callback def get_event(self, event): """ Get an event object. @@ -266,7 +271,8 @@ class Api(object): @see: L{Event.get_info} """ return Event.get_info(self, event) - + + @async_callback def get_location(self, city): """ Get a location object. @@ -279,6 +285,7 @@ class Api(object): """ return Location(self, city = city) + @async_callback def get_country(self, name): """ Get a country object. @@ -290,7 +297,8 @@ class Api(object): @rtype: L{Country} """ return Country(self, name = name) - + + @async_callback def get_group(self, name): """ Get a group object. @@ -303,6 +311,7 @@ class Api(object): """ return Group(self, name = name) + @async_callback def get_playlist(self, url): """ Get a playlist object. @@ -316,7 +325,8 @@ class Api(object): @see: L{Playlist.fetch} """ return Playlist.fetch(self, url) - + + @async_callback def get_tag(self, name): """ Get a tag object. @@ -329,6 +339,7 @@ class Api(object): """ return Tag(self, name = name) + @async_callback def get_global_top_tags(self): """ Get the top global tags on Last.fm, sorted by popularity (number of times used). @@ -338,6 +349,7 @@ class Api(object): """ return Tag.get_top_tags(self) + @async_callback def search_tag(self, tag, limit = None): @@ -356,6 +368,7 @@ class Api(object): """ return Tag.search(self, search_item = tag, limit = limit) + @async_callback def compare_taste(self, type1, type2, value1, value2, @@ -383,6 +396,7 @@ class Api(object): """ return Tasteometer.compare(self, type1, type2, value1, value2, limit) + @async_callback def get_track(self, track, artist = None, mbid = None): """ Get a track object. @@ -405,11 +419,9 @@ class Api(object): if isinstance(artist, Artist): artist = artist.name return Track.get_info(self, artist, track, mbid) - - def search_track(self, - track, - artist = None, - limit = None): + + @async_callback + def search_track(self, track, artist = None, limit = None): """ Search for a track by name. @@ -429,6 +441,7 @@ class Api(object): artist = artist.name return Track.search(self, search_item = track, limit = limit, artist = artist) + @async_callback def get_user(self, name): """ Get an user object. @@ -445,6 +458,7 @@ class Api(object): """ return User.get_info(self, name = name) + @async_callback def get_authenticated_user(self): """ Get the currently authenticated user. @@ -459,6 +473,7 @@ class Api(object): else: raise AuthenticationFailedError("session key must be present to call this method") + @async_callback def get_venue(self, venue): """ Get a venue object. @@ -478,6 +493,7 @@ class Api(object): except IndexError: raise InvalidParametersError("No such venue exists") + @async_callback def search_venue(self, venue, limit = None, country = None): """ Search for a venue by name. diff --git a/lastfm/decorators.py b/lastfm/decorators.py index 064b731..8a6ce10 100644 --- a/lastfm/decorators.py +++ b/lastfm/decorators.py @@ -100,6 +100,16 @@ def authenticate(func): return wrapper def depaginate(func): + """ + A decorator to depaginate the search results. + + @param func: a function that returns the first page of search results + @type func: C{function} + + @return: a function that wraps the original function and returns + a L{lazylist} of all search results (all pages) + @rtype: C{function} + """ from lastfm.lazylist import lazylist def wrapper(*args, **kwargs): @lazylist @@ -118,5 +128,82 @@ def depaginate(func): wrapper.__doc__ = func.__doc__ return wrapper +def async_callback(func): + """ + A decorator to convert a synchronous (blocking) function into + an asynchronous (non-blocking) function. + + Pass a callback function as a keyword argument + (C{func(other argument... , callback = callback)}) or positional argument + (C{func(other argument... , callback)}) to the function to activate the + asynchronous behaviour. The callback function is called with the return value + of the original function when it returns. If an exception is raised in the + original function, then the callback function is called with that exception. + If the callback function is not given then the original function is called + synchronously (it blocks the caller function) and its return value is returned. + + All the functions on which this decorator is applied get the signature: + C{func(self, *args, **kwargs)}. Refer to the documentation or source code of + the original function for the correct function signature. + + @param func: a synchronous (blocking) function + @type func: C{function} + + @return: an asynchronous (non-blocking) function that wraps the + original synchronous (blocking) function + @rtype: C{function} + """ + from threading import Thread + def wrapper(self, *args, **kwargs): + callback = None + for a in args: + if callable(a): + callback = a + args = list(args) + args.remove(a) + args = tuple(args) + break + if 'callback' in kwargs: + callback = kwargs['callback'] + del kwargs['callback'] + + if (callback is not None and callable(callback)): + def async_call(): + result = None + try: + result = func(self, *args, **kwargs) + except Exception, e: + result = e + callback(result) + thread = Thread(target = async_call) + thread.start() + return + return func(self, *args, **kwargs) + + wrapper.__doc__ = "%s\n @see: L{async_callback}" % func.__doc__ + return wrapper + +def _get_arg_string(argspecs): + arg_str = "" + args = argspecs.args + args.remove('self') + defaults = argspecs.defaults + + if defaults is not None: + defargs = args[-len(defaults):] + nondefargs = args[:-len(defaults)] + nondefargs_str = ", ".join(nondefargs) + defargs_str = ", ".join(["%s = %s" % (defargs[i], defaults[i]) for i in xrange(len(defargs))]) + if nondefargs_str != '' and defargs_str != '': + arg_str = "%s, %s" % (nondefargs_str, defargs_str) + elif nondefargs_str != '': + arg_str = nondefargs_str + elif defargs_str != '': + arg_str = defargs_str + else: + arg_str = ", ".join(args) + print arg_str + return arg_str + import copy from lastfm.error import LastfmError, AuthenticationFailedError \ No newline at end of file diff --git a/lastfm/weeklychart.py b/lastfm/weeklychart.py index 01a54af..436de6e 100644 --- a/lastfm/weeklychart.py +++ b/lastfm/weeklychart.py @@ -119,41 +119,41 @@ class WeeklyAlbumChart(WeeklyChart): end = datetime.utcfromtimestamp(int(data.attrib['to'])), ) return WeeklyAlbumChart( - subject = subject, - start = datetime.utcfromtimestamp(int(data.attrib['from'])), - end = datetime.utcfromtimestamp(int(data.attrib['to'])), - stats = Stats( - subject = subject, - playcount = reduce( - lambda x,y:( - x + int(y.findtext('playcount')) - ), - data.findall('album'), - 0 - ) - ), - albums = [ - Album( - api, - subject = w, - name = a.findtext('name'), - mbid = a.findtext('mbid'), - artist = Artist( - api, - subject = w, - name = a.findtext('artist'), - mbid = a.find('artist').attrib['mbid'], - ), - stats = Stats( - subject = a.findtext('name'), - rank = int(a.attrib['rank']), - playcount = int(a.findtext('playcount')), - ), - url = a.findtext('url'), - ) - for a in data.findall('album') - ] - ) + subject = subject, + start = datetime.utcfromtimestamp(int(data.attrib['from'])), + end = datetime.utcfromtimestamp(int(data.attrib['to'])), + stats = Stats( + subject = subject, + playcount = reduce( + lambda x,y:( + x + int(y.findtext('playcount')) + ), + data.findall('album'), + 0 + ) + ), + albums = [ + Album( + api, + subject = w, + name = a.findtext('name'), + mbid = a.findtext('mbid'), + artist = Artist( + api, + subject = w, + name = a.findtext('artist'), + mbid = a.find('artist').attrib['mbid'], + ), + stats = Stats( + subject = a.findtext('name'), + rank = int(a.attrib['rank']), + playcount = int(a.findtext('playcount')), + ), + url = a.findtext('url'), + ) + for a in data.findall('album') + ] + ) class WeeklyArtistChart(WeeklyChart): """A class for representing the weekly artist charts""" @@ -176,32 +176,34 @@ class WeeklyArtistChart(WeeklyChart): def get_count_attribute(artist): return {count_attribute: int(eval(artist.findtext(count_attribute)))} def get_count_attribute_sum(artists): - return {count_attribute: reduce(lambda x,y:(x + int(eval(y.findtext(count_attribute)))), artists, 0)} + return {count_attribute: reduce( + lambda x, y:(x + int(eval(y.findtext(count_attribute)))), artists, 0 + )} return WeeklyArtistChart( - subject = subject, - start = datetime.utcfromtimestamp(int(data.attrib['from'])), - end = datetime.utcfromtimestamp(int(data.attrib['to'])), - stats = Stats( - subject = subject, - **get_count_attribute_sum(data.findall('artist')) - ), - artists = [ - Artist( - api, - subject = w, - name = a.findtext('name'), - mbid = a.findtext('mbid'), - stats = Stats( - subject = a.findtext('name'), - rank = int(a.attrib['rank']), - **get_count_attribute(a) - ), - url = a.findtext('url'), - ) - for a in data.findall('artist') - ] - ) + subject = subject, + start = datetime.utcfromtimestamp(int(data.attrib['from'])), + end = datetime.utcfromtimestamp(int(data.attrib['to'])), + stats = Stats( + subject = subject, + **get_count_attribute_sum(data.findall('artist')) + ), + artists = [ + Artist( + api, + subject = w, + name = a.findtext('name'), + mbid = a.findtext('mbid'), + stats = Stats( + subject = a.findtext('name'), + rank = int(a.attrib['rank']), + **get_count_attribute(a) + ), + url = a.findtext('url'), + ) + for a in data.findall('artist') + ] + ) class WeeklyTrackChart(WeeklyChart): """A class for representing the weekly track charts""" @@ -216,45 +218,45 @@ class WeeklyTrackChart(WeeklyChart): @staticmethod def create_from_data(api, subject, data): w = WeeklyChart( - subject = subject, - start = datetime.utcfromtimestamp(int(data.attrib['from'])), - end = datetime.utcfromtimestamp(int(data.attrib['to'])), - ) + subject = subject, + start = datetime.utcfromtimestamp(int(data.attrib['from'])), + end = datetime.utcfromtimestamp(int(data.attrib['to'])), + ) return WeeklyTrackChart( - subject = subject, - start = datetime.utcfromtimestamp(int(data.attrib['from'])), - end = datetime.utcfromtimestamp(int(data.attrib['to'])), - stats = Stats( - subject = subject, - playcount = reduce( - lambda x,y:( - x + int(y.findtext('playcount')) - ), - data.findall('track'), - 0 - ) - ), - tracks = [ - Track( - api, - subject = w, - name = t.findtext('name'), - mbid = t.findtext('mbid'), - artist = Artist( - api, - name = t.findtext('artist'), - mbid = t.find('artist').attrib['mbid'], - ), - stats = Stats( - subject = t.findtext('name'), - rank = int(t.attrib['rank']), - playcount = int(t.findtext('playcount')), - ), - url = t.findtext('url'), - ) - for t in data.findall('track') - ] - ) + subject = subject, + start = datetime.utcfromtimestamp(int(data.attrib['from'])), + end = datetime.utcfromtimestamp(int(data.attrib['to'])), + stats = Stats( + subject = subject, + playcount = reduce( + lambda x,y:( + x + int(y.findtext('playcount')) + ), + data.findall('track'), + 0 + ) + ), + tracks = [ + Track( + api, + subject = w, + name = t.findtext('name'), + mbid = t.findtext('mbid'), + artist = Artist( + api, + name = t.findtext('artist'), + mbid = t.find('artist').attrib['mbid'], + ), + stats = Stats( + subject = t.findtext('name'), + rank = int(t.attrib['rank']), + playcount = int(t.findtext('playcount')), + ), + url = t.findtext('url'), + ) + for t in data.findall('track') + ] + ) class WeeklyTagChart(WeeklyChart): """A class for representing the weekly tag charts"""