added documentation for error, event, geo and group modules

master
Abhinav Sarkar 2009-03-17 15:21:40 +00:00
parent 636e9073ec
commit 2645e4565c
30 changed files with 977 additions and 142 deletions

View File

@ -1,9 +1,15 @@
#!/usr/bin/env python
"""A python interface to the last.fm web services API"""
"""
A python interface to the last.fm web services API at
U{http://ws.audioscrobbler.com/2.0}.
See U{the official documentation<http://www.last.fm/api/intro>}
of the web service API methods for more information.
"""
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
import sys
import os

View File

@ -4,6 +4,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Searchable, Taggable
@ -213,7 +214,7 @@ class Album(LastfmBase, Cacheable, Searchable, Taggable):
@param mbid: MBID of the album
@type mbid: L{str}
@return: an Album object corresponding the provided album name
@return: an Album object corresponding to the provided album name
@rtype: L{Album}
@raise lastfm.InvalidParametersError: Either album and artist parameters or

View File

@ -4,6 +4,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.decorators import cached_property

View File

@ -4,6 +4,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Searchable, Sharable, Shoutable, Taggable

View File

@ -4,6 +4,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
class LastfmBase(object):
"""Base class for all the classes in this package"""

View File

@ -1,69 +1,102 @@
#!/usr/bin/env python
"""Mdoule containing the exceptions for this package"""
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
class LastfmError(Exception):
"""Base class for Lastfm errors"""
def __init__(self,
message = None,
code = None):
"""Base class for Lastfm web service API errors"""
def __init__(self, message = None, code = None):
"""
Initialize the error object.
@param message: the error message
@type message: L{str}
@param code: the error code
@type code: L{int}
"""
super(LastfmError, self).__init__()
self._code = code
self._message = message
@property
def code(self):
"""
The error code as returned by last.fm web service API.
@rtype: L{int}
"""
return self._code
@property
def message(self):
"""
The error message as returned by last.fm web service API.
@rtype: L{str}
"""
return self._message
def __str__(self):
return "%s" % self.message
class InvalidServiceError(LastfmError):#2
"""Invalid service - This service does not exist."""
pass
class InvalidMethodError(LastfmError):#3
"""Invalid method - No method with that name in this package."""
pass
class AuthenticationFailedError(LastfmError):#4
"""Authentication failed - You do not have permissions to access the service"""
pass
class InvalidFormatError(LastfmError):#5
"""Invalid format - This service doesn't exist in that format"""
pass
class InvalidParametersError(LastfmError):#6
"""Invalid parameters - Your request is missing a required parameter"""
pass
class InvalidResourceError(LastfmError):#7
"""Invalid resource - Invalid resource specified"""
pass
class OperationFailedError(LastfmError):#8
"""
Operation failed - There was an error during the requested operation.
lease try again later.
"""
pass
class InvalidSessionKeyError(LastfmError):#9
"""Invalid session key - Please re-authenticate"""
pass
class InvalidApiKeyError(LastfmError):#10
"""Invalid API key - You must be granted a valid key by last.fm"""
pass
class ServiceOfflineError(LastfmError):#11
"""Service offline - This service is temporarily offline. Try again later."""
pass
class SubscribersOnlyError(LastfmError):#12
"""Subscribers only - This service is only available to paid last.fm subscribers"""
pass
class InvalidMethodSignatureError(LastfmError):#13
"""Invalid method signature - the method signature provided is invalid"""
pass
class TokenNotAuthorizedError(LastfmError):#14
"""Token not authorized - This token has not been authorized"""
pass
class TokenExpiredError(LastfmError):#15
"""Token expired - This token has expired"""
pass
error_map = {
@ -82,4 +115,5 @@ error_map = {
13: InvalidMethodSignatureError,
14: TokenNotAuthorizedError,
15: TokenExpiredError,
}
}
"""Map of error codes to the error types"""

View File

