diff --git a/lastfm/album.py b/lastfm/album.py index daa6fbe..e05f770 100644 --- a/lastfm/album.py +++ b/lastfm/album.py @@ -7,10 +7,13 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable, Searchable, Taggable +from lastfm.mixins import cacheable, searchable, taggable from lastfm.decorators import cached_property, top_property -class Album(LastfmBase, Cacheable, Searchable, Taggable): +@taggable +@searchable +@cacheable +class Album(LastfmBase): """A class representing an album.""" def init(self, api, @@ -58,7 +61,6 @@ class Album(LastfmBase, Cacheable, Searchable, Taggable): """ if not isinstance(api, Api): raise InvalidParametersError("api reference must be supplied as an argument") - Taggable.init(self, api) self._api = api self._name = name self._artist = artist diff --git a/lastfm/artist.py b/lastfm/artist.py index a88d5d4..d36df00 100644 --- a/lastfm/artist.py +++ b/lastfm/artist.py @@ -7,10 +7,15 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable, Searchable, Sharable, Shoutable, Taggable +from lastfm.mixins import cacheable, searchable, sharable, shoutable, taggable from lastfm.decorators import cached_property, top_property -class Artist(LastfmBase, Cacheable, Sharable, Shoutable, Searchable, Taggable): +@shoutable +@sharable +@taggable +@searchable +@cacheable +class Artist(LastfmBase): """A class representing an artist.""" def init(self, api, @@ -55,9 +60,6 @@ class Artist(LastfmBase, Cacheable, Sharable, Shoutable, Searchable, Taggable): """ if not isinstance(api, Api): raise InvalidParametersError("api reference must be supplied as an argument") - Sharable.init(self, api) - Shoutable.init(self, api) - Taggable.init(self, api) self._api = api self._name = name @@ -402,16 +404,16 @@ class Artist(LastfmBase, Cacheable, Sharable, Shoutable, Searchable, Taggable): listeners = int(data.findtext('stats/listeners')), playcount = int(data.findtext('stats/playcount')) ) - self._similar = [ - Artist( - self._api, - subject = self, - name = a.findtext('name'), - url = a.findtext('url'), - image = dict([(i.get('size'), i.text) for i in a.findall('image')]) - ) - for a in data.findall('similar/artist') - ] +# self._similar = [ +# Artist( +# self._api, +# subject = self, +# name = a.findtext('name'), +# url = a.findtext('url'), +# image = dict([(i.get('size'), i.text) for i in a.findall('image')]) +# ) +# for a in data.findall('similar/artist') +# ] self._top_tags = [ Tag( self._api, diff --git a/lastfm/chart.py b/lastfm/chart.py index 0398e4b..9d61431 100644 --- a/lastfm/chart.py +++ b/lastfm/chart.py @@ -6,10 +6,11 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable +from lastfm.mixins import cacheable from operator import xor -class Chart(LastfmBase, Cacheable): +@cacheable +class Chart(LastfmBase): """The base class for all the chart classes""" def init(self, subject, start, end, stats = None): @@ -435,7 +436,7 @@ class RollingChart(Chart): end = end, stats = Stats( subject = subject, - **{count_attribute[1:]: sum([a.stats.__dict__[count_attribute] for a in items])} + **{count_attribute[1:]: sum(a.stats.__dict__[count_attribute] for a in items)} ), **{"%ss" % chart_type: items} ) @@ -467,7 +468,7 @@ class RollingTagChart(TagChart): 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]) + count_sum = sum(t.stats.count for t in chart.tags) for t in chart.tags: t.stats.__dict__['_count'] /= count_sum return chart diff --git a/lastfm/event.py b/lastfm/event.py index e99b858..a402118 100644 --- a/lastfm/event.py +++ b/lastfm/event.py @@ -7,9 +7,12 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable, Sharable, Shoutable +from lastfm.mixins import cacheable, sharable, shoutable -class Event(LastfmBase, Cacheable, Sharable, Shoutable): +@shoutable +@sharable +@cacheable +class Event(LastfmBase): """A class representing an event.""" STATUS_ATTENDING = 0 STATUS_MAYBE = 1 @@ -59,8 +62,6 @@ class Event(LastfmBase, Cacheable, Sharable, Shoutable): """ if not isinstance(api, Api): raise InvalidParametersError("api reference must be supplied as an argument") - Sharable.init(self, api) - Shoutable.init(self, api) self._api = api self._id = id diff --git a/lastfm/geo.py b/lastfm/geo.py index 11837d6..ae2029b 100644 --- a/lastfm/geo.py +++ b/lastfm/geo.py @@ -7,7 +7,7 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable +from lastfm.mixins import cacheable from lastfm.decorators import cached_property, top_property, depaginate class Geo(object): @@ -145,7 +145,8 @@ class Geo(object): for t in data.findall('track') ] -class Location(LastfmBase, Cacheable): +@cacheable +class Location(LastfmBase): """A class representing a location of an event""" XMLNS = "http://www.w3.org/2003/01/geo/wgs84_pos#" @@ -324,7 +325,8 @@ class Location(LastfmBase, Cacheable): else: return "" % self.city -class Country(LastfmBase, Cacheable): +@cacheable +class Country(LastfmBase): """A class representing a country.""" ISO_CODES = { 'AD': 'Andorra', diff --git a/lastfm/group.py b/lastfm/group.py index 79151cb..ca4658c 100644 --- a/lastfm/group.py +++ b/lastfm/group.py @@ -7,13 +7,12 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import ( - Cacheable, AlbumChartable, ArtistChartable, - TrackChartable, TagChartable) +from lastfm.mixins import cacheable, chartable from lastfm.decorators import cached_property, depaginate -class Group(LastfmBase, Cacheable, AlbumChartable, - ArtistChartable, TrackChartable, TagChartable): +@chartable(['album', 'artist', 'track', 'tag']) +@cacheable +class Group(LastfmBase): """A class representing a group on last.fm.""" def init(self, api, name = None, **kwargs): """ @@ -29,11 +28,7 @@ class Group(LastfmBase, Cacheable, AlbumChartable, """ 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 diff --git a/lastfm/mixins/__init__.py b/lastfm/mixins/__init__.py index c627af6..5653a86 100644 --- a/lastfm/mixins/__init__.py +++ b/lastfm/mixins/__init__.py @@ -5,13 +5,12 @@ __version__ = "0.2" __license__ = "GNU Lesser General Public License" __package__ = "lastfm.mixins" -from lastfm.mixins.cacheable import Cacheable -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) +from lastfm.mixins._cacheable import cacheable +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 chartable -__all__ = ['Cacheable', 'Searchable', 'Sharable', 'Shoutable', 'Taggable' - 'AlbumChartable', 'ArtistChartable', 'TrackChartable', 'TagChartable'] \ No newline at end of file +__all__ = ['cacheable', 'searchable', 'sharable', 'shoutable', 'taggable' + 'chartable'] \ No newline at end of file diff --git a/lastfm/mixins/cacheable.py b/lastfm/mixins/_cacheable.py similarity index 55% rename from lastfm/mixins/cacheable.py rename to lastfm/mixins/_cacheable.py index e5df5a9..6ae87d0 100644 --- a/lastfm/mixins/cacheable.py +++ b/lastfm/mixins/_cacheable.py @@ -10,11 +10,25 @@ try: except ImportError: from dummy_threading import Lock -class Cacheable(object): - registry = {} - _lock = Lock() +registry = {} +_lock = Lock() +def register(ob, key): + if not ob.__class__ in registry: + registry[ob.__class__] = {} + if key in registry[ob.__class__]: + ob = registry[ob.__class__][key] + #print "already registered: %s" % repr(ob) + return (ob, True) + else: + #print "not already registered: %s" % ob.__class__ + registry[ob.__class__][key] = ob + return (ob, False) + +def cacheable(cls): + @classmethod def __new__(cls, *args, **kwds): + args = args[1:] subject = None if 'subject' in kwds and not cls.__name__.startswith('Weekly'): subject = kwds['subject'] @@ -29,29 +43,19 @@ class Cacheable(object): key = cls._hash_func(*args, **kwds) if subject is not None: key = (hash(subject), key) - - Cacheable._lock.acquire() - try: - inst, already_registered = Cacheable.register(object.__new__(cls), key) + + with _lock: + inst, already_registered = register(object.__new__(cls), key) if not already_registered: inst.init(*args, **kwds) - finally: - Cacheable._lock.release() return inst - - @staticmethod - def register(ob, key): - if not ob.__class__ in Cacheable.registry: - Cacheable.registry[ob.__class__] = {} - if key in Cacheable.registry[ob.__class__]: - ob = Cacheable.registry[ob.__class__][key] - #print "already registered: %s" % repr(ob) - return (ob, True) - else: - #print "not already registered: %s" % ob.__class__ - Cacheable.registry[ob.__class__][key] = ob - return (ob, False) @staticmethod def _hash_func(*args, **kwds): raise NotImplementedError("The subclass must override this method") + + cls.__new__ = __new__ + if not hasattr(cls, '_hash_func'): + cls._hash_func = _hash_func + + return cls diff --git a/lastfm/mixins/_chartable.py b/lastfm/mixins/_chartable.py new file mode 100644 index 0000000..ebe7771 --- /dev/null +++ b/lastfm/mixins/_chartable.py @@ -0,0 +1,445 @@ +#!/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 + +def chartable(chart_types): + def wrapper(cls): + @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 {} + + 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() + + 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_artist_chart(self): + return self.get_yearly_artist_chart() + + 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_track_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_track_chart(self): + return self.get_yearly_track_chart() + + 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_tag_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_tag_chart(self): + return self.get_yearly_tag_chart() + + cls.weekly_chart_list = weekly_chart_list + cls.monthly_chart_list = monthly_chart_list + if not hasattr(cls, '_default_params'): + cls._default_params = _default_params + + method_names = [ + 'get_weekly_%s_chart', 'recent_weekly_%s_chart', 'weekly_%s_chart_list', + 'get_monthly_%s_chart', 'recent_monthly_%s_chart', 'monthly_%s_chart_list', + 'get_quaterly_%s_chart', 'recent_quaterly_%s_chart', + 'get_half_yearly_%s_chart', 'recent_half_yearly_%s_chart', + 'get_yearly_%s_chart', 'recent_yearly_%s_chart' + ] + for chart_type in chart_types: + for method_name in method_names: + setattr(cls, method_name % chart_type, locals()[method_name % chart_type]) + + return cls + return wrapper + +from lastfm.error import LastfmError + \ No newline at end of file diff --git a/lastfm/mixins/searchable.py b/lastfm/mixins/_searchable.py similarity index 83% rename from lastfm/mixins/searchable.py rename to lastfm/mixins/_searchable.py index eb42a0c..d0e0399 100644 --- a/lastfm/mixins/searchable.py +++ b/lastfm/mixins/_searchable.py @@ -7,7 +7,7 @@ __package__ = "lastfm.mixins" from lastfm.decorators import depaginate -class Searchable(object): +def searchable(cls): @classmethod @depaginate def search(cls, @@ -40,4 +40,10 @@ class Searchable(object): @staticmethod def _search_yield_func(api, search_term): - pass + raise NotImplementedError("the subclass should implement this method") + + cls.search = search + if not hasattr(cls, '_search_yield_func'): + cls._search_yield_func = _search_yield_func + + return cls diff --git a/lastfm/mixins/sharable.py b/lastfm/mixins/_sharable.py similarity index 83% rename from lastfm/mixins/sharable.py rename to lastfm/mixins/_sharable.py index 70844c8..0ded3a7 100644 --- a/lastfm/mixins/sharable.py +++ b/lastfm/mixins/_sharable.py @@ -5,10 +5,7 @@ __version__ = "0.2" __license__ = "GNU Lesser General Public License" __package__ = "lastfm.mixins" -class Sharable(object): - def init(self, api): - self._api = api - +def sharable(cls): def share(self, recipient, message = None): from lastfm.user import User params = self._default_params({'method': '%s.share' % self.__class__.__name__.lower()}) @@ -28,4 +25,10 @@ class Sharable(object): if extra_params is not None: return extra_params else: - return {} \ No newline at end of file + return {} + + cls.share = share + if not hasattr(cls, '_default_params'): + cls._default_params = _default_params + + return cls \ No newline at end of file diff --git a/lastfm/mixins/shoutable.py b/lastfm/mixins/_shoutable.py similarity index 77% rename from lastfm/mixins/shoutable.py rename to lastfm/mixins/_shoutable.py index 5b3ac47..cd126bd 100644 --- a/lastfm/mixins/shoutable.py +++ b/lastfm/mixins/_shoutable.py @@ -7,13 +7,10 @@ __package__ = "lastfm.mixins" from lastfm.decorators import cached_property, top_property -class Shoutable(object): - def init(self, api): - self._api = api - +def shoutable(cls): @cached_property def shouts(self): - """shouts for this ssubject""" + """shouts for this %s""" % cls.__name__.lower() from lastfm.shout import Shout from lastfm.user import User params = self._default_params({'method': '%s.getShouts' % self.__class__.__name__.lower()}) @@ -30,7 +27,7 @@ class Shoutable(object): @top_property("shouts") def recent_shout(self): - """recent shout for this subject""" + """recent shout for this %s""" % cls.__name__.lower() pass def _default_params(self, extra_params = None): @@ -38,6 +35,13 @@ class Shoutable(object): return extra_params else: return {} + + cls.shouts = shouts + cls.recent_shout = recent_shout + if not hasattr(cls, '_default_params'): + cls._default_params = _default_params + return cls + from datetime import datetime import time \ No newline at end of file diff --git a/lastfm/mixins/taggable.py b/lastfm/mixins/_taggable.py similarity index 90% rename from lastfm/mixins/taggable.py rename to lastfm/mixins/_taggable.py index a8aab89..6e3d985 100644 --- a/lastfm/mixins/taggable.py +++ b/lastfm/mixins/_taggable.py @@ -8,10 +8,7 @@ __package__ = "lastfm.mixins" from lastfm.safelist import SafeList from lastfm.decorators import cached_property, authentication_required -class Taggable(object): - def init(self, api): - self._api = api - +def taggable(cls): @cached_property @authentication_required def tags(self): @@ -69,4 +66,12 @@ class Taggable(object): if extra_params is not None: return extra_params else: - return {} \ No newline at end of file + return {} + + cls.tags = tags + cls.add_tags = add_tags + cls.remove_tag = remove_tag + if not hasattr(cls, '_default_params'): + cls._default_params = _default_params + + return cls \ No newline at end of file diff --git a/lastfm/mixins/chartable.py b/lastfm/mixins/chartable.py deleted file mode 100644 index e036b14..0000000 --- a/lastfm/mixins/chartable.py +++ /dev/null @@ -1,432 +0,0 @@ -#!/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/objectcache.py b/lastfm/objectcache.py index 4c3aca4..f57d92d 100644 --- a/lastfm/objectcache.py +++ b/lastfm/objectcache.py @@ -7,7 +7,7 @@ __package__ = "lastfm" from lastfm.album import Album from lastfm.artist import Artist -from lastfm.mixins import Cacheable +from lastfm.mixins import _cacheable from lastfm.error import InvalidParametersError from lastfm.event import Event from lastfm.geo import Location, Country @@ -29,7 +29,7 @@ class ObjectCache(object): raise InvalidParametersError("Key does not correspond to a valid class") else: try: - vals = Cacheable.registry[eval(name)].values() + vals = _cacheable.registry[eval(name)].values() vals.sort() return vals except KeyError: diff --git a/lastfm/playlist.py b/lastfm/playlist.py index 88d3d90..61ce0f0 100644 --- a/lastfm/playlist.py +++ b/lastfm/playlist.py @@ -6,10 +6,11 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable +from lastfm.mixins import cacheable from lastfm.decorators import cached_property -class Playlist(LastfmBase, Cacheable): +@cacheable +class Playlist(LastfmBase): """A class representing an XPSF playlist.""" def init(self, api, url, **kwargs): self._api = api diff --git a/lastfm/shout.py b/lastfm/shout.py index 68613fe..7db5cab 100644 --- a/lastfm/shout.py +++ b/lastfm/shout.py @@ -6,10 +6,11 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable +from lastfm.mixins import cacheable from lastfm.decorators import cached_property -class Shout(LastfmBase, Cacheable): +@cacheable +class Shout(LastfmBase): """A class representing a shout.""" def init(self, diff --git a/lastfm/tag.py b/lastfm/tag.py index 230d7a2..30454fc 100644 --- a/lastfm/tag.py +++ b/lastfm/tag.py @@ -6,11 +6,13 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import ( - Cacheable, Searchable, ArtistChartable) +from lastfm.mixins import cacheable, searchable, chartable from lastfm.decorators import cached_property, top_property -class Tag(LastfmBase, Cacheable, Searchable, ArtistChartable): +@chartable(['artist']) +@searchable +@cacheable +class Tag(LastfmBase): """A class representing a tag.""" def init(self, api, @@ -21,7 +23,6 @@ class Tag(LastfmBase, Cacheable, Searchable, ArtistChartable): **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 diff --git a/lastfm/track.py b/lastfm/track.py index a2d8440..db6bb33 100644 --- a/lastfm/track.py +++ b/lastfm/track.py @@ -6,10 +6,14 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable, Searchable, Sharable, Taggable +from lastfm.mixins import cacheable, searchable, sharable, taggable from lastfm.decorators import cached_property, top_property -class Track(LastfmBase, Cacheable, Sharable, Searchable, Taggable): +@sharable +@taggable +@searchable +@cacheable +class Track(LastfmBase): """A class representing a track.""" def init(self, api, @@ -30,8 +34,6 @@ class Track(LastfmBase, Cacheable, Sharable, Searchable, Taggable): subject = None): if not isinstance(api, Api): raise InvalidParametersError("api reference must be supplied as an argument") - Taggable.init(self, api) - Sharable.init(self, api) self._api = api self._id = id self._name = name diff --git a/lastfm/user.py b/lastfm/user.py index 7fc2f60..3c13d24 100644 --- a/lastfm/user.py +++ b/lastfm/user.py @@ -6,16 +6,15 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import ( - Cacheable, Shoutable, AlbumChartable, - ArtistChartable, TrackChartable, TagChartable) +from lastfm.mixins import cacheable, shoutable, chartable import lastfm.playlist from lastfm.decorators import ( cached_property, top_property, authentication_required, depaginate) -class User(LastfmBase, Cacheable, Shoutable, - AlbumChartable, ArtistChartable, - TrackChartable, TagChartable): +@chartable(['album', 'artist', 'track', 'tag']) +@shoutable +@cacheable +class User(LastfmBase): """A class representing an user.""" def init(self, api, @@ -27,11 +26,6 @@ class User(LastfmBase, Cacheable, Shoutable, **kwargs): 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 diff --git a/lastfm/venue.py b/lastfm/venue.py index 14a41f2..b87958e 100644 --- a/lastfm/venue.py +++ b/lastfm/venue.py @@ -6,10 +6,12 @@ __license__ = "GNU Lesser General Public License" __package__ = "lastfm" from lastfm.base import LastfmBase -from lastfm.mixins import Cacheable, Searchable +from lastfm.mixins import cacheable, searchable from lastfm.decorators import cached_property, depaginate -class Venue(LastfmBase, Cacheable, Searchable): +@searchable +@cacheable +class Venue(LastfmBase): """A class representing a venue of an event""" def init(self, api,