* added Rolling Charts: Monthly, Quaterly, Half-yearly and Yearly; Album, Artist, Track and Tag charts

* refactored the chart related code to mixins.chartable module
* made related changes in user, tag and group modules
master
Abhinav Sarkar 2009-03-28 07:24:24 +00:00
parent 42aa35fcbb
commit d709434a7b
6 changed files with 717 additions and 460 deletions

View File

@ -10,7 +10,7 @@ from lastfm.mixins import Cacheable
from operator import xor from operator import xor
class Chart(LastfmBase, Cacheable): class Chart(LastfmBase, Cacheable):
"""A class for representing the weekly charts""" """The base class for all the chart classes"""
def init(self, subject, start, end, stats = None): def init(self, subject, start, end, stats = None):
self._subject = subject self._subject = subject
@ -35,26 +35,16 @@ class Chart(LastfmBase, Cacheable):
return self._stats return self._stats
@staticmethod @staticmethod
def create_from_data(api, subject, data): def _check_chart_params(params, subject, start = None, end = None):
return Chart(
subject = subject,
start = datetime.utcfromtimestamp(int(data.attrib['from'])),
end = datetime.utcfromtimestamp(int(data.attrib['to']))
)
@staticmethod
def _check_chart_params(params, start = None, end = None):
if xor(start is None, end is None): if xor(start is None, end is None):
raise InvalidParametersError("both start and end have to be provided.") raise InvalidParametersError("both start and end have to be provided.")
if start is not None and end is not None: if start is not None and end is not None:
if isinstance(start, datetime) and isinstance(end, datetime): if not (isinstance(start, datetime) and isinstance(end, datetime)):
params.update({
'from': int(calendar.timegm(start.timetuple())),
'to': int(calendar.timegm(end.timetuple()))
})
else:
raise InvalidParametersError("start and end must be datetime.datetime instances") raise InvalidParametersError("start and end must be datetime.datetime instances")
params.update({
'from': int(calendar.timegm(start.timetuple())),
'to': int(calendar.timegm(end.timetuple()))
})
return params return params
@staticmethod @staticmethod
@ -71,10 +61,10 @@ class Chart(LastfmBase, Cacheable):
def __hash__(self): def __hash__(self):
return self.__class__._hash_func( return self.__class__._hash_func(
subject = self.subject, subject = self.subject,
start = self.start, start = self.start,
end = self.end end = self.end
) )
def __eq__(self, other): def __eq__(self, other):
return self.subject == other.subject and \ return self.subject == other.subject and \
@ -99,21 +89,68 @@ class Chart(LastfmBase, Cacheable):
self.start.strftime("%x"), self.start.strftime("%x"),
self.end.strftime("%x"), self.end.strftime("%x"),
) )
class WeeklyChart(Chart):
def init(self, subject, start, end, stats = None):
super(WeeklyChart, self).init(subject, start, end, stats)
class WeeklyAlbumChart(WeeklyChart): class AlbumChart(Chart):
"""A class for representing the weekly album charts"""
def init(self, subject, start, end, stats, albums): def init(self, subject, start, end, stats, albums):
super(WeeklyAlbumChart, self).init(subject, start, end, stats) super(AlbumChart, self).init(subject, start, end, stats)
self._albums = albums self._albums = albums
@property @property
def albums(self): def albums(self):
return self._albums return self._albums
class ArtistChart(Chart):
def init(self, subject, start, end, stats, artists):
super(ArtistChart, self).init(subject, start, end, stats)
self._artists = artists
@property
def artists(self):
return self._artists
class TrackChart(Chart):
def init(self, subject, start, end, tracks, stats):
super(TrackChart, self).init(subject, start, end, stats)
self._tracks = tracks
@property
def tracks(self):
return self._tracks
class TagChart(Chart):
def init(self, subject, start, end, tags, stats):
super(TagChart, self).init(subject, start, end, stats)
self._tags = tags
@property
def tags(self):
return self._tags
class WeeklyChart(Chart):
"""A class for representing the weekly charts"""
@staticmethod
def create_from_data(api, subject, data):
return WeeklyChart(
subject = subject,
start = datetime.utcfromtimestamp(int(data.attrib['from'])),
end = datetime.utcfromtimestamp(int(data.attrib['to']))
)
@staticmethod
def _check_chart_params(params, subject, start = None, end = None):
params = Chart._check_chart_params(params, subject, start, end)
if start is not None and end is not None:
wcl = subject.weekly_chart_list
is_valid = False
for wc in wcl:
if wc.start == start and wc.end == end:
is_valid = True
if not is_valid:
raise InvalidParametersError("%s - %s chart dates are invalid" % (start, end))
return params
class WeeklyAlbumChart(AlbumChart, WeeklyChart):
"""A class for representing the weekly album charts"""
@staticmethod @staticmethod
def create_from_data(api, subject, data): def create_from_data(api, subject, data):
w = WeeklyChart( w = WeeklyChart(
@ -158,16 +195,8 @@ class WeeklyAlbumChart(WeeklyChart):
] ]
) )
class WeeklyArtistChart(WeeklyChart): class WeeklyArtistChart(ArtistChart, WeeklyChart):
"""A class for representing the weekly artist charts""" """A class for representing the weekly artist charts"""
def init(self, subject, start, end, stats, artists):
super(WeeklyArtistChart, self).init(subject, start, end, stats)
self._artists = artists
@property
def artists(self):
return self._artists
@staticmethod @staticmethod
def create_from_data(api, subject, data): def create_from_data(api, subject, data):
w = WeeklyChart( w = WeeklyChart(
@ -208,16 +237,8 @@ class WeeklyArtistChart(WeeklyChart):
] ]
) )
class WeeklyTrackChart(WeeklyChart): class WeeklyTrackChart(TrackChart, WeeklyChart):
"""A class for representing the weekly track charts""" """A class for representing the weekly track charts"""
def init(self, subject, start, end, tracks, stats):
super(WeeklyTrackChart, self).init(subject, start, end, stats)
self._tracks = tracks
@property
def tracks(self):
return self._tracks
@staticmethod @staticmethod
def create_from_data(api, subject, data): def create_from_data(api, subject, data):
w = WeeklyChart( w = WeeklyChart(
@ -261,16 +282,8 @@ class WeeklyTrackChart(WeeklyChart):
] ]
) )
class WeeklyTagChart(WeeklyChart): class WeeklyTagChart(TagChart, WeeklyChart):
"""A class for representing the weekly tag charts""" """A class for representing the weekly tag charts"""
def init(self, subject, start, end, tags, stats):
super(WeeklyTagChart, self).init(subject, start, end, stats)
self._tags = tags
@property
def tags(self):
return self._tags
@staticmethod @staticmethod
def create_from_data(api, subject, start, end): def create_from_data(api, subject, start, end):
w = WeeklyChart( w = WeeklyChart(
@ -356,77 +369,216 @@ class WeeklyTagChart(WeeklyChart):
return wtc return wtc
class RollingChart(Chart): class RollingChart(Chart):
pass """Base class for the rolling charts classes"""
@classmethod
def _check_chart_params(cls, params, subject, start = None, end = None):
duration = cls._period['duration']
params = Chart._check_chart_params(params, subject, start, end)
if start is not None and end is not None:
mcl = MonthlyChart.get_chart_list(subject)
is_valid = False
for i in xrange(len(mcl)-(duration-1)):
if mcl[i].start == start and mcl[i+(duration-1)].end == end:
is_valid = True
if not is_valid:
raise InvalidParametersError("%s - %s chart dates are invalid" % (start, end))
return params
@classmethod
def create_from_data(cls, subject, key_func,
start = None, end = None):
chart_type = cls.mro()[0]._chart_type
period = cls.mro()[3]._period
globals()["%slyChart" % period['name'].title().replace(' ','')]._check_chart_params({}, subject, start, end)
mcl = MonthlyChart.get_chart_list(subject)
if start is None and end is None:
start = mcl[-period['duration']].start
end = mcl[-1].end
wcl = subject.weekly_chart_list
period_wcl = [wc for wc in wcl
if start < wc.start < end or start < wc.end < end]
period_wacl = []
for wc in period_wcl:
try:
period_wacl.append(
getattr(subject, "get_weekly_%s_chart" % chart_type)(wc.start, wc.end))
except LastfmError:
pass
stats_dict = period_wacl[0].__dict__["_%ss" % chart_type][0].stats.__dict__
count_attribute = [k for k in stats_dict.keys()
if stats_dict[k] is not None and k not in ['_rank', '_subject']][0]
items = {}
for wac in period_wacl:
for item in wac.__dict__["_%ss" % chart_type]:
key = key_func(item)
mw_start = max(wac.start, start)
mw_end = min(wac.end, end)
count = item.stats.__dict__[count_attribute] * (mw_end - mw_start).days / 7.0
if key in items:
items[key].stats.__dict__[count_attribute] += count
else:
items[key] = item
items[key].stats.__dict__[count_attribute] = count
items = items.values()
items = [a for a in items if a.stats.__dict__[count_attribute] >= 1]
items.sort(key = lambda a: a.stats.__dict__[count_attribute], reverse=True)
for i,item in enumerate(items):
item.stats._rank = i + 1
item.stats.__dict__[count_attribute] = int(item.stats.__dict__[count_attribute])
return globals()[
"%sly%sChart" % (
period['name'].title().replace(' ',''),
chart_type.capitalize()
)](
subject = subject,
start = start,
end = end,
stats = Stats(
subject = subject,
**{count_attribute[1:]: sum([a.stats.__dict__[count_attribute] for a in items])}
),
**{"%ss" % chart_type: items}
)
class RollingAlbumChart(AlbumChart):
@classmethod
def create_from_data(cls, subject, start = None, end = None):
key_func = lambda album: "::".join((album.name, album.artist.name))
return super(cls.mro()[3], cls).create_from_data(
subject, key_func, start, end)
class RollingArtistChart(ArtistChart):
@classmethod
def create_from_data(cls, subject, start = None, end = None):
key_func = lambda artist: artist.name
return super(cls.mro()[3], cls).create_from_data(
subject, key_func, start, end)
class RollingTrackChart(TrackChart):
@classmethod
def create_from_data(cls, subject, start = None, end = None):
key_func = lambda track: "::".join((track.name, track.artist.name))
return super(cls.mro()[3], cls).create_from_data(
subject, key_func, start, end)
class RollingTagChart(TagChart):
@classmethod
def create_from_data(cls, subject, start = None, end = None):
key_func = lambda tag: tag.name
chart = super(cls.mro()[3], cls).create_from_data(
subject, key_func, start, end)
count_sum = sum([t.stats.count for t in chart.tags])
for t in chart.tags:
t.stats.__dict__['_count'] /= count_sum
return chart
class MonthlyChart(RollingChart): class MonthlyChart(RollingChart):
pass """A class for representing the monthly charts"""
_period = {'name': 'month', 'duration': 1}
@staticmethod
def get_chart_list(subject):
wcl = subject.weekly_chart_list
months = set()
for l in wcl:
months.add(l.start.replace(day=1, hour=12, minute=0, second=0))
months = list(months)
months.sort()
months[0] = wcl[0].start.replace(hour=12, minute=0, second=0)
months.append(wcl[-1].end.replace(hour=12, minute=0, second=0))
class MonthlyAlbumChart(MonthlyChart): return [MonthlyChart(
pass subject=subject,
start=months[i],
end=months[i+1]
)
for i in xrange(len(months)-1)]
class MonthlyAlbumChart(RollingAlbumChart, MonthlyChart):
"""A class for representing the monthly album charts"""
_chart_type = "album"
class MonthlyArtistChart(RollingArtistChart, MonthlyChart):
"""A class for representing the monthly artist charts"""
_chart_type = "artist"
class MonthlyArtistChart(MonthlyChart): class MonthlyTrackChart(RollingTrackChart, MonthlyChart):
pass """A class for representing the monthly track charts"""
_chart_type = "track"
class MonthlyTrackChart(MonthlyChart): class MonthlyTagChart(RollingTagChart, MonthlyChart):
pass """A class for representing the monthly tag charts"""
_chart_type = "tag"
class MonthlyTagChart(MonthlyChart): class QuaterlyChart(RollingChart):
pass """A class for representing the three monthly charts"""
_period = {'name': 'quater', 'duration': 3}
class ThreeMonthlyChart(RollingChart): class QuaterlyAlbumChart(RollingAlbumChart, QuaterlyChart):
pass """A class for representing the three monthly album charts"""
_chart_type = "album"
class ThreeMonthlyAlbumChart(ThreeMonthlyChart): class QuaterlyArtistChart(RollingArtistChart, QuaterlyChart):
pass """A class for representing the three monthly artist charts"""
_chart_type = "artist"
class ThreeMonthlyArtistChart(ThreeMonthlyChart): class QuaterlyTrackChart(RollingTrackChart, QuaterlyChart):
pass """A class for representing the three monthly track charts"""
_chart_type = "track"
class ThreeMonthlyTrackChart(ThreeMonthlyChart): class QuaterlyTagChart(RollingTagChart, QuaterlyChart):
pass """A class for representing the three monthly tag charts"""
_chart_type = "tag"
class ThreeMonthlyTagChart(ThreeMonthlyChart): class HalfYearlyChart(RollingChart):
pass """A class for representing the six monthly charts"""
_period = {'name': 'half year', 'duration': 6}
class SixMonthlyChart(RollingChart): class HalfYearlyAlbumChart(RollingAlbumChart, HalfYearlyChart):
pass """A class for representing the six monthly album charts"""
_chart_type = "album"
class SixMonthlyAlbumChart(SixMonthlyChart): class HalfYearlyArtistChart(RollingArtistChart, HalfYearlyChart):
pass """A class for representing the six monthly artist charts"""
_chart_type = "artist"
class SixMonthlyArtistChart(SixMonthlyChart): class HalfYearlyTrackChart(RollingTrackChart, HalfYearlyChart):
pass """A class for representing the six monthly track charts"""
_chart_type = "track"
class SixMonthlyTrackChart(SixMonthlyChart): class HalfYearlyTagChart(RollingTagChart, HalfYearlyChart):
pass """A class for representing the six monthly tag charts"""
_chart_type = "tag"
class SixMonthlyTagChart(SixMonthlyChart):
pass
class YearlyChart(RollingChart): class YearlyChart(RollingChart):
pass """A class for representing the yearly charts"""
_period = {'name': 'year', 'duration': 12}
class YearlyAlbumChart(YearlyChart): class YearlyAlbumChart(RollingAlbumChart, YearlyChart):
pass """A class for representing the yearly album charts"""
_chart_type = "album"
class YearlyArtistChart(YearlyChart): class YearlyArtistChart(RollingArtistChart, YearlyChart):
pass """A class for representing the yearly artist charts"""
_chart_type = "artist"
class YearlyTrackChart(YearlyChart): class YearlyTrackChart(RollingTrackChart, YearlyChart):
pass """A class for representing the yearly track charts"""
_chart_type = "track"
class YearlyTagChart(YearlyChart): class YearlyTagChart(RollingTagChart, YearlyChart):
pass """A class for representing the yearly tag charts"""
_chart_type = "tag"
__all__ = [ __all__ = [
'WeeklyChart', 'WeeklyChart',
'WeeklyAlbumChart', 'WeeklyArtistChart', 'WeeklyTrackChart', 'WeeklyTagChart', 'WeeklyAlbumChart', 'WeeklyArtistChart', 'WeeklyTrackChart', 'WeeklyTagChart',
'MonthlyChart', 'MonthlyChart',
'MonthlyAlbumChart', 'MonthlyArtistChart', 'MonthlyTrackChart', 'MonthlyTagChart', 'MonthlyAlbumChart', 'MonthlyArtistChart', 'MonthlyTrackChart', 'MonthlyTagChart',
'ThreeMonthlyChart', 'QuaterlyChart',
'ThreeMonthlyAlbumChart', 'ThreeMonthlyArtistChart', 'ThreeMonthlyTrackChart', 'ThreeMonthlyTagChart', 'QuaterlyAlbumChart', 'QuaterlyArtistChart', 'QuaterlyTrackChart', 'QuaterlyTagChart',
'SixMonthlyChart', 'HalfYearlyChart',
'SixMonthlyAlbumChart', 'SixMonthlyArtistChart', 'SixMonthlyTrackChart', 'SixMonthlyTagChart', 'HalfYearlyAlbumChart', 'HalfYearlyArtistChart', 'HalfYearlyTrackChart', 'HalfYearlyTagChart',
'YearlyChart', 'YearlyChart',
'YearlyAlbumChart', 'YearlyArtistChart', 'YearlyTrackChart', 'YearlyTagChart' 'YearlyAlbumChart', 'YearlyArtistChart', 'YearlyTrackChart', 'YearlyTagChart'
] ]
@ -435,7 +587,7 @@ import calendar
from lastfm.album import Album from lastfm.album import Album
from lastfm.artist import Artist from lastfm.artist import Artist
from lastfm.error import InvalidParametersError from lastfm.error import InvalidParametersError, LastfmError
from lastfm.stats import Stats from lastfm.stats import Stats
from lastfm.track import Track from lastfm.track import Track
from lastfm.tag import Tag from lastfm.tag import Tag

View File

@ -7,11 +7,13 @@ __license__ = "GNU Lesser General Public License"
__package__ = "lastfm" __package__ = "lastfm"
from lastfm.base import LastfmBase from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable from lastfm.mixins import (
from lastfm.lazylist import lazylist Cacheable, AlbumChartable, ArtistChartable,
TrackChartable, TagChartable)
from lastfm.decorators import cached_property, depaginate from lastfm.decorators import cached_property, depaginate
class Group(LastfmBase, Cacheable): class Group(LastfmBase, Cacheable, AlbumChartable,
ArtistChartable, TrackChartable, TagChartable):
"""A class representing a group on last.fm.""" """A class representing a group on last.fm."""
def init(self, api, name = None, **kwargs): def init(self, api, name = None, **kwargs):
""" """
@ -27,6 +29,11 @@ class Group(LastfmBase, Cacheable):
""" """
if not isinstance(api, Api): if not isinstance(api, Api):
raise InvalidParametersError("api reference must be supplied as an argument") raise InvalidParametersError("api reference must be supplied as an argument")
AlbumChartable.init(self, api)
ArtistChartable.init(self, api)
TrackChartable.init(self, api)
TagChartable.init(self, api)
self._api = api self._api = api
self._name = name self._name = name
@ -37,209 +44,6 @@ class Group(LastfmBase, Cacheable):
@rtype: L{str} @rtype: L{str}
""" """
return self._name 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 [
WeeklyChart.create_from_data(self._api, self, c)
for c in data.findall('chart')
]
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_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklyalbumchart')
return WeeklyAlbumChart.create_from_data(self._api, self, data)
@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
def gen(lst):
for wc in wcl:
yield self.get_weekly_album_chart(wc.start, wc.end)
return gen()
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_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklyartistchart')
return WeeklyArtistChart.create_from_data(self._api, self, data)
@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
def gen(lst):
for wc in wcl:
yield self.get_weekly_artist_chart(wc.start, wc.end)
return gen()
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_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklytrackchart')
return WeeklyTrackChart.create_from_data(self._api, self, data)
@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
def gen(lst):
for wc in wcl:
yield self.get_weekly_track_chart(wc.start, wc.end)
return gen()
def get_weekly_tag_chart(self, start = None, end = None):
"""
Get a tag chart for the group, for a given date range.
If no date range is supplied, it will return the most
recent tag 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 tag chart for the group
@rtype: L{WeeklyTagChart}
@raise InvalidParametersError: Both start and end parameter have to be either
provided or not provided. Providing only one of
them will raise an exception.
@note: This method is a composite method. It is not provided directly by the
last.fm API. It uses other methods to collect the data, analyzes it and
creates a chart. So this method is a little heavy to call, as it does
mulitple calls to the API.
"""
WeeklyChart._check_chart_params({}, start, end)
return WeeklyTagChart.create_from_data(self._api, self, start, end)
@cached_property
def recent_weekly_tag_chart(self):
"""
most recent tag chart for the group
@rtype: L{WeeklyTagChart}
"""
return self.get_weekly_tag_chart()
@cached_property
def weekly_tag_chart_list(self):
"""
a list of all tag charts for this group in reverse-chronological
order. (that means 0th chart is the most recent chart)
@rtype: L{lazylist} of L{WeeklyTagChart}
"""
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_tag_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
@cached_property @cached_property
@depaginate @depaginate

View File

@ -10,5 +10,8 @@ from lastfm.mixins.searchable import Searchable
from lastfm.mixins.sharable import Sharable from lastfm.mixins.sharable import Sharable
from lastfm.mixins.shoutable import Shoutable from lastfm.mixins.shoutable import Shoutable
from lastfm.mixins.taggable import Taggable from lastfm.mixins.taggable import Taggable
from lastfm.mixins.chartable import (
AlbumChartable, ArtistChartable, TrackChartable, TagChartable)
__all__ = ['Cacheable', 'Searchable', 'Sharable', 'Shoutable', 'Taggable'] __all__ = ['Cacheable', 'Searchable', 'Sharable', 'Shoutable', 'Taggable'
'AlbumChartable', 'ArtistChartable', 'TrackChartable', 'TagChartable']

432
lastfm/mixins/chartable.py Normal file
View File

@ -0,0 +1,432 @@
#!/usr/bin/env python
__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm.mixins"
from lastfm.lazylist import lazylist
from lastfm.decorators import cached_property
class Chartable(object):
def init(self, api):
self._api = api
@cached_property
def weekly_chart_list(self):
"""
a list of available weekly charts for this group
@rtype: L{list} of L{WeeklyChart}
"""
from lastfm.chart import WeeklyChart
params = self._default_params(
{'method': '%s.getWeeklyChartList' % self.__class__.__name__.lower()})
data = self._api._fetch_data(params).find('weeklychartlist')
return [
WeeklyChart.create_from_data(self._api, self, c)
for c in data.findall('chart')
]
@cached_property
def monthly_chart_list(self):
from lastfm.chart import MonthlyChart
return MonthlyChart.get_chart_list(self)
def _default_params(self, extra_params = None):
if extra_params is not None:
return extra_params
else:
return {}
class AlbumChartable(Chartable):
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.
"""
from lastfm.chart import WeeklyChart, WeeklyAlbumChart
params = self._default_params(
{'method': '%s.getWeeklyAlbumChart' % self.__class__.__name__.lower()})
params = WeeklyChart._check_chart_params(params, self, start, end)
data = self._api._fetch_data(params).find('weeklyalbumchart')
return WeeklyAlbumChart.create_from_data(self._api, self, data)
@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
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_album_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
def get_monthly_album_chart(self, start = None, end = None):
from lastfm.chart import MonthlyAlbumChart
return MonthlyAlbumChart.create_from_data(self, start, end)
@cached_property
def recent_monthly_album_chart(self):
return self.get_monthly_album_chart()
@cached_property
def monthly_album_chart_list(self):
mcl = list(self.monthly_chart_list)
mcl.reverse()
@lazylist
def gen(lst):
for mc in mcl:
try:
yield self.get_monthly_album_chart(mc.start, mc.end)
except LastfmError:
pass
return gen()
def get_quaterly_album_chart(self, start = None, end = None):
from lastfm.chart import QuaterlyAlbumChart
return QuaterlyAlbumChart.create_from_data(self, start, end)
@cached_property
def recent_quaterly_album_chart(self):
return self.get_quaterly_album_chart()
def get_half_yearly_album_chart(self, start = None, end = None):
from lastfm.chart import HalfYearlyAlbumChart
return HalfYearlyAlbumChart.create_from_data(self, start, end)
@cached_property
def recent_half_yearly_album_chart(self):
return self.get_half_yearly_album_chart()
def get_yearly_album_chart(self, start = None, end = None):
from lastfm.chart import YearlyAlbumChart
return YearlyAlbumChart.create_from_data(self, start, end)
@cached_property
def recent_yearly_album_chart(self):
return self.get_yearly_album_chart()
class ArtistChartable(Chartable):
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.
"""
from lastfm.chart import WeeklyChart, WeeklyArtistChart
params = self._default_params(
{'method': '%s.getWeeklyArtistChart' % self.__class__.__name__.lower()})
params = WeeklyChart._check_chart_params(params, self, start, end)
data = self._api._fetch_data(params).find('weeklyartistchart')
return WeeklyArtistChart.create_from_data(self._api, self, data)
@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
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_artist_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
def get_monthly_artist_chart(self, start = None, end = None):
from lastfm.chart import MonthlyArtistChart
return MonthlyArtistChart.create_from_data(self, start, end)
@cached_property
def recent_monthly_artist_chart(self):
return self.get_monthly_artist_chart()
@cached_property
def monthly_artist_chart_list(self):
mcl = list(self.monthly_chart_list)
mcl.reverse()
@lazylist
def gen(lst):
for mc in mcl:
try:
yield self.get_monthly_artist_chart(mc.start, mc.end)
except LastfmError:
pass
return gen()
def get_quaterly_artist_chart(self, start = None, end = None):
from lastfm.chart import QuaterlyArtistChart
return QuaterlyArtistChart.create_from_data(self, start, end)
@cached_property
def recent_quaterly_artist_chart(self):
return self.get_quaterly_artist_chart()
def get_half_yearly_artist_chart(self, start = None, end = None):
from lastfm.chart import HalfYearlyArtistChart
return HalfYearlyArtistChart.create_from_data(self, start, end)
@cached_property
def recent_half_yearly_artist_chart(self):
return self.get_half_yearly_artist_chart()
def get_yearly_artist_chart(self, start = None, end = None):
from lastfm.chart import YearlyArtistChart
return YearlyArtistChart.create_from_data(self, start, end)
@cached_property
def recent_yearly_album_chart(self):
return self.get_yearly_artist_chart()
class TrackChartable(Chartable):
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.
"""
from lastfm.chart import WeeklyChart, WeeklyTrackChart
params = self._default_params(
{'method': '%s.getWeeklyTrackChart' % self.__class__.__name__.lower()})
params = WeeklyChart._check_chart_params(params, self, start, end)
data = self._api._fetch_data(params).find('weeklytrackchart')
return WeeklyTrackChart.create_from_data(self._api, self, data)
@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
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_track_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
def get_monthly_track_chart(self, start = None, end = None):
from lastfm.chart import MonthlyTrackChart
return MonthlyTrackChart.create_from_data(self, start, end)
@cached_property
def recent_monthly_track_chart(self):
return self.get_monthly_track_chart()
@cached_property
def monthly_track_chart_list(self):
mcl = list(self.monthly_chart_list)
mcl.reverse()
@lazylist
def gen(lst):
for mc in mcl:
try:
yield self.get_monthly_track_chart(mc.start, mc.end)
except LastfmError:
pass
return gen()
def get_quaterly_track_chart(self, start = None, end = None):
from lastfm.chart import QuaterlyTrackChart
return QuaterlyTrackChart.create_from_data(self, start, end)
@cached_property
def recent_quaterly_track_chart(self):
return self.get_quaterly_track_chart()
def get_half_yearly_track_chart(self, start = None, end = None):
from lastfm.chart import HalfYearlyTrackChart
return HalfYearlyTrackChart.create_from_data(self, start, end)
@cached_property
def recent_half_yearly_artist_chart(self):
return self.get_half_yearly_track_chart()
def get_yearly_track_chart(self, start = None, end = None):
from lastfm.chart import YearlyTrackChart
return YearlyTrackChart.create_from_data(self, start, end)
@cached_property
def recent_yearly_album_chart(self):
return self.get_yearly_track_chart()
class TagChartable(Chartable):
def get_weekly_tag_chart(self, start = None, end = None):
"""
Get a tag chart for the group, for a given date range.
If no date range is supplied, it will return the most
recent tag 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 tag chart for the group
@rtype: L{WeeklyTagChart}
@raise InvalidParametersError: Both start and end parameter have to be either
provided or not provided. Providing only one of
them will raise an exception.
@note: This method is a composite method. It is not provided directly by the
last.fm API. It uses other methods to collect the data, analyzes it and
creates a chart. So this method is a little heavy to call, as it does
mulitple calls to the API.
"""
from lastfm.chart import WeeklyChart, WeeklyTagChart
WeeklyChart._check_chart_params({}, self, start, end)
return WeeklyTagChart.create_from_data(self._api, self, start, end)
@cached_property
def recent_weekly_tag_chart(self):
"""
most recent tag chart for the group
@rtype: L{WeeklyTagChart}
"""
return self.get_weekly_tag_chart()
@cached_property
def weekly_tag_chart_list(self):
"""
a list of all tag charts for this group in reverse-chronological
order. (that means 0th chart is the most recent chart)
@rtype: L{lazylist} of L{WeeklyTagChart}
"""
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_tag_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
def get_monthly_tag_chart(self, start = None, end = None):
from lastfm.chart import MonthlyTagChart
return MonthlyTagChart.create_from_data(self, start, end)
@cached_property
def recent_monthly_tag_chart(self):
return self.get_monthly_tag_chart()
@cached_property
def monthly_tag_chart_list(self):
mcl = list(self.monthly_chart_list)
mcl.reverse()
@lazylist
def gen(lst):
for mc in mcl:
try:
yield self.get_monthly_tag_chart(mc.start, mc.end)
except LastfmError:
pass
return gen()
def get_quaterly_tag_chart(self, start = None, end = None):
from lastfm.chart import QuaterlyTagChart
return QuaterlyTagChart.create_from_data(self, start, end)
@cached_property
def recent_quaterly_tag_chart(self):
return self.get_quaterly_tag_chart()
def get_half_yearly_tag_chart(self, start = None, end = None):
from lastfm.chart import HalfYearlyTagChart
return HalfYearlyTagChart.create_from_data(self, start, end)
@cached_property
def recent_half_yearly_artist_chart(self):
return self.get_half_yearly_tag_chart()
def get_yearly_tag_chart(self, start = None, end = None):
from lastfm.chart import YearlyTagChart
return YearlyTagChart.create_from_data(self, start, end)
@cached_property
def recent_yearly_album_chart(self):
return self.get_yearly_tag_chart()
from lastfm.error import LastfmError

View File

@ -6,11 +6,11 @@ __license__ = "GNU Lesser General Public License"
__package__ = "lastfm" __package__ = "lastfm"
from lastfm.base import LastfmBase from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Searchable from lastfm.mixins import (
from lastfm.lazylist import lazylist Cacheable, Searchable, ArtistChartable)
from lastfm.decorators import cached_property, top_property from lastfm.decorators import cached_property, top_property
class Tag(LastfmBase, Cacheable, Searchable): class Tag(LastfmBase, Cacheable, Searchable, ArtistChartable):
"""A class representing a tag.""" """A class representing a tag."""
def init(self, def init(self,
api, api,
@ -21,6 +21,8 @@ class Tag(LastfmBase, Cacheable, Searchable):
**kwargs): **kwargs):
if not isinstance(api, Api): if not isinstance(api, Api):
raise InvalidParametersError("api reference must be supplied as an argument") raise InvalidParametersError("api reference must be supplied as an argument")
ArtistChartable.init(self, api)
self._api = api self._api = api
self._name = name self._name = name
self._url = url self._url = url
@ -172,44 +174,7 @@ class Tag(LastfmBase, Cacheable, Searchable):
def playlist(self): def playlist(self):
return Playlist.fetch(self._api, return Playlist.fetch(self._api,
"lastfm://playlist/tag/%s/freetracks" % self.name) "lastfm://playlist/tag/%s/freetracks" % self.name)
@cached_property
def weekly_chart_list(self):
params = self._default_params({'method': 'tag.getWeeklyChartList'})
data = self._api._fetch_data(params).find('weeklychartlist')
return [
WeeklyChart.create_from_data(self._api, self, c)
for c in data.findall('chart')
]
def get_weekly_artist_chart(self,
start = None,
end = None,
limit = None):
params = self._default_params({'method': 'tag.getWeeklyArtistChart'})
if limit is not None:
params['limit'] = limit
params = WeeklyArtistChart._check_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklyartistchart')
return WeeklyArtistChart.create_from_data(self._api, self, data)
@cached_property
def recent_weekly_artist_chart(self):
return self.get_weekly_artist_chart()
@cached_property
def weekly_artist_chart_list(self):
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_artist_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
@staticmethod @staticmethod
def get_top_tags(api): def get_top_tags(api):
params = {'method': 'tag.getTopTags'} params = {'method': 'tag.getTopTags'}

View File

@ -6,12 +6,16 @@ __license__ = "GNU Lesser General Public License"
__package__ = "lastfm" __package__ = "lastfm"
from lastfm.base import LastfmBase from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Shoutable from lastfm.mixins import (
from lastfm.lazylist import lazylist Cacheable, Shoutable, AlbumChartable,
ArtistChartable, TrackChartable, TagChartable)
import lastfm.playlist import lastfm.playlist
from lastfm.decorators import cached_property, top_property, authentication_required, depaginate from lastfm.decorators import (
cached_property, top_property, authentication_required, depaginate)
class User(LastfmBase, Cacheable, Shoutable): class User(LastfmBase, Cacheable, Shoutable,
AlbumChartable, ArtistChartable,
TrackChartable, TagChartable):
"""A class representing an user.""" """A class representing an user."""
def init(self, def init(self,
api, api,
@ -24,6 +28,11 @@ class User(LastfmBase, Cacheable, Shoutable):
if not isinstance(api, Api): if not isinstance(api, Api):
raise InvalidParametersError("api reference must be supplied as an argument") raise InvalidParametersError("api reference must be supplied as an argument")
Shoutable.init(self, api) Shoutable.init(self, api)
AlbumChartable.init(self, api)
ArtistChartable.init(self, api)
TrackChartable.init(self, api)
TagChartable.init(self, api)
self._api = api self._api = api
self._name = name self._name = name
self._real_name = real_name self._real_name = real_name
@ -478,113 +487,6 @@ class User(LastfmBase, Cacheable, Shoutable):
"""top tag of the user""" """top tag of the user"""
pass pass
@cached_property
def weekly_chart_list(self):
params = self._default_params({'method': 'user.getWeeklyChartList'})
data = self._api._fetch_data(params).find('weeklychartlist')
return [
WeeklyChart.create_from_data(self._api, self, c)
for c in data.findall('chart')
]
def get_weekly_album_chart(self,
start = None,
end = None):
params = self._default_params({'method': 'user.getWeeklyAlbumChart'})
params = WeeklyChart._check_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklyalbumchart')
return WeeklyAlbumChart.create_from_data(self._api, self, data)
@cached_property
def recent_weekly_album_chart(self):
return self.get_weekly_album_chart()
@cached_property
def weekly_album_chart_list(self):
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_album_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
def get_weekly_artist_chart(self,
start = None,
end = None):
params = self._default_params({'method': 'user.getWeeklyArtistChart'})
params = WeeklyChart._check_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklyartistchart')
return WeeklyArtistChart.create_from_data(self._api, self, data)
@cached_property
def recent_weekly_artist_chart(self):
return self.get_weekly_artist_chart()
@cached_property
def weekly_artist_chart_list(self):
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_artist_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
def get_weekly_track_chart(self,
start = None,
end = None):
params = self._default_params({'method': 'user.getWeeklyTrackChart'})
params = WeeklyChart._check_chart_params(params, start, end)
data = self._api._fetch_data(params).find('weeklytrackchart')
return WeeklyTrackChart.create_from_data(self._api, self, data)
@cached_property
def recent_weekly_track_chart(self):
return self.get_weekly_track_chart()
@cached_property
def weekly_track_chart_list(self):
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_track_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
def get_weekly_tag_chart(self,
start = None,
end = None):
WeeklyChart._check_chart_params({}, start, end)
return WeeklyTagChart.create_from_data(self._api, self, start, end)
@cached_property
def recent_weekly_tag_chart(self):
return self.get_weekly_tag_chart()
@cached_property
def weekly_tag_chart_list(self):
wcl = list(self.weekly_chart_list)
wcl.reverse()
@lazylist
def gen(lst):
for wc in wcl:
try:
yield self.get_weekly_tag_chart(wc.start, wc.end)
except LastfmError:
pass
return gen()
def compare(self, other, limit = None): def compare(self, other, limit = None):
if isinstance(other, User): if isinstance(other, User):
other = other.name other = other.name
@ -920,4 +822,3 @@ from lastfm.stats import Stats
from lastfm.tag import Tag from lastfm.tag import Tag
from lastfm.tasteometer import Tasteometer from lastfm.tasteometer import Tasteometer
from lastfm.track import Track from lastfm.track import Track
from lastfm.chart import WeeklyChart, WeeklyAlbumChart, WeeklyArtistChart, WeeklyTrackChart, WeeklyTagChart