@ -1,8 +1,10 @@
#!/usr/bin/env python
"""Module for calling Event related last.fm web services API methods"""
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Sharable, Shoutable
@ -26,7 +28,35 @@ class Event(LastfmBase, Cacheable, Sharable, Shoutable):
url = None,
stats = None,
tag = None,
subject = None):
**kwargs):
"""
Create an Event object by providing all the data related to it.
@param api: an instance of L{Api}
@type api: L{Api}
@param id: ID of the event
@type id: L{int}
@param title: title of the event
@type title: L{str}
@param artists: artists performing in the event
@type artists: L{list} of L{Artist}
@param headliner: headliner artist of the event
@type headliner: L{Artist}
@param venue: venue of the event
@type venue: L{Venue}
@param start_date: start date and time of the event
@type start_date: C{datetime.datetime}
@param description: description of the event
@type description: L{str}
@param image: poster images of the event in various sizes
@type image: L{dict}
@param url: URL of the event on last.fm
@type url: L{str}
@param stats: the statistics of the event (attendance and no. of reviews)
@type stats: L{Stats}
@param tag: tag for the event
@type tag: L{str}
"""
if not isinstance(api, Api):
raise InvalidParametersError("api reference must be supplied as an argument")
Sharable.init(self, api)
@ -48,64 +78,106 @@ class Event(LastfmBase, Cacheable, Sharable, Shoutable):
reviews = stats.reviews
)
self._tag = tag
self._subject = subject
@property
def id(self):
"""id of the event"""
"""
id of the event
@rtype: L{int}
"""
return self._id
@property
def title(self):
"""title of the event"""
"""
title of the event
@rtype: L{str}
"""
return self._title
@property
def artists(self):
"""artists performing in the event"""
"""
artists performing in the event
@rtype: L{list} of L{Artist}
"""
return self._artists
@property
def headliner(self):
"""headliner artist of the event"""
"""
headliner artist of the event
@rtype: L{Artist}
"""
return self._headliner
@property
def venue(self):
"""venue of the event"""
"""
venue of the event
@rtype: L{Venue}
"""
return self._venue
@property
def start_date(self):
"""start date of the event"""
"""
start date of the event
@rtype: C{datetime.datetime}
"""
return self._start_date
@property
def description(self):
"""description of the event"""
"""
description of the event
@rtype: L{str}
"""
return self._description
@property
def image(self):
"""poster of the event"""
"""
poster of the event
@rtype: L{dict}
"""
return self._image
@property
def url(self):
"""url of the event's page"""
"""
url of the event's page
@rtype: L{str}
"""
return self._url
@property
def stats(self):
"""stats of the event"""
"""
statistics for the event
@rtype: L{Stats}
"""
return self._stats
@property
def tag(self):
"""tags for the event"""
"""
tag for the event
@rtype: L{str}
"""
return self._tag
def attend(self, status = STATUS_ATTENDING):
"""
Set the attendance status of the authenticated user for this event.
@param status: attendance status, should be one of:
L{Event.STATUS_ATTENDING} OR L{Event.STATUS_MAYBE} OR L{Event.STATUS_NOT}
@type status: L{int}
@raise InvalidParametersError: If status parameters is not one of the allowed values
then an exception is raised.
"""
if status not in [Event.STATUS_ATTENDING, Event.STATUS_MAYBE, Event.STATUS_NOT]:
InvalidParametersError("status has to be 0, 1 or 2")
params = self._default_params({'method': 'event.attend', 'status': status})
@ -113,12 +185,38 @@ class Event(LastfmBase, Cacheable, Sharable, Shoutable):
@staticmethod
def get_info(api, event):
"""
Get the data for the event.
@param api: an instance of L{Api}
@type api: L{Api}
@param event: ID of the event
@type event: L{int}
@return: an Event object corresponding to the provided event id
@rtype: L{Event}
@note: Use the L{Api.get_event} method instead of using this method directly.
"""
params = {'method': 'event.getInfo', 'event': event}
data = api._fetch_data(params).find('event')
return Event.create_from_data(api, data)
@staticmethod
def create_from_data(api, data):
"""
Create the Event object from the provided XML element.
@param api: an instance of L{Api}
@type api: L{Api}
@param data: XML element
@type data: C{xml.etree.ElementTree.Element}
@return: an Event object corresponding to the provided XML element
@rtype: L{Event}
@note: Use the L{Api.get_event} method instead of using this method directly.
"""
start_date = None
if data.findtext('startTime') is not None:
@ -150,7 +248,9 @@ class Event(LastfmBase, Cacheable, Sharable, Shoutable):
except ValueError:
pass
latitude = data.findtext('venue/location/{%s}point/{%s}lat' % ((Location.XMLNS,)*2))
longitude = data.findtext('venue/location/{%s}point/{%s}long' % ((Location.XMLNS,)*2))
return Event(
api,
id = int(data.findtext('id')),
@ -161,22 +261,18 @@ class Event(LastfmBase, Cacheable, Sharable, Shoutable):
api,
name = data.findtext('venue/name'),
location = Location(
api,
city = data.findtext('venue/location/city'),
country = Country(
api,
name = data.findtext('venue/location/country')
),
street = data.findtext('venue/location/street'),
postal_code = data.findtext('venue/location/postalcode'),
latitude = float(data.findtext(
'venue/location/{%s}point/{%s}lat' % ((Location.XMLNS,)*2)
)),
longitude = float(data.findtext(
'venue/location/{%s}point/{%s}long' % ((Location.XMLNS,)*2)
)),
#timezone = data.findtext('venue/location/timezone')
),
api,
city = data.findtext('venue/location/city'),
country = Country(
api,
name = data.findtext('venue/location/country')
),
street = data.findtext('venue/location/street'),
postal_code = data.findtext('venue/location/postalcode'),
latitude = (latitude.strip()!= '') and float(latitude) or None,
longitude = (longitude.strip()!= '') and float(longitude) or None,
#timezone = data.findtext('venue/location/timezone')
),
url = data.findtext('venue/url')
),
start_date = start_date,

View File

