* added decorator package to setup dependency

* used 'decorator' decorator on the decorators in the decorators module to avoid function signature mangling
* added 'callback' argument and related documentation in the asynchronous methods in api module
master
Abhinav Sarkar 14 years ago
parent 238d88f7e8
commit bbdbfcebb6
  1. 150
      lastfm/api.py
  2. 160
      lastfm/decorators.py
  3. 24
      setup.py

@ -173,7 +173,8 @@ class Api(object):
def get_album(self,
album = None,
artist = None,
mbid = None):
mbid = None,
callback = None):
"""
Get an album object.
@ -183,6 +184,8 @@ class Api(object):
@type artist: L{str} OR L{Artist}
@param mbid: MBID of the album
@type mbid: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: an Album object corresponding the provided album name
@rtype: L{Album}
@ -192,13 +195,14 @@ class Api(object):
Otherwise exception is raised.
@see: L{Album.get_info}
@see: L{async_callback}
"""
if isinstance(artist, Artist):
artist = artist.name
return Album.get_info(self, artist, album, mbid)
@async_callback
def search_album(self, album, limit = None):
def search_album(self, album, limit = None, callback = None):
"""
Search for an album by name.
@ -206,18 +210,19 @@ class Api(object):
@type album: L{str}
@param limit: maximum number of results returned (optional)
@type limit: L{int}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: matches sorted by relevance
@rtype: L{lazylist} of L{Album}
@see: L{Album.search}
@see: L{async_callback}
"""
return Album.search(self, search_item = album, limit = limit)
@async_callback
def get_artist(self,
artist = None,
mbid = None):
def get_artist(self, artist = None, mbid = None, callback = None):
"""
Get an artist object.
@ -225,6 +230,8 @@ class Api(object):
@type artist: L{str}
@param mbid: MBID of the artist
@type mbid: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: an Artist object corresponding the provided artist name
@rtype: L{Artist}
@ -233,13 +240,12 @@ class Api(object):
to be provided. Otherwise exception is raised.
@see: L{Artist.get_info}
@see: L{async_callback}
"""
return Artist.get_info(self, artist, mbid)
@async_callback
def search_artist(self,
artist,
limit = None):
def search_artist(self, artist, limit = None, callback = None):
"""
Search for an artist by name.
@ -247,21 +253,26 @@ class Api(object):
@type artist: L{str}
@param limit: maximum number of results returned (optional)
@type limit: L{int}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: matches sorted by relevance
@rtype: L{lazylist} of L{Artist}
@see: L{Artist.search}
@see: L{async_callback}
"""
return Artist.search(self, search_item = artist, limit = limit)
@async_callback
def get_event(self, event):
def get_event(self, event, callback = None):
"""
Get an event object.
@param event: the event id
@type event: L{int}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: an event object corresponding to the event id provided
@rtype: L{Event}
@ -269,90 +280,113 @@ class Api(object):
@raise InvalidParametersError: Exception is raised if an invalid event id is supplied.
@see: L{Event.get_info}
@see: L{async_callback}
"""
return Event.get_info(self, event)
@async_callback
def get_location(self, city):
def get_location(self, city, callback = None):
"""
Get a location object.
@param city: the city name
@type city: L{str}
@param city: the city name
@type city: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: a location object corresponding to the city name provided
@rtype: L{Location}
@see: L{async_callback}
"""
return Location(self, city = city)
@async_callback
def get_country(self, name):
def get_country(self, name, callback = None):
"""
Get a country object.
@param name: the country name
@type name: L{str}
@param name: the country name
@type name: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: a country object corresponding to the country name provided
@rtype: L{Country}
@see: L{async_callback}
"""
return Country(self, name = name)
@async_callback
def get_group(self, name):
def get_group(self, name, callback = None):
"""
Get a group object.
@param name: the group name
@type name: L{str}
@param name: the group name
@type name: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: a group object corresponding to the group name provided
@rtype: L{Group}
@see: L{async_callback}
"""
return Group(self, name = name)
@async_callback
def get_playlist(self, url):
def get_playlist(self, url, callback = None):
"""
Get a playlist object.
@param url: lastfm url of the playlist
@type url: L{str}
@param url: lastfm url of the playlist
@type url: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: a playlist object corresponding to the playlist url provided
@rtype: L{Playlist}
@see: L{Playlist.fetch}
@see: L{async_callback}
"""
return Playlist.fetch(self, url)
@async_callback
def get_tag(self, name):
def get_tag(self, name, callback = None):
"""
Get a tag object.
@param name: the tag name
@type name: L{str}
@param name: the tag name
@type name: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: a tag object corresponding to the tag name provided
@rtype: L{Tag}
@see: L{async_callback}
"""
return Tag(self, name = name)
@async_callback
def get_global_top_tags(self):
def get_global_top_tags(self, callback = None):
"""
Get the top global tags on Last.fm, sorted by popularity (number of times used).
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: a list of top global tags
@rtype: L{list} of L{Tag}
@see: L{async_callback}
"""
return Tag.get_top_tags(self)
@async_callback
def search_tag(self,
tag,
limit = None):
def search_tag(self, tag, limit = None, callback = None):
"""
Search for a tag by name.
@ -360,11 +394,14 @@ class Api(object):
@type tag: L{str}
@param limit: maximum number of results returned (optional)
@type limit: L{int}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: matches sorted by relevance
@rtype: L{lazylist} of L{Tag}
@see: L{Tag.search}
@see: L{async_callback}
"""
return Tag.search(self, search_item = tag, limit = limit)
@ -372,7 +409,8 @@ class Api(object):
def compare_taste(self,
type1, type2,
value1, value2,
limit = None):
limit = None,
callback = None):
"""
Get a Tasteometer score from two inputs, along with a list of
shared artists. If the input is a User or a Myspace URL, some
@ -388,16 +426,23 @@ class Api(object):
@type value2: L{str}
@param limit: maximum number of results returned (optional)
@type limit: L{int}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: the taste-o-meter score for the inputs
@rtype: L{Tasteometer}
@see: L{Tasteometer.compare}
@see: L{async_callback}
"""
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,
callback = None):
"""
Get a track object.
@ -407,6 +452,8 @@ class Api(object):
@type artist: L{str} OR L{Artist}
@param mbid: MBID of the track
@type mbid: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: a track object corresponding to the track name provided
@rtype: L{Track}
@ -415,13 +462,18 @@ class Api(object):
to be provided. Otherwise exception is raised.
@see: L{Track.get_info}
@see: L{async_callback}
"""
if isinstance(artist, Artist):
artist = artist.name
return Track.get_info(self, artist, track, mbid)
@async_callback
def search_track(self, track, artist = None, limit = None):
def search_track(self,
track,
artist = None,
limit = None,
callback = None):
"""
Search for a track by name.
@ -431,23 +483,28 @@ class Api(object):
@type artist: L{str} OR L{Artist}
@param limit: maximum number of results returned (optional)
@type limit: L{int}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: matches sorted by relevance
@rtype: L{lazylist} of L{Track}
@see: L{Track.search}
@see: L{async_callback}
"""
if isinstance(artist, Artist):
artist = artist.name
return Track.search(self, search_item = track, limit = limit, artist = artist)
@async_callback
def get_user(self, name):
def get_user(self, name, callback = None):
"""
Get an user object.
@param name: the last.fm user name
@type name: L{str}
@param name: the last.fm user name
@type name: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: an user object corresponding to the user name provided
@rtype: L{User}
@ -455,18 +512,23 @@ class Api(object):
@raise InvalidParametersError: Exception is raised if an invalid user name is supplied.
@see: L{User.get_info}
@see: L{async_callback}
"""
return User.get_info(self, name = name)
@async_callback
def get_authenticated_user(self):
def get_authenticated_user(self, callback = None):
"""
Get the currently authenticated user.
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: The currently authenticated user if the session is authenticated
@rtype: L{User}
@see: L{User.get_authenticated_user}
@see: L{async_callback}
"""
if self.session_key is not None:
return User.get_authenticated_user(self)
@ -474,12 +536,14 @@ class Api(object):
raise AuthenticationFailedError("session key must be present to call this method")
@async_callback
def get_venue(self, venue):
def get_venue(self, venue, callback = None):
"""
Get a venue object.
@param venue: the venue name
@type venue: L{str}
@param venue: the venue name
@type venue: L{str}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: a venue object corresponding to the venue name provided
@rtype: L{Venue}
@ -487,6 +551,7 @@ class Api(object):
@raise InvalidParametersError: Exception is raised if an non-existant venue name is supplied.
@see: L{search_venue}
@see: L{async_callback}
"""
try:
return self.search_venue(venue)[0]
@ -494,7 +559,11 @@ class Api(object):
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,
callback = None):
"""
Search for a venue by name.
@ -505,11 +574,14 @@ class Api(object):
@type country: L{str}
@param limit: maximum number of results returned (optional)
@type limit: L{int}
@param callback: callback function for asynchronous invocation (optional)
@type callback: C{function}
@return: matches sorted by relevance
@rtype: L{lazylist} of L{Venue}
@see: L{Venue.search}
@see: L{async_callback}
"""
return Venue.search(self, search_item = venue, limit = limit, country = country)

