* renamed all mixin modules to avoid name conflict * removed mixin classes from class inheritence list and put then as class decorators
680 lines
22 KiB
Python
680 lines
22 KiB
Python
#!/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
|
|
from lastfm.decorators import cached_property, top_property, depaginate
|
|
|
|
class Geo(object):
|
|
"""A class representing an geographic location"""
|
|
@staticmethod
|
|
@depaginate
|
|
def get_events(api,
|
|
location,
|
|
latitude = None,
|
|
longitude = None,
|
|
distance = None,
|
|
page = 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({'lat': latitude, 'long': longitude})
|
|
if page is not None:
|
|
params.update({'page': page})
|
|
|
|
data = api._fetch_data(params).find('events')
|
|
total_pages = int(data.attrib['totalpages'])
|
|
yield total_pages
|
|
|
|
for e in data.findall('event'):
|
|
yield Event.create_from_data(api, e)
|
|
|
|
@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 [
|
|
Artist(
|
|
api,
|
|
name = a.findtext('name'),
|
|
mbid = a.findtext('mbid'),
|
|
stats = Stats(
|
|
subject = a.findtext('name'),
|
|
rank = int(a.attrib['rank']),
|
|
playcount = int(a.findtext('playcount'))
|
|
),
|
|
url = 'http://' + a.findtext('url'),
|
|
image = {'large': a.findtext('image')}
|
|
)
|
|
for a in data.findall('artist')
|
|
]
|
|
|
|
@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})
|
|
|
|
data = api._fetch_data(params).find('toptracks')
|
|
return [
|
|
Track(
|
|
api,
|
|
name = t.findtext('name'),
|
|
mbid = t.findtext('mbid'),
|
|
artist = Artist(
|
|
api,
|
|
name = t.findtext('artist/name'),
|
|
mbid = t.findtext('artist/mbid'),
|
|
url = t.findtext('artist/url')
|
|
),
|
|
stats = Stats(
|
|
subject = t.findtext('name'),
|
|
rank = int(t.attrib['rank']),
|
|
playcount = int(t.findtext('playcount'))
|
|
),
|
|
streamable = (t.findtext('streamable') == '1'),
|
|
full_track = (t.find('streamable').attrib['fulltrack'] == '1'),
|
|
url = 'http://' + t.findtext('url'),
|
|
image = {'large': t.findtext('image')}
|
|
)
|
|
for t in data.findall('track')
|
|
]
|
|
|
|
@cacheable
|
|
class Location(LastfmBase):
|
|
"""A class representing a location of an event"""
|
|
XMLNS = "http://www.w3.org/2003/01/geo/wgs84_pos#"
|
|
|
|
def init(self,
|
|
api,
|
|
city = None,
|
|
country = None,
|
|
street = None,
|
|
postal_code = None,
|
|
latitude = None,
|
|
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
|
|
self._city = city
|
|
self._country = country
|
|
self._street = street
|
|
self._postal_code = postal_code
|
|
self._latitude = latitude
|
|
self._longitude = longitude
|
|
self._timezone = timezone
|
|
|
|
@property
|
|
def city(self):
|
|
"""
|
|
city in which the location is situated
|
|
@rtype: L{str}
|
|
"""
|
|
return self._city
|
|
|
|
@property
|
|
def country(self):
|
|
"""
|
|
country in which the location is situated
|
|
@rtype: L{Country}
|
|
"""
|
|
return self._country
|
|
|
|
@property
|
|
def street(self):
|
|
"""
|
|
street in which the location is situated
|
|
@rtype: L{str}
|
|
"""
|
|
return self._street
|
|
|
|
@property
|
|
def postal_code(self):
|
|
"""
|
|
postal code of the location
|
|
@rtype: L{str}
|
|
"""
|
|
return self._postal_code
|
|
|
|
@property
|
|
def latitude(self):
|
|
"""
|
|
latitude of the location
|
|
@rtype: L{float}
|
|
"""
|
|
return self._latitude
|
|
|
|
@property
|
|
def longitude(self):
|
|
"""
|
|
longitude of the location
|
|
@rtype: L{float}
|
|
"""
|
|
return self._longitude
|
|
|
|
@property
|
|
def timezone(self):
|
|
"""
|
|
timezone in which the location is situated
|
|
@rtype: L{str}
|
|
"""
|
|
return self._timezone
|
|
|
|
@cached_property
|
|
def top_tracks(self):
|
|
"""
|
|
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 for the location
|
|
@rtype: L{Track}
|
|
"""
|
|
pass
|
|
|
|
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,
|
|
self.longitude,
|
|
distance)
|
|
|
|
@cached_property
|
|
def events(self):
|
|
"""
|
|
events taking place at/around the location
|
|
@rtype: L{lazylist} of L{Event}
|
|
"""
|
|
return self.get_events()
|
|
|
|
@staticmethod
|
|
def _hash_func(*args, **kwds):
|
|
try:
|
|
return hash("latlong%s%s" % (kwds['latitude'], kwds['longitude']))
|
|
except KeyError:
|
|
try:
|
|
return hash("name%s" % kwds['city'])
|
|
except KeyError:
|
|
raise InvalidParametersError("either latitude and longitude or city has to be provided for hashing")
|
|
|
|
def __hash__(self):
|
|
if not self.city:
|
|
return self.__class__._hash_func(
|
|
latitude = self.latitude,
|
|
longitude = self.longitude)
|
|
else:
|
|
return self.__class__._hash_func(name = self.city)
|
|
|
|
def __eq__(self, other):
|
|
return self.latitude == other.latitude and self.longitude == other.longitude
|
|
|
|
def __lt__(self, other):
|
|
if self.country != other.country:
|
|
return self.country < other.country
|
|
else:
|
|
return self.city < other.city
|
|
|
|
def __repr__(self):
|
|
if self.city is None:
|
|
return "<lastfm.geo.Location: (%s, %s)>" % (self.latitude, self.longitude)
|
|
else:
|
|
return "<lastfm.geo.Location: %s>" % self.city
|
|
|
|
@cacheable
|
|
class Country(LastfmBase):
|
|
"""A class representing a country."""
|
|
ISO_CODES = {
|
|
'AD': 'Andorra',
|
|
'AE': 'United Arab Emirates',
|
|
'AF': 'Afghanistan',
|
|
'AG': 'Antigua and Barbuda',
|
|
'AI': 'Anguilla',
|
|
'AL': 'Albania',
|
|
'AM': 'Armenia',
|
|
'AN': 'Netherlands Antilles',
|
|
'AO': 'Angola',
|
|
'AQ': 'Antarctica',
|
|
'AR': 'Argentina',
|
|
'AS': 'American Samoa',
|
|
'AT': 'Austria',
|
|
'AU': 'Australia',
|
|
'AW': 'Aruba',
|
|
'AX': 'land Islands',
|
|
'AZ': 'Azerbaijan',
|
|
'BA': 'Bosnia and Herzegovina',
|
|
'BB': 'Barbados',
|
|
'BD': 'Bangladesh',
|
|
'BE': 'Belgium',
|
|
'BF': 'Burkina Faso',
|
|
'BG': 'Bulgaria',
|
|
'BH': 'Bahrain',
|
|
'BI': 'Burundi',
|
|
'BJ': 'Benin',
|
|
'BL': 'Saint Barthlemy',
|
|
'BM': 'Bermuda',
|
|
'BN': 'Brunei Darussalam',
|
|
'BO': 'Bolivia',
|
|
'BR': 'Brazil',
|
|
'BS': 'Bahamas',
|
|
'BT': 'Bhutan',
|
|
'BV': 'Bouvet Island',
|
|
'BW': 'Botswana',
|
|
'BY': 'Belarus',
|
|
'BZ': 'Belize',
|
|
'CA': 'Canada',
|
|
'CC': 'Cocos (Keeling) Islands',
|
|
'CD': 'Congo, The Democratic Republic of the',
|
|
'CF': 'Central African Republic',
|
|
'CG': 'Congo',
|
|
'CH': 'Switzerland',
|
|
'CI': "Cte d'Ivoire",
|
|
'CK': 'Cook Islands',
|
|
'CL': 'Chile',
|
|
'CM': 'Cameroon',
|
|
'CN': 'China',
|
|
'CO': 'Colombia',
|
|
'CR': 'Costa Rica',
|
|
'CU': 'Cuba',
|
|
'CV': 'Cape Verde',
|
|
'CX': 'Christmas Island',
|
|
'CY': 'Cyprus',
|
|
'CZ': 'Czech Republic',
|
|
'DE': 'Germany',
|
|
'DJ': 'Djibouti',
|
|
'DK': 'Denmark',
|
|
'DM': 'Dominica',
|
|
'DO': 'Dominican Republic',
|
|
'DZ': 'Algeria',
|
|
'EC': 'Ecuador',
|
|
'EE': 'Estonia',
|
|
'EG': 'Egypt',
|
|
'EH': 'Western Sahara',
|
|
'ER': 'Eritrea',
|
|
'ES': 'Spain',
|
|
'ET': 'Ethiopia',
|
|
'FI': 'Finland',
|
|
'FJ': 'Fiji',
|
|
'FK': 'Falkland Islands (Malvinas)',
|
|
'FM': 'Micronesia, Federated States of',
|
|
'FO': 'Faroe Islands',
|
|
'FR': 'France',
|
|
'GA': 'Gabon',
|
|
'GB': 'United Kingdom',
|
|
'GD': 'Grenada',
|
|
'GE': 'Georgia',
|
|
'GF': 'French Guiana',
|
|
'GG': 'Guernsey',
|
|
'GH': 'Ghana',
|
|
'GI': 'Gibraltar',
|
|
'GL': 'Greenland',
|
|
'GM': 'Gambia',
|
|
'GN': 'Guinea',
|
|
'GP': 'Guadeloupe',
|
|
'GQ': 'Equatorial Guinea',
|
|
'GR': 'Greece',
|
|
'GS': 'South Georgia and the South Sandwich Islands',
|
|
'GT': 'Guatemala',
|
|
'GU': 'Guam',
|
|
'GW': 'Guinea-Bissau',
|
|
'GY': 'Guyana',
|
|
'HK': 'Hong Kong',
|
|
'HM': 'Heard Island and McDonald Islands',
|
|
'HN': 'Honduras',
|
|
'HR': 'Croatia',
|
|
'HT': 'Haiti',
|
|
'HU': 'Hungary',
|
|
'ID': 'Indonesia',
|
|
'IE': 'Ireland',
|
|
'IL': 'Israel',
|
|
'IM': 'Isle of Man',
|
|
'IN': 'India',
|
|
'IO': 'British Indian Ocean Territory',
|
|
'IQ': 'Iraq',
|
|
'IR': 'Iran, Islamic Republic of',
|
|
'IS': 'Iceland',
|
|
'IT': 'Italy',
|
|
'JE': 'Jersey',
|
|
'JM': 'Jamaica',
|
|
'JO': 'Jordan',
|
|
'JP': 'Japan',
|
|
'KE': 'Kenya',
|
|
'KG': 'Kyrgyzstan',
|
|
'KH': 'Cambodia',
|
|
'KI': 'Kiribati',
|
|
'KM': 'Comoros',
|
|
'KN': 'Saint Kitts and Nevis',
|
|
'KP': "Korea, Democratic People's Republic of",
|
|
'KR': 'Korea, Republic of',
|
|
'KW': 'Kuwait',
|
|
'KY': 'Cayman Islands',
|
|
'KZ': 'Kazakhstan',
|
|
'LA': "Lao People's Democratic Republic",
|
|
'LB': 'Lebanon',
|
|
'LC': 'Saint Lucia',
|
|
'LI': 'Liechtenstein',
|
|
'LK': 'Sri Lanka',
|
|
'LR': 'Liberia',
|
|
'LS': 'Lesotho',
|
|
'LT': 'Lithuania',
|
|
'LU': 'Luxembourg',
|
|
'LV': 'Latvia',
|
|
'LY': 'Libyan Arab Jamahiriya',
|
|
'MA': 'Morocco',
|
|
'MC': 'Monaco',
|
|
'MD': 'Moldova',
|
|
'ME': 'Montenegro',
|
|
'MF': 'Saint Martin',
|
|
'MG': 'Madagascar',
|
|
'MH': 'Marshall Islands',
|
|
'MK': 'Macedonia, The Former Yugoslav Republic of',
|
|
'ML': 'Mali',
|
|
'MM': 'Myanmar',
|
|
'MN': 'Mongolia',
|
|
'MO': 'Macao',
|
|
'MP': 'Northern Mariana Islands',
|
|
'MQ': 'Martinique',
|
|
'MR': 'Mauritania',
|
|
'MS': 'Montserrat',
|
|
'MT': 'Malta',
|
|
'MU': 'Mauritius',
|
|
'MV': 'Maldives',
|
|
'MW': 'Malawi',
|
|
'MX': 'Mexico',
|
|
'MY': 'Malaysia',
|
|
'MZ': 'Mozambique',
|
|
'NA': 'Namibia',
|
|
'NC': 'New Caledonia',
|
|
'NE': 'Niger',
|
|
'NF': 'Norfolk Island',
|
|
'NG': 'Nigeria',
|
|
'NI': 'Nicaragua',
|
|
'NL': 'Netherlands',
|
|
'NO': 'Norway',
|
|
'NP': 'Nepal',
|
|
'NR': 'Nauru',
|
|
'NU': 'Niue',
|
|
'NZ': 'New Zealand',
|
|
'OM': 'Oman',
|
|
'PA': 'Panama',
|
|
'PE': 'Peru',
|
|
'PF': 'French Polynesia',
|
|
'PG': 'Papua New Guinea',
|
|
'PH': 'Philippines',
|
|
'PK': 'Pakistan',
|
|
'PL': 'Poland',
|
|
'PM': 'Saint Pierre and Miquelon',
|
|
'PN': 'Pitcairn',
|
|
'PR': 'Puerto Rico',
|
|
'PS': 'Palestinian Territory, Occupied',
|
|
'PT': 'Portugal',
|
|
'PW': 'Palau',
|
|
'PY': 'Paraguay',
|
|
'QA': 'Qatar',
|
|
'RE': 'Runion',
|
|
'RO': 'Romania',
|
|
'RS': 'Serbia',
|
|
'RU': 'Russian Federation',
|
|
'RW': 'Rwanda',
|
|
'SA': 'Saudi Arabia',
|
|
'SB': 'Solomon Islands',
|
|
'SC': 'Seychelles',
|
|
'SD': 'Sudan',
|
|
'SE': 'Sweden',
|
|
'SG': 'Singapore',
|
|
'SH': 'Saint Helena',
|
|
'SI': 'Slovenia',
|
|
'SJ': 'Svalbard and Jan Mayen',
|
|
'SK': 'Slovakia',
|
|
'SL': 'Sierra Leone',
|
|
'SM': 'San Marino',
|
|
'SN': 'Senegal',
|
|
'SO': 'Somalia',
|
|
'SR': 'Suriname',
|
|
'ST': 'Sao Tome and Principe',
|
|
'SV': 'El Salvador',
|
|
'SY': 'Syrian Arab Republic',
|
|
'SZ': 'Swaziland',
|
|
'TC': 'Turks and Caicos Islands',
|
|
'TD': 'Chad',
|
|
'TF': 'French Southern Territories',
|
|
'TG': 'Togo',
|
|
'TH': 'Thailand',
|
|
'TJ': 'Tajikistan',
|
|
'TK': 'Tokelau',
|
|
'TL': 'Timor-Leste',
|
|
'TM': 'Turkmenistan',
|
|
'TN': 'Tunisia',
|
|
'TO': 'Tonga',
|
|
'TR': 'Turkey',
|
|
'TT': 'Trinidad and Tobago',
|
|
'TV': 'Tuvalu',
|
|
'TW': 'Taiwan, Province of China',
|
|
'TZ': 'Tanzania, United Republic of',
|
|
'UA': 'Ukraine',
|
|
'UG': 'Uganda',
|
|
'UM': 'United States Minor Outlying Islands',
|
|
'US': 'United States',
|
|
'UY': 'Uruguay',
|
|
'UZ': 'Uzbekistan',
|
|
'VA': 'Holy See (Vatican City State)',
|
|
'VC': 'Saint Vincent and the Grenadines',
|
|
'VE': 'Venezuela',
|
|
'VG': 'Virgin Islands, British',
|
|
'VI': 'Virgin Islands, U.S.',
|
|
'VN': 'Viet Nam',
|
|
'VU': 'Vanuatu',
|
|
'WF': 'Wallis and Futuna',
|
|
'WS': 'Samoa',
|
|
'YE': 'Yemen',
|
|
'YT': 'Mayotte',
|
|
'ZA': 'South Africa',
|
|
'ZM': 'Zambia',
|
|
'ZW': 'Zimbabwe'}
|
|
"""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
|
|
self._name = name
|
|
|
|
@property
|
|
def name(self):
|
|
"""
|
|
name of the country
|
|
@rtype: L{str}
|
|
"""
|
|
return self._name
|
|
|
|
@cached_property
|
|
def top_artists(self):
|
|
"""
|
|
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
|
|
@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
|
|
@rtype: L{list} of L{Track}
|
|
"""
|
|
return self.get_top_tracks()
|
|
|
|
@top_property("top_tracks")
|
|
def top_track(self):
|
|
"""
|
|
top track of the country
|
|
@rtype: L{Track}
|
|
"""
|
|
pass
|
|
|
|
@cached_property
|
|
def events(self):
|
|
"""
|
|
events taking place in the country
|
|
@rtype: L{lazylist} of L{Event}
|
|
"""
|
|
return Geo.get_events(self._api, self.name)
|
|
|
|
@staticmethod
|
|
def _hash_func(*args, **kwds):
|
|
try:
|
|
return hash(kwds['name'].lower())
|
|
except KeyError:
|
|
raise InvalidParametersError("name has to be provided for hashing")
|
|
|
|
def __hash__(self):
|
|
return self.__class__._hash_func(name = self.name)
|
|
|
|
def __eq__(self, other):
|
|
return self.name.lower() == other.name.lower()
|
|
|
|
def __lt__(self, other):
|
|
return self.name < other.name
|
|
|
|
def __repr__(self):
|
|
return "<lastfm.geo.Country: %s>" % self.name
|
|
|
|
from lastfm.api import Api
|
|
from lastfm.artist import Artist
|
|
from lastfm.error import InvalidParametersError
|
|
from lastfm.event import Event
|
|
from lastfm.stats import Stats
|
|
from lastfm.track import Track
|