@ -1,8 +1,10 @@
#!/usr/bin/env python
"""Module for caching the files"""
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
import sys
if sys.version < '2.6':
@ -18,84 +20,83 @@ import os
import tempfile
class _FileCacheError(Exception):
'''Base exception class for FileCache related errors'''
"""Base exception class for FileCache related errors"""
class FileCache(object):
DEPTH = 3
DEPTH = 3
def __init__(self,root_directory=None):
self._InitializeRootDirectory(root_directory)
def __init__(self,root_directory=None):
self._InitializeRootDirectory(root_directory)
def Get(self,key):
path = self._GetPath(key)
if os.path.exists(path):
return open(path).read()
else:
return None
def Get(self,key):
path = self._GetPath(key)
if os.path.exists(path):
return open(path).read()
else:
return None
def Set(self,key,data):
path = self._GetPath(key)
directory = os.path.dirname(path)
if not os.path.exists(directory):
os.makedirs(directory)
if not os.path.isdir(directory):
raise _FileCacheError('%s exists but is not a directory' % directory)
temp_fd, temp_path = tempfile.mkstemp()
temp_fp = os.fdopen(temp_fd, 'w')
temp_fp.write(data)
temp_fp.close()
if not path.startswith(self._root_directory):
raise _FileCacheError('%s does not appear to live under %s' %
(path, self._root_directory))
if os.path.exists(path):
os.remove(path)
os.rename(temp_path, path)
def Set(self,key,data):
path = self._GetPath(key)
directory = os.path.dirname(path)
if not os.path.exists(directory):
os.makedirs(directory)
if not os.path.isdir(directory):
raise _FileCacheError('%s exists but is not a directory' % directory)
temp_fd, temp_path = tempfile.mkstemp()
temp_fp = os.fdopen(temp_fd, 'w')
temp_fp.write(data)
temp_fp.close()
if not path.startswith(self._root_directory):
raise _FileCacheError('%s does not appear to live under %s' %
(path, self._root_directory))
if os.path.exists(path):
os.remove(path)
os.rename(temp_path, path)
def Remove(self,key):
path = self._GetPath(key)
if not path.startswith(self._root_directory):
raise _FileCacheError('%s does not appear to live under %s' %
(path, self._root_directory ))
if os.path.exists(path):
os.remove(path)
def Remove(self,key):
path = self._GetPath(key)
if not path.startswith(self._root_directory):
raise _FileCacheError('%s does not appear to live under %s' %
(path, self._root_directory ))
if os.path.exists(path):
os.remove(path)
def GetCachedTime(self,key):
path = self._GetPath(key)
if os.path.exists(path):
return os.path.getmtime(path)
else:
return None
def GetCachedTime(self,key):
path = self._GetPath(key)
if os.path.exists(path):
return os.path.getmtime(path)
else:
return None
def _GetUsername(self):
'''Attempt to find the username in a cross-platform fashion.'''
return os.getenv('USER') or \
os.getenv('LOGNAME') or \
os.getenv('USERNAME') or \
os.getlogin() or \
'nobody'
def _GetUsername(self):
'''Attempt to find the username in a cross-platform fashion.'''
return os.getenv('USER') or \
os.getenv('LOGNAME') or \
os.getenv('USERNAME') or \
os.getlogin() or \
'nobody'
def _GetTmpCachePath(self):
username = self._GetUsername()
cache_directory = 'python.cache_' + username
return os.path.join(tempfile.gettempdir(), cache_directory)
def _GetTmpCachePath(self):
username = self._GetUsername()
cache_directory = 'python.cache_' + username
return os.path.join(tempfile.gettempdir(), cache_directory)
def _InitializeRootDirectory(self, root_directory):
if not root_directory:
root_directory = self._GetTmpCachePath()
root_directory = os.path.abspath(root_directory)
if not os.path.exists(root_directory):
os.mkdir(root_directory)
if not os.path.isdir(root_directory):
raise _FileCacheError('%s exists but is not a directory' %
root_directory)
self._root_directory = root_directory
def _InitializeRootDirectory(self, root_directory):
if not root_directory:
root_directory = self._GetTmpCachePath()
root_directory = os.path.abspath(root_directory)
if not os.path.exists(root_directory):
os.mkdir(root_directory)
if not os.path.isdir(root_directory):
raise _FileCacheError('%s exists but is not a directory' %
root_directory)
self._root_directory = root_directory
def _GetPath(self,key):
hashed_key = md5hash(key)
return os.path.join(self._root_directory,
self._GetPrefix(hashed_key),
hashed_key)
def _GetPath(self,key):
hashed_key = md5hash(key)
return os.path.join(self._root_directory,
self._GetPrefix(hashed_key),
hashed_key)
def _GetPrefix(self,hashed_key):
return os.path.sep.join(hashed_key[0:FileCache.DEPTH])
def _GetPrefix(self,hashed_key):
return os.path.sep.join(hashed_key[0:FileCache.DEPTH])

View File