@ -6,6 +6,8 @@ __version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from decorator import decorator
def top_property(list_property_name):
"""
A decorator to return a property that returns the first value of list
@ -57,7 +59,8 @@ def cached_property(func):
return property(fget = wrapper, doc = func.__doc__)
def authenticate(func):
@decorator
def authenticate(func, *args, **kwargs):
"""
A decorator to check if the current user is authenticated or not. Used only
on the functions that need authentication. If not authenticated then an
@ -72,34 +75,33 @@ def authenticate(func):
@raise AuthenticationFailedError: If the user is not authenticated, then an
exception is raised.
"""
def wrapper(self, *args, **kwargs):
from lastfm.user import User, Api
username = None
if isinstance(self, User):
username = self.name
if self.authenticated:
return func(self, *args, **kwargs)
elif hasattr(self, 'user'):
username = self.user.name
if self.user.authenticated:
return func(self, *args, **kwargs)
elif hasattr(self, '_subject') and isinstance(self._subject, User):
username = self._subject.name
if self._subject.authenticated:
return func(self, *args, **kwargs)
elif hasattr(self, '_api') and isinstance(self._api, Api):
try:
user = self._api.get_authenticated_user()
username = user.name
return func(self, *args, **kwargs)
except AuthenticationFailedError:
pass
raise AuthenticationFailedError(
"user '%s' does not have permissions to access the service" % username)
wrapper.__doc__ = func.__doc__
return wrapper
self = args[0]
from lastfm.user import User, Api
username = None
if isinstance(self, User):
username = self.name
if self.authenticated:
return func(self, *args, **kwargs)
elif hasattr(self, 'user'):
username = self.user.name
if self.user.authenticated:
return func(self, *args, **kwargs)
elif hasattr(self, '_subject') and isinstance(self._subject, User):
username = self._subject.name
if self._subject.authenticated:
return func(self, *args, **kwargs)
elif hasattr(self, '_api') and isinstance(self._api, Api):
try:
user = self._api.get_authenticated_user()
username = user.name
return func(self, *args, **kwargs)
except AuthenticationFailedError:
pass
raise AuthenticationFailedError(
"user '%s' does not have permissions to access the service" % username)
def depaginate(func):
@decorator
def depaginate(func, *args, **kwargs):
"""
A decorator to depaginate the search results.
@ -111,24 +113,22 @@ def depaginate(func):
@rtype: C{function}
"""
from lastfm.lazylist import lazylist
def wrapper(*args, **kwargs):
@lazylist
def generator(lst):
@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)
total_pages = gen.next()
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
def async_callback(func):
return generator()
@decorator
def async_callback(func, *args, **kwargs):
"""
A decorator to convert a synchronous (blocking) function into
an asynchronous (non-blocking) function.
@ -154,56 +154,30 @@ def async_callback(func):
@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
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(*args, **kwargs)
except Exception, e:
result = e
callback(result)
thread = Thread(target = async_call)
thread.start()
return
return func(*args, **kwargs)
import copy
from lastfm.error import LastfmError, AuthenticationFailedError

