diff --git a/lastfm/chart.py b/lastfm/chart.py index 3119c19..0398e4b 100644 --- a/lastfm/chart.py +++ b/lastfm/chart.py @@ -10,7 +10,7 @@ from lastfm.mixins import Cacheable from operator import xor 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): self._subject = subject @@ -35,26 +35,16 @@ class Chart(LastfmBase, Cacheable): return self._stats @staticmethod - def create_from_data(api, subject, data): - 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): + def _check_chart_params(params, subject, start = None, end = 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): - params.update({ - 'from': int(calendar.timegm(start.timetuple())), - 'to': int(calendar.timegm(end.timetuple())) - }) - else: + if not (isinstance(start, datetime) and isinstance(end, datetime)): 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 @staticmethod @@ -71,10 +61,10 @@ class Chart(LastfmBase, Cacheable): def __hash__(self): return self.__class__._hash_func( - subject = self.subject, - start = self.start, - end = self.end - ) + subject = self.subject, + start = self.start, + end = self.end + ) def __eq__(self, other): return self.subject == other.subject and \ @@ -99,21 +89,68 @@ class Chart(LastfmBase, Cacheable): self.start.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): - """A class for representing the weekly album charts""" +class AlbumChart(Chart): 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 @property def albums(self): 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 def create_from_data(api, subject, data): w = WeeklyChart( @@ -158,16 +195,8 @@ class WeeklyAlbumChart(WeeklyChart): ] ) -class WeeklyArtistChart(WeeklyChart): +class WeeklyArtistChart(ArtistChart, WeeklyChart): """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 def create_from_data(api, subject, data): w = WeeklyChart( @@ -208,16 +237,8 @@ class WeeklyArtistChart(WeeklyChart): ] ) -class WeeklyTrackChart(WeeklyChart): +class WeeklyTrackChart(TrackChart, WeeklyChart): """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 def create_from_data(api, subject, data): w = WeeklyChart( @@ -261,16 +282,8 @@ class WeeklyTrackChart(WeeklyChart): ] ) -class WeeklyTagChart(WeeklyChart): +class WeeklyTagChart(TagChart, WeeklyChart): """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 def create_from_data(api, subject, start, end): w = WeeklyChart( @@ -356,77 +369,216 @@ class WeeklyTagChart(WeeklyChart): return wtc 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): - 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): - pass + return [MonthlyChart( + 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): - pass +class MonthlyTrackChart(RollingTrackChart, MonthlyChart): + """A class for representing the monthly track charts""" + _chart_type = "track" -class MonthlyTrackChart(MonthlyChart): - pass +class MonthlyTagChart(RollingTagChart, MonthlyChart): + """A class for representing the monthly tag charts""" + _chart_type = "tag" -class MonthlyTagChart(MonthlyChart): - pass +class QuaterlyChart(RollingChart): + """A class for representing the three monthly charts""" + _period = {'name': 'quater', 'duration': 3} -class ThreeMonthlyChart(RollingChart): - pass +class QuaterlyAlbumChart(RollingAlbumChart, QuaterlyChart): + """A class for representing the three monthly album charts""" + _chart_type = "album" -class ThreeMonthlyAlbumChart(ThreeMonthlyChart): - pass +class QuaterlyArtistChart(RollingArtistChart, QuaterlyChart): + """A class for representing the three monthly artist charts""" + _chart_type = "artist" -class ThreeMonthlyArtistChart(ThreeMonthlyChart): - pass +class QuaterlyTrackChart(RollingTrackChart, QuaterlyChart): + """A class for representing the three monthly track charts""" + _chart_type = "track" -class ThreeMonthlyTrackChart(ThreeMonthlyChart): - pass +class QuaterlyTagChart(RollingTagChart, QuaterlyChart): + """A class for representing the three monthly tag charts""" + _chart_type = "tag" -class ThreeMonthlyTagChart(ThreeMonthlyChart): - pass +class HalfYearlyChart(RollingChart): + """A class for representing the six monthly charts""" + _period = {'name': 'half year', 'duration': 6} -class SixMonthlyChart(RollingChart): - pass +class HalfYearlyAlbumChart(RollingAlbumChart, HalfYearlyChart): + """A class for representing the six monthly album charts""" + _chart_type = "album" -class SixMonthlyAlbumChart(SixMonthlyChart): - pass +class HalfYearlyArtistChart(RollingArtistChart, HalfYearlyChart): + """A class for representing the six monthly artist charts""" + _chart_type = "artist" -class SixMonthlyArtistChart(SixMonthlyChart): - pass +class HalfYearlyTrackChart(RollingTrackChart, HalfYearlyChart): + """A class for representing the six monthly track charts""" + _chart_type = "track" -class SixMonthlyTrackChart(SixMonthlyChart): - pass - -class SixMonthlyTagChart(SixMonthlyChart): - pass +class HalfYearlyTagChart(RollingTagChart, HalfYearlyChart): + """A class for representing the six monthly tag charts""" + _chart_type = "tag" class YearlyChart(RollingChart): - pass + """A class for representing the yearly charts""" + _period = {'name': 'year', 'duration': 12} -class YearlyAlbumChart(YearlyChart): - pass +class YearlyAlbumChart(RollingAlbumChart, YearlyChart): + """A class for representing the yearly album charts""" + _chart_type = "album" -class YearlyArtistChart(YearlyChart): - pass +class YearlyArtistChart(RollingArtistChart, YearlyChart): + """A class for representing the yearly artist charts""" + _chart_type = "artist" -class YearlyTrackChart(YearlyChart): - pass +class YearlyTrackChart(RollingTrackChart, YearlyChart): + """A class for representing the yearly track charts""" + _chart_type = "track" -class YearlyTagChart(YearlyChart): - pass +class YearlyTagChart(RollingTagChart, YearlyChart): + """A class for representing the yearly tag charts""" + _chart_type = "tag" __all__ = [ 'WeeklyChart', 'WeeklyAlbumChart', 'WeeklyArtistChart', 'WeeklyTrackChart', 'WeeklyTagChart', 'MonthlyChart', 'MonthlyAlbumChart', 'MonthlyArtistChart', 'MonthlyTrackChart', 'MonthlyTagChart', - 'ThreeMonthlyChart', - 'ThreeMonthlyAlbumChart', 'ThreeMonthlyArtistChart', 'ThreeMonthlyTrackChart', 'ThreeMonthlyTagChart', - 'SixMonthlyChart', - 'SixMonthlyAlbumChart', 'SixMonthlyArtistChart', 'SixMonthlyTrackChart', 'SixMonthlyTagChart', + 'QuaterlyChart', + 'QuaterlyAlbumChart', 'QuaterlyArtistChart', 'QuaterlyTrackChart', 'QuaterlyTagChart', + 'HalfYearlyChart', + 'HalfYearlyAlbumChart', 'HalfYearlyArtistChart', 'HalfYearlyTrackChart', 'HalfYearlyTagChart', 'YearlyChart', 'YearlyAlbumChart', 'YearlyArtistChart', 'YearlyTrackChart', 'YearlyTagChart' ] @@ -435,7 +587,7 @@ import calendar from lastfm.album import Album from lastfm.artist import Artist -from lastfm.error import InvalidParametersError +from lastfm.error import InvalidParametersError, LastfmError from lastfm.stats import Stats from lastfm.track import Track from lastfm.tag import Tag \ No newline at end of file diff --git a/lastfm/group.py b/lastfm/group.py index 2742c06..79151cb 100644 --- a/lastfm/group.py +++ b/lastfm/group.py @@ -7,11 +7,13 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable -from lastfm.lazylist import lazylist +from lastfm.mixins import ( + Cacheable, AlbumChartable, ArtistChartable, + TrackChartable, TagChartable) 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.""" def init(self, api, name = None, **kwargs): """ @@ -27,6 +29,11 @@ class Group(LastfmBase, Cacheable): """ if not isinstance(api, Api): 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._name = name @@ -37,209 +44,6 @@ class Group(LastfmBase, Cacheable): @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 [ - 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 @depaginate diff --git a/lastfm/mixins/__init__.py b/lastfm/mixins/__init__.py index ddfd1d8..c627af6 100644 --- a/lastfm/mixins/__init__.py +++ b/lastfm/mixins/__init__.py @@ -10,5 +10,8 @@ from lastfm.mixins.searchable import Searchable from lastfm.mixins.sharable import Sharable from lastfm.mixins.shoutable import Shoutable from lastfm.mixins.taggable import Taggable +from lastfm.mixins.chartable import ( + AlbumChartable, ArtistChartable, TrackChartable, TagChartable) -__all__ = ['Cacheable', 'Searchable', 'Sharable', 'Shoutable', 'Taggable'] \ No newline at end of file +__all__ = ['Cacheable', 'Searchable', 'Sharable', 'Shoutable', 'Taggable' + 'AlbumChartable', 'ArtistChartable', 'TrackChartable', 'TagChartable'] \ No newline at end of file diff --git a/lastfm/mixins/chartable.py b/lastfm/mixins/chartable.py new file mode 100644 index 0000000..e036b14 --- /dev/null +++ b/lastfm/mixins/chartable.py @@ -0,0 +1,432 @@ +#!/usr/bin/env python + +__author__ = "Abhinav Sarkar " +__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 + \ No newline at end of file diff --git a/lastfm/tag.py b/lastfm/tag.py index e58a2d7..230d7a2 100644 --- a/lastfm/tag.py +++ b/lastfm/tag.py @@ -6,11 +6,11 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable, Searchable -from lastfm.lazylist import lazylist +from lastfm.mixins import ( + Cacheable, Searchable, ArtistChartable) from lastfm.decorators import cached_property, top_property -class Tag(LastfmBase, Cacheable, Searchable): +class Tag(LastfmBase, Cacheable, Searchable, ArtistChartable): """A class representing a tag.""" def init(self, api, @@ -21,6 +21,8 @@ class Tag(LastfmBase, Cacheable, Searchable): **kwargs): if not isinstance(api, Api): raise InvalidParametersError("api reference must be supplied as an argument") + ArtistChartable.init(self, api) + self._api = api self._name = name self._url = url @@ -172,44 +174,7 @@ class Tag(LastfmBase, Cacheable, Searchable): def playlist(self): return Playlist.fetch(self._api, "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 def get_top_tags(api): params = {'method': 'tag.getTopTags'} diff --git a/lastfm/user.py b/lastfm/user.py index 46d3e8b..7fc2f60 100644 --- a/lastfm/user.py +++ b/lastfm/user.py @@ -6,12 +6,16 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable, Shoutable -from lastfm.lazylist import lazylist +from lastfm.mixins import ( + Cacheable, Shoutable, AlbumChartable, + ArtistChartable, TrackChartable, TagChartable) 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.""" def init(self, api, @@ -24,6 +28,11 @@ class User(LastfmBase, Cacheable, Shoutable): if not isinstance(api, Api): raise InvalidParametersError("api reference must be supplied as an argument") Shoutable.init(self, api) + AlbumChartable.init(self, api) + ArtistChartable.init(self, api) + TrackChartable.init(self, api) + TagChartable.init(self, api) + self._api = api self._name = name self._real_name = real_name @@ -478,113 +487,6 @@ class User(LastfmBase, Cacheable, Shoutable): """top tag of the user""" 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): if isinstance(other, User): other = other.name @@ -920,4 +822,3 @@ from lastfm.stats import Stats from lastfm.tag import Tag from lastfm.tasteometer import Tasteometer from lastfm.track import Track -from lastfm.chart import WeeklyChart, WeeklyAlbumChart, WeeklyArtistChart, WeeklyTrackChart, WeeklyTagChart