@ -1,8 +1,10 @@
#!/usr/bin/env python
"""Module for calling Geo related last.fm web services API methods"""
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable
@ -10,19 +12,46 @@ from lastfm.lazylist import lazylist
from lastfm.decorators import cached_property, top_property
class Geo(object):
"""A class representing an geographic location."""
"""A class representing an geographic location"""
@staticmethod
def get_events(api,
location,
latitude = None,
longitude = None,
distance = None):
"""
Get the events for a location.
@param api: an instance of L{Api}
@type api: L{Api}
@param location: location to retrieve events for (optional)
@type location: L{str}
@param latitude: latitude value to retrieve events for (optional)
@type latitude: L{float}
@param longitude: longitude value to retrieve events for (optional)
@type longitude: L{float}
@param distance: find events within a specified distance (optional)
@type distance: L{float}
@return: events for the location
@rtype: L{lazylist} of L{Event}
@raise InvalidParametersError: Either location or latitude and longitude
has to be provided. Otherwise exception is
raised.
@note: Use L{Location.events} instead of using this method directly.
"""
if reduce(lambda x,y: x and y is None, [location, latitude, longitude], True):
raise InvalidParametersError(
"Either location or latitude and longitude has to be provided")
params = {'method': 'geo.getEvents', 'location': location}
if distance is not None:
params.update({'distance': distance})
if latitude is not None and longitude is not None:
params.update({'latitude': latitude, 'longitude': longitude})
params.update({'lat': latitude, 'long': longitude})
@lazylist
def gen(lst):
@ -46,6 +75,20 @@ class Geo(object):
@staticmethod
def get_top_artists(api, country):
"""
Get the most popular artists on Last.fm by country
@param api: an instance of L{Api}
@type api: L{Api}
@param country: a country name, as defined by
the ISO 3166-1 country names standard
@type country: L{str}
@return: most popular artists of the country
@rtype: L{list} of L{Artist}
@note: Use L{Country.top_artists} instead of using this method directly.
"""
params = {'method': 'geo.getTopArtists', 'country': country}
data = api._fetch_data(params).find('topartists')
return [
@ -66,6 +109,23 @@ class Geo(object):
@staticmethod
def get_top_tracks(api, country, location = None):
"""
Get the most popular tracks on Last.fm by country
@param api: an instance of L{Api}
@type api: L{Api}
@param country: a country name, as defined by
the ISO 3166-1 country names standard
@type country: L{str}
@param location: a metro name, to fetch the charts for
(must be within the country specified) (optional)
@return: most popular tracks of the country
@rtype: L{list} of L{Track}
@note: Use L{Country.top_tracks} and L{Country.get_top_tracks}
instead of using this method directly.
"""
params = {'method': 'geo.getTopTracks', 'country': country}
if location is not None:
params.update({'location': location})
@ -109,6 +169,29 @@ class Location(LastfmBase, Cacheable):
longitude = None,
timezone = None,
**kwargs):
"""
Create a Location object by providing all the data related to it.
@param api: an instance of L{Api}
@type api: L{Api}
@param city: city in which the location is situated
@type city: L{str}
@param country: country in which the location is situated
@type country: L{Country}
@param street: street in which the location is situated
@type street: L{str}
@param postal_code: postal code of the location
@type postal_code: L{str}
@param latitude: latitude of the location
@type latitude: L{float}
@param longitude: longitude of the location
@type longitude: L{float}
@param timezone: timezone in which the location is situated
@type timezone: L{str}
@raise InvalidParametersError: If an instance of L{Api} is not provided as the first
parameter then an Exception is raised.
"""
if not isinstance(api, Api):
raise InvalidParametersError("api reference must be supplied as an argument")
self._api = api
@ -122,53 +205,88 @@ class Location(LastfmBase, Cacheable):
@property
def city(self):
"""city in which the location is situated"""
"""
city in which the location is situated
@rtype: L{str}
"""
return self._city
@property
def country(self):
"""country in which the location is situated"""
"""
country in which the location is situated
@rtype: L{Country}
"""
return self._country
@property
def street(self):
"""street in which the location is situated"""
"""
street in which the location is situated
@rtype: L{str}
"""
return self._street
@property
def postal_code(self):
"""postal code of the location"""
"""
postal code of the location
@rtype: L{str}
"""
return self._postal_code
@property
def latitude(self):
"""latitude of the location"""
"""
latitude of the location
@rtype: L{float}
"""
return self._latitude
@property
def longitude(self):
"""longitude of the location"""
"""
longitude of the location
@rtype: L{float}
"""
return self._longitude
@property
def timezone(self):
"""timezone in which the location is situated"""
"""
timezone in which the location is situated
@rtype: L{str}
"""
return self._timezone
@cached_property
def top_tracks(self):
"""top tracks of the location"""
"""
top tracks for the location
@rtype: L{list} of L{Track}
"""
if self.country is None or self.city is None:
raise InvalidParametersError("country and city of this location are required for calling this method")
return Geo.get_top_tracks(self._api, self.country.name, self.city)
@top_property("top_tracks")
def top_track(self):
"""top track of the location"""
"""
top track for the location
@rtype: L{Track}
"""
pass
def get_events(self,
distance = None):
def get_events(self, distance = None):
"""
Get the events taking place at the location.
@param distance: find events within a specified distance (optional)
@type distance: L{float}
@return: events taking place at the location
@rtype: L{lazylist} of L{Event}
"""
return Geo.get_events(self._api,
self.city,
self.latitude,
@ -177,7 +295,10 @@ class Location(LastfmBase, Cacheable):
@cached_property
def events(self):
"""events taking place at/around the location"""
"""
events taking place at/around the location
@rtype: L{lazylist} of L{Event}
"""
return self.get_events()
@staticmethod
@ -462,10 +583,18 @@ class Country(LastfmBase, Cacheable):
'ZA': 'South Africa',
'ZM': 'Zambia',
'ZW': 'Zimbabwe'}
def init(self,
api,
name = None,
**kwargs):
"""ISO Codes of the countries"""
def init(self, api, name = None, **kwargs):
"""
Create a Country object by providing all the data related to it.
@param api: an instance of L{Api}
@type api: L{Api}
@param name: name of the country
@type name: L{str}
@raise InvalidParametersError: If an instance of L{Api} is not provided as the first
parameter then an Exception is raised.
"""
if not isinstance(api, Api):
raise InvalidParametersError("api reference must be supplied as an argument")
self._api = api
@ -473,35 +602,62 @@ class Country(LastfmBase, Cacheable):
@property
def name(self):
"""name of the country"""
"""
name of the country
@rtype: L{str}
"""
return self._name
@cached_property
def top_artists(self):
"""top artists of the country"""
"""
top artists of the country
@rtype: L{list} of L{Artist}
"""
return Geo.get_top_artists(self._api, self.name)
@top_property("top_artists")
def top_artist(self):
"""top artist of the country"""
"""
top artist of the country
@rtype: L{Artist}
"""
pass
def get_top_tracks(self, location = None):
"""
Get the top tracks for country.
@param location: a metro name, to fetch the charts for
(must be within the country specified) (optional)
@return: most popular tracks of the country
@rtype: L{list} of L{Track}
"""
return Geo.get_top_tracks(self._api, self.name, location)
@cached_property
def top_tracks(self):
"""top tracks of the country"""
"""
top tracks of the country
@rtype: L{list} of L{Track}
"""
return self.get_top_tracks()
@top_property("top_tracks")
def top_track(self):
"""top track of the country"""
"""
top track of the country
@rtype: L{Track}
"""
pass
@cached_property
def events(self):
"""events taking place at/around the location"""
"""
events taking place in the country
@rtype: L{lazylist} of L{Event}
"""
return Geo.get_events(self._api, self.name)
@staticmethod

