added async_callback decorator for supporting asynchronous calls. applied it to get_* methods in Api class.
parent
d1656c1a84
commit
238d88f7e8
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
@ -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"""
|
||||||
|
|
Loading…
Reference in New Issue