added async_callback decorator for supporting asynchronous calls. applied it to get_* methods in Api class.

master
Abhinav Sarkar 2009-03-21 06:53:24 +00:00
parent d1656c1a84
commit 238d88f7e8
3 changed files with 212 additions and 107 deletions

View File

@ -6,7 +6,7 @@ __version__ = "0.2"
__license__ = "GNU Lesser General Public License" __license__ = "GNU Lesser General Public License"
__package__ = "lastfm" __package__ = "lastfm"
from lastfm.decorators import cached_property from lastfm.decorators import cached_property, async_callback
class Api(object): class Api(object):
"""The class representing the last.fm web services API.""" """The class representing the last.fm web services API."""
@ -169,6 +169,7 @@ class Api(object):
""" """
self._request_headers['User-Agent'] = user_agent self._request_headers['User-Agent'] = user_agent
@async_callback
def get_album(self, def get_album(self,
album = None, album = None,
artist = None, artist = None,
@ -196,6 +197,7 @@ class Api(object):
artist = artist.name artist = artist.name
return Album.get_info(self, artist, album, mbid) return Album.get_info(self, artist, album, mbid)
@async_callback
def search_album(self, album, limit = None): def search_album(self, album, limit = None):
""" """
Search for an album by name. Search for an album by name.
@ -212,6 +214,7 @@ class Api(object):
""" """
return Album.search(self, search_item = album, limit = limit) return Album.search(self, search_item = album, limit = limit)
@async_callback
def get_artist(self, def get_artist(self,
artist = None, artist = None,
mbid = None): mbid = None):
@ -232,7 +235,8 @@ class Api(object):
@see: L{Artist.get_info} @see: L{Artist.get_info}
""" """
return Artist.get_info(self, artist, mbid) return Artist.get_info(self, artist, mbid)
@async_callback
def search_artist(self, def search_artist(self,
artist, artist,
limit = None): limit = None):
@ -251,6 +255,7 @@ class Api(object):
""" """
return Artist.search(self, search_item = artist, limit = limit) return Artist.search(self, search_item = artist, limit = limit)
@async_callback
def get_event(self, event): def get_event(self, event):
""" """
Get an event object. Get an event object.
@ -266,7 +271,8 @@ class Api(object):
@see: L{Event.get_info} @see: L{Event.get_info}
""" """
return Event.get_info(self, event) return Event.get_info(self, event)
@async_callback
def get_location(self, city): def get_location(self, city):
""" """
Get a location object. Get a location object.
@ -279,6 +285,7 @@ class Api(object):
""" """
return Location(self, city = city) return Location(self, city = city)
@async_callback
def get_country(self, name): def get_country(self, name):
""" """
Get a country object. Get a country object.
@ -290,7 +297,8 @@ class Api(object):
@rtype: L{Country} @rtype: L{Country}
""" """
return Country(self, name = name) return Country(self, name = name)
@async_callback
def get_group(self, name): def get_group(self, name):
""" """
Get a group object. Get a group object.
@ -303,6 +311,7 @@ class Api(object):
""" """
return Group(self, name = name) return Group(self, name = name)
@async_callback
def get_playlist(self, url): def get_playlist(self, url):
""" """
Get a playlist object. Get a playlist object.
@ -316,7 +325,8 @@ class Api(object):
@see: L{Playlist.fetch} @see: L{Playlist.fetch}
""" """
return Playlist.fetch(self, url) return Playlist.fetch(self, url)
@async_callback
def get_tag(self, name): def get_tag(self, name):
""" """
Get a tag object. Get a tag object.
@ -329,6 +339,7 @@ class Api(object):
""" """
return Tag(self, name = name) return Tag(self, name = name)
@async_callback
def get_global_top_tags(self): def get_global_top_tags(self):
""" """
Get the top global tags on Last.fm, sorted by popularity (number of times used). 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) return Tag.get_top_tags(self)
@async_callback
def search_tag(self, def search_tag(self,
tag, tag,
limit = None): limit = None):
@ -356,6 +368,7 @@ class Api(object):
""" """
return Tag.search(self, search_item = tag, limit = limit) return Tag.search(self, search_item = tag, limit = limit)
@async_callback
def compare_taste(self, def compare_taste(self,
type1, type2, type1, type2,
value1, value2, value1, value2,
@ -383,6 +396,7 @@ class Api(object):
""" """
return Tasteometer.compare(self, type1, type2, value1, value2, limit) return Tasteometer.compare(self, type1, type2, value1, value2, limit)
@async_callback
def get_track(self, track, artist = None, mbid = None): def get_track(self, track, artist = None, mbid = None):
""" """
Get a track object. Get a track object.
@ -405,11 +419,9 @@ class Api(object):
if isinstance(artist, Artist): if isinstance(artist, Artist):
artist = artist.name artist = artist.name
return Track.get_info(self, artist, track, mbid) return Track.get_info(self, artist, track, mbid)
def search_track(self, @async_callback
track, def search_track(self, track, artist = None, limit = None):
artist = None,
limit = None):
""" """
Search for a track by name. Search for a track by name.
@ -429,6 +441,7 @@ class Api(object):
artist = artist.name artist = artist.name
return Track.search(self, search_item = track, limit = limit, artist = artist) return Track.search(self, search_item = track, limit = limit, artist = artist)
@async_callback
def get_user(self, name): def get_user(self, name):
""" """
Get an user object. Get an user object.
@ -445,6 +458,7 @@ class Api(object):
""" """
return User.get_info(self, name = name) return User.get_info(self, name = name)
@async_callback
def get_authenticated_user(self): def get_authenticated_user(self):
""" """
Get the currently authenticated user. Get the currently authenticated user.
@ -459,6 +473,7 @@ class Api(object):
else: else:
raise AuthenticationFailedError("session key must be present to call this method") raise AuthenticationFailedError("session key must be present to call this method")
@async_callback
def get_venue(self, venue): def get_venue(self, venue):
""" """
Get a venue object. Get a venue object.
@ -478,6 +493,7 @@ class Api(object):
except IndexError: except IndexError:
raise InvalidParametersError("No such venue exists") raise InvalidParametersError("No such venue exists")
@async_callback
def search_venue(self, venue, limit = None, country = None): def search_venue(self, venue, limit = None, country = None):
""" """
Search for a venue by name. Search for a venue by name.

View File

@ -100,6 +100,16 @@ def authenticate(func):
return wrapper return wrapper
def depaginate(func): 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 from lastfm.lazylist import lazylist
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
@lazylist @lazylist
@ -118,5 +128,82 @@ def depaginate(func):
wrapper.__doc__ = func.__doc__ wrapper.__doc__ = func.__doc__
return wrapper 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 import copy
from lastfm.error import LastfmError, AuthenticationFailedError from lastfm.error import LastfmError, AuthenticationFailedError

View File

@ -119,41 +119,41 @@ class WeeklyAlbumChart(WeeklyChart):
end = datetime.utcfromtimestamp(int(data.attrib['to'])), end = datetime.utcfromtimestamp(int(data.attrib['to'])),
) )
return WeeklyAlbumChart( return WeeklyAlbumChart(
subject = subject, subject = subject,
start = datetime.utcfromtimestamp(int(data.attrib['from'])), start = datetime.utcfromtimestamp(int(data.attrib['from'])),
end = datetime.utcfromtimestamp(int(data.attrib['to'])), end = datetime.utcfromtimestamp(int(data.attrib['to'])),
stats = Stats( stats = Stats(
subject = subject, subject = subject,
playcount = reduce( playcount = reduce(
lambda x,y:( lambda x,y:(
x + int(y.findtext('playcount')) x + int(y.findtext('playcount'))
), ),
data.findall('album'), data.findall('album'),
0 0
) )
), ),
albums = [ albums = [
Album( Album(
api, api,
subject = w, subject = w,
name = a.findtext('name'), name = a.findtext('name'),
mbid = a.findtext('mbid'), mbid = a.findtext('mbid'),
artist = Artist( artist = Artist(
api, api,
subject = w, subject = w,
name = a.findtext('artist'), name = a.findtext('artist'),
mbid = a.find('artist').attrib['mbid'], mbid = a.find('artist').attrib['mbid'],
), ),
stats = Stats( stats = Stats(
subject = a.findtext('name'), subject = a.findtext('name'),
rank = int(a.attrib['rank']), rank = int(a.attrib['rank']),
playcount = int(a.findtext('playcount')), playcount = int(a.findtext('playcount')),
), ),
url = a.findtext('url'), url = a.findtext('url'),
) )
for a in data.findall('album') for a in data.findall('album')
] ]
) )
class WeeklyArtistChart(WeeklyChart): class WeeklyArtistChart(WeeklyChart):
"""A class for representing the weekly artist charts""" """A class for representing the weekly artist charts"""
@ -176,32 +176,34 @@ class WeeklyArtistChart(WeeklyChart):
def get_count_attribute(artist): def get_count_attribute(artist):
return {count_attribute: int(eval(artist.findtext(count_attribute)))} return {count_attribute: int(eval(artist.findtext(count_attribute)))}
def get_count_attribute_sum(artists): 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( return WeeklyArtistChart(
subject = subject, subject = subject,
start = datetime.utcfromtimestamp(int(data.attrib['from'])), start = datetime.utcfromtimestamp(int(data.attrib['from'])),
end = datetime.utcfromtimestamp(int(data.attrib['to'])), end = datetime.utcfromtimestamp(int(data.attrib['to'])),
stats = Stats( stats = Stats(
subject = subject, subject = subject,
**get_count_attribute_sum(data.findall('artist')) **get_count_attribute_sum(data.findall('artist'))
), ),
artists = [ artists = [
Artist( Artist(
api, api,
subject = w, subject = w,
name = a.findtext('name'), name = a.findtext('name'),
mbid = a.findtext('mbid'), mbid = a.findtext('mbid'),
stats = Stats( stats = Stats(
subject = a.findtext('name'), subject = a.findtext('name'),
rank = int(a.attrib['rank']), rank = int(a.attrib['rank']),
**get_count_attribute(a) **get_count_attribute(a)
), ),
url = a.findtext('url'), url = a.findtext('url'),
) )
for a in data.findall('artist') for a in data.findall('artist')
] ]
) )
class WeeklyTrackChart(WeeklyChart): class WeeklyTrackChart(WeeklyChart):
"""A class for representing the weekly track charts""" """A class for representing the weekly track charts"""
@ -216,45 +218,45 @@ class WeeklyTrackChart(WeeklyChart):
@staticmethod @staticmethod
def create_from_data(api, subject, data): def create_from_data(api, subject, data):
w = WeeklyChart( w = WeeklyChart(
subject = subject, subject = subject,
start = datetime.utcfromtimestamp(int(data.attrib['from'])), start = datetime.utcfromtimestamp(int(data.attrib['from'])),
end = datetime.utcfromtimestamp(int(data.attrib['to'])), end = datetime.utcfromtimestamp(int(data.attrib['to'])),
) )
return WeeklyTrackChart( return WeeklyTrackChart(
subject = subject, subject = subject,
start = datetime.utcfromtimestamp(int(data.attrib['from'])), start = datetime.utcfromtimestamp(int(data.attrib['from'])),
end = datetime.utcfromtimestamp(int(data.attrib['to'])), end = datetime.utcfromtimestamp(int(data.attrib['to'])),
stats = Stats( stats = Stats(
subject = subject, subject = subject,
playcount = reduce( playcount = reduce(
lambda x,y:( lambda x,y:(
x + int(y.findtext('playcount')) x + int(y.findtext('playcount'))
), ),
data.findall('track'), data.findall('track'),
0 0
) )
), ),
tracks = [ tracks = [
Track( Track(
api, api,
subject = w, subject = w,
name = t.findtext('name'), name = t.findtext('name'),
mbid = t.findtext('mbid'), mbid = t.findtext('mbid'),
artist = Artist( artist = Artist(
api, api,
name = t.findtext('artist'), name = t.findtext('artist'),
mbid = t.find('artist').attrib['mbid'], mbid = t.find('artist').attrib['mbid'],
), ),
stats = Stats( stats = Stats(
subject = t.findtext('name'), subject = t.findtext('name'),
rank = int(t.attrib['rank']), rank = int(t.attrib['rank']),
playcount = int(t.findtext('playcount')), playcount = int(t.findtext('playcount')),
), ),
url = t.findtext('url'), url = t.findtext('url'),
) )
for t in data.findall('track') for t in data.findall('track')
] ]
) )
class WeeklyTagChart(WeeklyChart): class WeeklyTagChart(WeeklyChart):
"""A class for representing the weekly tag charts""" """A class for representing the weekly tag charts"""