Mercurial > hg > pmhd
view musixmatch-master/build/lib/musixmatch/base.py @ 13:844d341cf643 tip
Back up before ISMIR
author | Yading Song <yading.song@eecs.qmul.ac.uk> |
---|---|
date | Thu, 31 Oct 2013 13:17:06 +0000 |
parents | 8c29444cb5fd |
children |
line wrap: on
line source
""" This module contains tha base classes for the Musixmatch API generated content: * :py:class:`musixmatch.artist.Artist` * :py:class:`musixmatch.artist.Album` * :py:class:`musixmatch.track.Track` * :py:class:`musixmatch.lyrics.Lyrics` * :py:class:`musixmatch.subtitle.Subtitle` """ import musixmatch __license__ = musixmatch.__license__ __author__ = musixmatch.__author__ from musixmatch import api import pprint class Base(object): """ The very base (abstract) class of the musixmatch package. I want all classes to implement :py:meth:`__str__` and :py:meth:`__repr__`. Casting an :py:class:`Base` into a :py:class:`str` returns a pretty printed (maybe using :py:mod:`pprint`) string representing the object. Using :py:func:`repr` on an :py:class:`Base` returns an evaluable string representing the instance, whenever it is reasonable. :py:class:`Base` instances are hashable. """ @classmethod def label(cls): """ Returns the label that should be used as keyword in the :py:class:`musixmatch.api.JsonResponseMessage` body. """ return getattr(cls, '__label__', cls.__name__.lower()) @classmethod def apiMethod(cls): """ Returns the :py:class:`musixmatch.api.Method` that should be used to build the object. Defaults to *label.get* where *label* is the result from :py:meth:`label` """ api_method = getattr(cls, '__api_method__', '%s.%s' % (cls.label(),'get')) if not isinstance(api_method, api.Method): api_method = api.Method(str(api_method)) return api_method def __str__(self): raise NotImplementedError def __repr__(self): raise NotImplementedError class Item(Base, dict): """ This is the base class for any entity in musixmatch package. Even if response messages may have XML format, the JSON representation will be the main data format, so the :py:class:`dict` sounds like the best base class. It fetches the item data by guessing the :py:class:`musixmatch.api.Method` and building the query based on a given keyword argument. Positional argument is meant to be used by collection classes. Use only keyword arguments. """ __api_method__ = None def __init__(self, dictionary=None, **keywords): if dictionary: dict.update(self, dictionary) elif keywords: message = self.apiMethod()(**keywords) dict.update(self, self.fromResponseMessage(message)) def __str__(self): return pprint.pformat(dict(self),4,1) def __repr__(self): return '%s(%r)' % (type(self).__name__, dict(self)) def __hash__(self): return int(self['%s_id' % self.label()]) @classmethod def fromResponseMessage(cls, message): """ Returns an object instance, built from a :py:class:`musixmatch.api.ResponseMessage` """ if not message.status_code: raise api.Error(str(message.status_code)) return cls.fromDictionary(message['body'][cls.label()]) @classmethod def fromDictionary(cls, dictionary, **keywords): """ Returns an object instance, built from a :py:class:`dict` """ item = cls() dict.update(item, dictionary, **keywords) return item class ItemsCollection(Base, list): """ This is the base class for collections of items, like search results, or charts. It behaves like :py:class:`list`, but enforce new items to be instance of appropriate class checking against :py:meth:`allowedin`. """ __allowedin__ = Item def __init__(self, *items): self.extend(items) def __repr__(self): items = [ repr(i) for i in self ] return '%s(%s)' % (type(self).__name__, ', '.join(items)) def __str__(self): return list.__str__(self) def __add__(self, iterable): collection = self.copy() collection.extend(iterable) return collection def __iadd__(self, iterable): raise NotImplementedError def __mul__(self, by): raise NotImplementedError def __imul__(self, by): raise NotImplementedError def __setitem__(self, key, item): raise NotImplementedError def __setslice__(self, *indices): raise NotImplementedError def __getslice__(self, i=0, j=-1): return self.__getitem__(slice(i,j)) def __getitem__(self, i): if type(i) is int: return list.__getitem__(self, i) elif type(i) is slice: collection = type(self)() list.extend(collection, list.__getitem__(self, i)) return collection else: raise TypeError, i def append(self, item): self.insert(len(self), item) def extend(self, iterable): for item in iterable: self.append(item) def count(self, item): return int(item in self) def copy(self): """Returns a shallow copy of the collection.""" collection = type(self)() list.extend(collection, self) return collection def index(self, item, *indices): return list.index(self, item, *indices[:2]) def insert(self, key, item): allowed = self.allowedin() if not isinstance(item, allowed): item = allowed.fromDictionary(item) if not item in self: list.insert(self, key, item) def paged(self, page_size=3): """ Returns self, paged by **page_size**. That is, a list of sub-collections which contain "at most" **page_size** items. """ return [ self.page(i,page_size) for i in range(self.pages(page_size)) ] def page(self, page_index, page_size=3): """ Returns a specific page, considering pages that contain "at most" **page_size** items. """ page = type(self)() i = page_index * page_size list.extend(page, self[i:i+page_size]) return page def pager(self, page_size=3): """ A generator of pages, considering pages that contain "at most" **page_size** items. """ for i in xrange(self.pages(page_size)): yield self.page(i, page_size) def pages(self,page_size=3): """ Returns the number of pages, considering pages that contain "at most" **page_size** items. """ pages, more = divmod(len(self), page_size) return more and pages + 1 or pages @classmethod def fromResponseMessage(cls, message): """ Returns an object instance, built on a :py:class:`musixmatch.api.ResponseMessage` """ if not message.status_code: raise api.Error(str(message.status_code)) list_label = cls.label() item_label = cls.allowedin().label() items = [ i[item_label] for i in message['body'][list_label] ] return cls(*items) @classmethod def allowedin(cls): """ Returns the allowed content class. Defaults to :py:class:`Item` """ return cls.__allowedin__ @classmethod def label(cls): item_name = cls.allowedin().label() return '%s_list' % item_name