View File

@ -1,8 +1,10 @@
#!/usr/bin/env python
"""Module for calling Group related last.fm web services API methods"""
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable
@ -11,10 +13,18 @@ from lastfm.decorators import cached_property, top_property
class Group(LastfmBase, Cacheable):
"""A class representing a group on last.fm."""
def init(self,
api,
name = None,
**kwargs):
def init(self, api, name = None, **kwargs):
"""
Create a Group object by providing all the data related to it.
@param api: an instance of L{Api}
@type api: L{Api}
@param name: name of the group on last.fm
@type name: L{str}
@raise InvalidParametersError: If an instance of L{Api} is not provided as the first
parameter then an Exception is raised.
"""
if not isinstance(api, Api):
raise InvalidParametersError("api reference must be supplied as an argument")
self._api = api
@ -22,10 +32,18 @@ class Group(LastfmBase, Cacheable):
@property
def name(self):
"""
name of the group
@rtype: L{str}
"""
return self._name
@cached_property
def weekly_chart_list(self):
"""
a list of available weekly charts for this group
@rtype: L{list} of L{WeeklyChart}
"""
params = self._default_params({'method': 'group.getWeeklyChartList'})
data = self._api._fetch_data(params).find('weeklychartlist')
return [
@ -33,9 +51,24 @@ class Group(LastfmBase, Cacheable):
for c in data.findall('chart')
]
def get_weekly_album_chart(self,
start = None,
end = None):
def get_weekly_album_chart(self, start = None, end = None):
"""
Get an album chart for the group, for a given date range.
If no date range is supplied, it will return the most
recent album chart for the group.
@param start: the date at which the chart should start from (optional)
@type start: C{datetime.datetime}
@param end: the date at which the chart should end on (optional)
@type end: C{datetime.datetime}
@return: an album chart for the group
@rtype: L{WeeklyAlbumChart}
@raise InvalidParametersError: Both start and end parameter have to be either
provided or not provided. Providing only one of
them will raise an exception.
"""
params = self._default_params({'method': 'group.getWeeklyAlbumChart'})
params = WeeklyChart._check_weekly_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklyalbumchart')
@ -43,10 +76,19 @@ class Group(LastfmBase, Cacheable):
@cached_property
def recent_weekly_album_chart(self):
"""
most recent album chart for the group
@rtype: L{WeeklyAlbumChart}
"""
return self.get_weekly_album_chart()
@cached_property
def weekly_album_chart_list(self):
"""
a list of all album charts for this group in reverse-chronological
order. (that means 0th chart is the most recent chart)
@rtype: L{lazylist} of L{WeeklyAlbumChart}
"""
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
@ -58,6 +100,23 @@ class Group(LastfmBase, Cacheable):
def get_weekly_artist_chart(self,
start = None,
end = None):
"""
Get an artist chart for the group, for a given date range.
If no date range is supplied, it will return the most
recent artist chart for the group.
@param start: the date at which the chart should start from (optional)
@type start: C{datetime.datetime}
@param end: the date at which the chart should end on (optional)
@type end: C{datetime.datetime}
@return: an artist chart for the group
@rtype: L{WeeklyArtistChart}
@raise InvalidParametersError: Both start and end parameter have to be either
provided or not provided. Providing only one of
them will raise an exception.
"""
params = self._default_params({'method': 'group.getWeeklyArtistChart'})
params = WeeklyChart._check_weekly_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklyartistchart')
@ -65,10 +124,19 @@ class Group(LastfmBase, Cacheable):
@cached_property
def recent_weekly_artist_chart(self):
"""
most recent artist chart for the group
@rtype: L{WeeklyArtistChart}
"""
return self.get_weekly_artist_chart()
@cached_property
def weekly_artist_chart_list(self):
"""
a list of all artist charts for this group in reverse-chronological
order. (that means 0th chart is the most recent chart)
@rtype: L{lazylist} of L{WeeklyArtistChart}
"""
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
@ -80,6 +148,23 @@ class Group(LastfmBase, Cacheable):
def get_weekly_track_chart(self,
start = None,
end = None):
"""
Get a track chart for the group, for a given date range.
If no date range is supplied, it will return the most
recent artist chart for the group.
@param start: the date at which the chart should start from (optional)
@type start: C{datetime.datetime}
@param end: the date at which the chart should end on (optional)
@type end: C{datetime.datetime}
@return: a track chart for the group
@rtype: L{WeeklyTrackChart}
@raise InvalidParametersError: Both start and end parameter have to be either
provided or not provided. Providing only one of
them will raise an exception.
"""
params = self._default_params({'method': 'group.getWeeklyTrackChart'})
params = WeeklyChart._check_weekly_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklytrackchart')
@ -87,10 +172,19 @@ class Group(LastfmBase, Cacheable):
@cached_property
def recent_weekly_track_chart(self):
"""
most recent track chart for the group
@rtype: L{WeeklyTrackChart}
"""
return self.get_weekly_track_chart()
@cached_property
def weekly_track_chart_list(self):
"""
a list of all track charts for this group in reverse-chronological
order. (that means 0th chart is the most recent chart)
@rtype: L{lazylist} of L{WeeklyTrackChart}
"""
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
@ -101,6 +195,10 @@ class Group(LastfmBase, Cacheable):
@cached_property
def members(self):
"""
members of the group
@rtype: L{lazylist} of L{User}
"""
params = self._default_params({'method': 'group.getMembers'})
@lazylist

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm.mixins"
from lastfm.mixins.cacheable import Cacheable
from lastfm.mixins.searchable import Searchable

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm.mixins"
try:
from threading import Lock

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm.mixins"
from lastfm.lazylist import lazylist

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm.mixins"
from lastfm.decorators import authenticate
@ -10,7 +11,6 @@ class Sharable(object):
def init(self, api):
self._api = api
@authenticate
def share(self, recipient, message = None):
from lastfm.user import User
params = self._default_params({'method': '%s.share' % self.__class__.__name__.lower()})

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm.mixins"
from lastfm.base import LastfmBase
from lastfm.decorators import cached_property, top_property

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm.mixins"
from lastfm.base import LastfmBase
from lastfm.safelist import SafeList

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.album import Album
from lastfm.artist import Artist

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
import sys
class SafeList(object):

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
class Stats(object):
"""A class representing the stats of an artist."""

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Searchable

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
class Tasteometer(object):
"""A class representing a tasteometer."""

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Searchable, Sharable, Taggable

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Shoutable

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Searchable