@ -22,7 +22,7 @@ located at http://ws.audioscrobbler.com/2.0/ .""",
)
SETUPTOOLS_METADATA = dict(
install_requires = ['setuptools'],
install_requires = ['setuptools', 'decorator'],
include_package_data = True,
tests_require = ['wsgi_intercept'],
classifiers = [
@ -40,18 +40,18 @@ SETUPTOOLS_METADATA = dict(
import sys
if sys.version < '2.5':
SETUPTOOLS_METADATA['install_requires'].append('ElementTree')
SETUPTOOLS_METADATA['install_requires'].append('cElementTree')
SETUPTOOLS_METADATA['install_requires'].append('ElementTree')
SETUPTOOLS_METADATA['install_requires'].append('cElementTree')
def Main():
# Use setuptools if available, otherwise fallback and use distutils
try:
import setuptools
METADATA.update(SETUPTOOLS_METADATA)
setuptools.setup(**METADATA)
except ImportError:
import distutils.core
distutils.core.setup(**METADATA)
# Use setuptools if available, otherwise fallback and use distutils
try:
import setuptools
METADATA.update(SETUPTOOLS_METADATA)
setuptools.setup(**METADATA)
except ImportError:
import distutils.core
distutils.core.setup(**METADATA)
if __name__ == '__main__':
Main()
Main()

Loading…
Cancel
Save