View File

@ -3,9 +3,11 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable
from operator import xor
class WeeklyChart(LastfmBase, Cacheable):
"""A class for representing the weekly charts"""
@ -43,7 +45,7 @@ class WeeklyChart(LastfmBase, Cacheable):
@staticmethod
def _check_weekly_chart_params(params, start = None, end = None):
if (start is not None and end is None) or (start is None and end is not None):
if xor(start is None, end is None):
raise InvalidParametersError("both start and end have to be provided.")
if start is not None and end is not None:
if isinstance(start, datetime) and isinstance(end, datetime):

View File

@ -3,6 +3,7 @@
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"
class Wiki(object):
"""A class representing the information from the wiki of the subject."""

View File

@ -0,0 +1,422 @@
<?xml version="1.0" encoding="utf-8"?>
<lfm status="ok">
<events location="Tokyo, Japan" page="1" totalpages="37" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" total="367">
<event>
<id>920495</id>
<title>The Birthday</title>
<artists>
<artist>The Birthday</artist>
<headliner>The Birthday</headliner>
</artists>
<venue>
<name>大分 T.O.P.S</name>
<location>
<city>Japan</city>
<country>Japan</country>
<street>Oita</street>
<postalcode></postalcode>
<geo:point>
<geo:lat>35.7108378353001</geo:lat>
<geo:long>139.751586914062</geo:long>
</geo:point>
<timezone>JST</timezone>
</location>
<url>http://www.last.fm/venue/8941482</url>
</venue>
<startDate>Tue, 17 Mar 2009</startDate>
<description></description>
<image size="small">http://userserve-ak.last.fm/serve/34/557848.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/557848.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/557848.jpg</image>
<attendance>1</attendance>
<reviews>0</reviews>
<tag>lastfm:event=920495</tag>
<url>http://www.last.fm/event/920495</url>
</event>
<event>
<id>929525</id>
<title>Lewis Furey</title>
<artists>
<artist>Lewis Furey</artist>
<headliner>Lewis Furey</headliner>
</artists>
<venue>
<name>高田馬場</name>
<location>
<city>tokyo</city>
<country>Japan</country>
<street></street>
<postalcode></postalcode>
<geo:point>
<geo:lat>35.7108378353001</geo:lat>
<geo:long>139.751586914062</geo:long>
</geo:point>
<timezone>JST</timezone>
</location>
<url>http://www.last.fm/venue/9003318</url>
</venue>
<startDate>Tue, 17 Mar 2009</startDate>
<description></description>
<image size="small">http://userserve-ak.last.fm/serve/34/23760119.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/23760119.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/23760119.jpg</image>
<attendance>0</attendance>
<reviews>0</reviews>
<tag>lastfm:event=929525</tag>
<url>http://www.last.fm/event/929525</url>
</event>
<event>
<id>828211</id>
<title>Duffy</title>
<artists>
<artist>Duffy</artist>
<headliner>Duffy</headliner>
</artists>
<venue>
<name>SHIBUYA-AX</name>
<location>
<city>Tōkyō</city>
<country>Japan</country>
<street>渋谷区神南2-1-1</street>
<postalcode>150-0041</postalcode>
<geo:point>
<geo:lat>35.685</geo:lat>
<geo:long>139.7513889</geo:long>
</geo:point>
<timezone>JST</timezone>
</location>
<url>http://www.last.fm/venue/8788725</url>
</venue>
<startDate>Tue, 17 Mar 2009</startDate>
<startTime>19:00</startTime>
<description></description>
<image size="small">http://userserve-ak.last.fm/serve/34/4893704.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/4893704.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/4893704.jpg</image>
<attendance>8</attendance>
<reviews>0</reviews>
<tag>lastfm:event=828211</tag>
<url>http://www.last.fm/event/828211</url>
</event>
<event>
<id>876654</id>
<title>Mos Def</title>
<artists>
<artist>Mos Def</artist>
<headliner>Mos Def</headliner>
</artists>
<venue>
<name>Billboard Live</name>
<location>
<city>Tokyo</city>
<country>Japan</country>
<street>Tokyo Midtown Garden Terrace 4F, 7-4 Akasaka 9-chome, Minato-ku</street>
<postalcode>107-0052</postalcode>
<geo:point>
<geo:lat>35.7108378353001</geo:lat>
<geo:long>139.751586914062</geo:long>
</geo:point>
<timezone>JST</timezone>
</location>
<url>http://www.last.fm/venue/8830206</url>
</venue>
<startDate>Tue, 17 Mar 2009</startDate>
<startTime>19:00</startTime>
<description></description>
<image size="small">http://userserve-ak.last.fm/serve/34/4114471.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/4114471.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/4114471.jpg</image>
<attendance>4</attendance>
<reviews>0</reviews>
<tag>lastfm:event=876654</tag>
<url>http://www.last.fm/event/876654</url>
</event>
<event>
<id>957549</id>
<title>No Charge!! Club Cyber Collection 2009</title>
<artists>
<artist></artist>
<artist></artist>
<artist>Diprogram</artist>
<artist>Ziggrat</artist>
<artist>Mistes</artist>
<artist>カルト☆フィクション倶楽部</artist>
<artist></artist>
<artist>ザ☆ビックマウス</artist>
<headliner></headliner>
</artists>
<venue>
<name>池袋CYBER</name>
<location>
<city>Tokyo</city>
<country>Japan</country>
<street></street>
<postalcode></postalcode>
<geo:point>
<geo:lat>35.7108378353001</geo:lat>
<geo:long>139.751586914062</geo:long>
</geo:point>
<timezone>JST</timezone>
</location>
<url>http://www.last.fm/venue/8854834</url>
</venue>
<startDate>Wed, 18 Mar 2009</startDate>
<description><![CDATA[<div class="bbcode">Free live (drink 600 yens)</div>]]></description>
<image size="small">http://userserve-ak.last.fm/serve/34/17811927.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/17811927.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/17811927.jpg</image>
<attendance>2</attendance>
<reviews>0</reviews>
<tag>lastfm:event=957549</tag>
<url>http://www.last.fm/event/957549</url>
</event>
<event>
<id>888895</id>
<title>Ryuichi Sakamoto Playing The Piano 2009</title>
<artists>
<artist>坂本龍一</artist>
<headliner>坂本龍一</headliner>
</artists>
<venue>
<name>東京国際フォーラム ホールC</name>
<location>
<city>東京都</city>
<country>Japan</country>
<street>千代田区丸の内3-5-1</street>
<postalcode>100-0005</postalcode>
<geo:point>
<geo:lat>35.7108378353001</geo:lat>
<geo:long>139.751586914062</geo:long>
</geo:point>
<timezone>JST</timezone>
</location>
<url>http://www.last.fm/venue/8994410</url>
</venue>
<startDate>Wed, 18 Mar 2009</startDate>
<startTime>19:00</startTime>
<description></description>
<image size="small">http://userserve-ak.last.fm/serve/34/24067119.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/24067119.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/24067119.jpg</image>
<attendance>3</attendance>
<reviews>0</reviews>
<tag>lastfm:event=888895</tag>
<url>http://www.last.fm/event/888895</url>
</event>
<event>
<id>948701</id>
<title>Crazy Die Amond Act.3</title>
<artists>
<artist>Tecda</artist>
<artist>Helmet</artist>
<artist>Asa</artist>
<artist>Sol</artist>
<artist>shino</artist>
<artist>Ein</artist>
<artist>Hisato</artist>
<artist>Yudai</artist>
<artist>starsoldier</artist>
<artist>koshi fujiyama</artist>
<artist>digital sato</artist>
<artist>shikaval</artist>
<artist>Suoiyo! Masarusan</artist>
<headliner>Tecda</headliner>
</artists>
<venue>
<name>Bitters Yokohama</name>
<location>
<city>Yokohama, Kanagawa</city>
<country>Japan</country>
<street></street>
<postalcode></postalcode>
<geo:point>
<geo:lat>35.45</geo:lat>
<geo:long>139.65</geo:long>
</geo:point>
<timezone>UTC</timezone>
</location>
<url>http://www.last.fm/venue/8981623</url>
</venue>
<startDate>Thu, 19 Mar 2009</startDate>
<description><![CDATA[<div class="bbcode">2009.03.19.THU<br />
<br />
Solid De Acid Disco Party<br />
<br />
<strong>&quot;Crazy Die Amond Act.3″</strong><br />
<br />
@Studio Bitters yokohama<br />
<br />
door: 2000yen/1D<br />
<br />
w/f: 1500yen/1D<br />
<br />
Start: 22:00<br />
<br />
Live set<br />
<br />
<a href="http://ws.audioscrobbler.com/music/Tecda" class="bbcode_artist">Tecda</a>(shizukamura.net)<br />
<br />
Djz<br />
<br />
Digital Sato(PsycotroBeat)<br />
<br />
ASA(IMP, stonetemple)<br />
<br />
SOL(MACHiDA CYCHO)<br />
<br />
HELMET(dbc17)<br />
<br />
Yudai(crazy die amond)<br />
<br />
EIN(codekommando/Berlin)<br />
<br />
shikaval(closed)<br />
<br />
Sound System by 和泉中央<br />
<br />
Bar/パラレルラウンジ<br />
<br />
Djz<br />
<br />
shino(shizukamura.net)<br />
<br />
starsoldier(9we9 Sound)<br />
<br />
koshi fujiyama(最高変態)<br />
<br />
SOL(FirstClass)<br />
<br />
hisato<br />
<br />
sugoiyo!!masarusan(sexy command)<br />
<br />
INCENSE SHOP『幻夢堂』<br />
<br />
危険物、法律で禁止されてる物の持ち込みは固くお断りします。<br />
<br />
20歳以下の方は入場できません。<br />
<br />
尚、すべてのトラブル、事故等は責任を負いかねます。<br />
<br />
Studio Bitters yokohama<br />
<br />
〒231-0801 神奈川県横浜市中区新山下 F<br />
<br />
TEL/FAX 045-623-6099<br />
<br />
※お車でお越しの方は最寄のパーキングをお使いください</div>]]></description>
<image size="small">http://userserve-ak.last.fm/serve/34/24268155.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/24268155.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/24268155.jpg</image>
<attendance>1</attendance>
<reviews>0</reviews>
<tag>lastfm:event=948701</tag>
<url>http://www.last.fm/event/948701</url>
</event>
<event>
<id>986029</id>
<title>Byte Size - Panophonic Downturn Special</title>
<artists>
<artist>skab/t</artist>
<headliner>skab/t</headliner>
</artists>
<venue>
<name>Bar Aoyama, Shibuya</name>
<location>
<city>Tokyo</city>
<country>Japan</country>
<street></street>
<postalcode></postalcode>
<geo:point>
<geo:lat>35.7108378353001</geo:lat>
<geo:long>139.751586914062</geo:long>
</geo:point>
<timezone>JST</timezone>
</location>
<url>http://www.last.fm/venue/9015838</url>
</venue>
<startDate>Thu, 19 Mar 2009</startDate>
<description><![CDATA[<div class="bbcode"><a href="http://maps.google.com/maps?f=q&amp;source=s_q&amp;hl=en&amp;q=%E6%B8%8B%E8%B0%B7%E9%A7%85%EF%BC%88%E6%9D%B1%E4%BA%AC%EF%BC%89+station+Japan&amp;sll=35.614631,139.741032&amp;sspn=0.010309,0.015428&amp;ie=UTF8&amp;cd=1&amp;geocode=FRUbIAIdVqxTCA&amp;split=0&amp;ll=35.658463,139.709991&amp;spn=0.001288,0.001929&amp;z=19&amp;iwloc=addr&amp;layer=c&amp;cbll=35.658547,139.709204&amp;panoid=qY6u-voxAiSyqkvSTtiA9A&amp;cbp=12,355.2921180785233,,0,6.627260083449234" rel="nofollow">Map to Venue</a><br />
<br />
<img src="http://www.freeta.org/news/wp-content/gallery/flyers-and-posters/bs090319_web.png" /><br />
Byte Size is back! Fighting the spiralling economy with beats and treats at Bar Aoyama in Shibuya. Fun starts at 10pm on Thursday the 19th of March (remember, Friday is a public holiday people!). We will bring you, as always, a rich selection of DJs and live electronic music from around Tokyo. No door/table charge, cheap drinks and a recently redecorated venue.</div>]]></description>
<image size="small">http://userserve-ak.last.fm/serve/34/24739555.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/24739555.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/24739555.jpg</image>
<attendance>2</attendance>
<reviews>0</reviews>
<tag>lastfm:event=986029</tag>
<url>http://www.last.fm/event/986029</url>
</event>
<event>
<id>846323</id>
<title>『Baggy Bogy Free Festival』</title>
<artists>
<artist>バギーボギー</artist>
<headliner>バギーボギー</headliner>
</artists>
<venue>
<name>池袋CYBER</name>
<location>
<city>Tokyo</city>
<country>Japan</country>
<street></street>
<postalcode></postalcode>
<geo:point>
<geo:lat>35.7108378353001</geo:lat>
<geo:long>139.751586914062</geo:long>
</geo:point>
<timezone>JST</timezone>
</location>
<url>http://www.last.fm/venue/8854834</url>
</venue>
<startDate>Thu, 19 Mar 2009</startDate>
<description><![CDATA[<div class="bbcode">OPEN/未定 START/未定<br />
前売/¥0 当日/¥0<br />
ドリンク/別</div>]]></description>
<image size="small">http://userserve-ak.last.fm/serve/34/11871967.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/11871967.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/11871967.jpg</image>
<attendance>1</attendance>
<reviews>0</reviews>
<tag>lastfm:event=846323</tag>
<url>http://www.last.fm/event/846323</url>
</event>
<event>
<id>951660</id>
<title>GAN-BAN NIGHT SPECIAL</title>
<artists>
<artist>電気グルーヴ</artist>
<artist>Fumiya Tanaka</artist>
<artist>Metalmouse</artist>
<artist>80KIDZ</artist>
<artist>田中フミヤ</artist>
<artist>DEXPISTOLS</artist>
<headliner>電気グルーヴ</headliner>
</artists>
<venue>
<name>ageHa@STUDIO COAST</name>
<location>
<city>Tokyo</city>
<country>Japan</country>
<street>2-2-10 Kotou-ku, Shinkiba</street>
<postalcode>136-0082</postalcode>
<geo:point>
<geo:lat>35.7108378353001</geo:lat>
<geo:long>139.751586914062</geo:long>
</geo:point>
<timezone>JST</timezone>
</location>
<url>http://www.last.fm/venue/8962708</url>
</venue>
<startDate>Thu, 19 Mar 2009</startDate>
<description></description>
<image size="small">http://userserve-ak.last.fm/serve/34/5928937.jpg</image>
<image size="medium">http://userserve-ak.last.fm/serve/64/5928937.jpg</image>
<image size="large">http://userserve-ak.last.fm/serve/126/5928937.jpg</image>
<attendance>5</attendance>
<reviews>0</reviews>
<tag>lastfm:event=951660</tag>
<url>http://www.last.fm/event/951660</url>
</event>
</events></lfm>

View File

@ -55,8 +55,8 @@ class TestGeo(unittest.TestCase):
self.assertEqual((top_track.name, top_track.artist.name), ('Viva la Vida', 'Coldplay'))
def testLocationEvents(self):
event_ids = [957543, 871240, 843216, 938214, 910474,
875468, 863115, 954783, 890885, 843238]
event_ids = [920495, 929525, 828211, 876654, 957549,
888895, 948701, 986029, 846323, 951660]
self.assertEqual([e.id for e in self.location.events[:10]], event_ids)
def testCountryName(self):