yading@7
|
1 """
|
yading@7
|
2 This module contains tha base classes for the Musixmatch API generated content:
|
yading@7
|
3
|
yading@7
|
4 * :py:class:`musixmatch.artist.Artist`
|
yading@7
|
5 * :py:class:`musixmatch.artist.Album`
|
yading@7
|
6 * :py:class:`musixmatch.track.Track`
|
yading@7
|
7 * :py:class:`musixmatch.lyrics.Lyrics`
|
yading@7
|
8 * :py:class:`musixmatch.subtitle.Subtitle`
|
yading@7
|
9 """
|
yading@7
|
10 import musixmatch
|
yading@7
|
11 __license__ = musixmatch.__license__
|
yading@7
|
12 __author__ = musixmatch.__author__
|
yading@7
|
13
|
yading@7
|
14 from musixmatch import api
|
yading@7
|
15 import pprint
|
yading@7
|
16
|
yading@7
|
17 class Base(object):
|
yading@7
|
18 """
|
yading@7
|
19 The very base (abstract) class of the musixmatch package. I want all
|
yading@7
|
20 classes to implement :py:meth:`__str__` and :py:meth:`__repr__`.
|
yading@7
|
21
|
yading@7
|
22 Casting an :py:class:`Base` into a :py:class:`str` returns a pretty printed
|
yading@7
|
23 (maybe using :py:mod:`pprint`) string representing the object.
|
yading@7
|
24
|
yading@7
|
25 Using :py:func:`repr` on an :py:class:`Base` returns an evaluable string
|
yading@7
|
26 representing the instance, whenever it is reasonable.
|
yading@7
|
27
|
yading@7
|
28 :py:class:`Base` instances are hashable.
|
yading@7
|
29 """
|
yading@7
|
30
|
yading@7
|
31 @classmethod
|
yading@7
|
32 def label(cls):
|
yading@7
|
33 """
|
yading@7
|
34 Returns the label that should be used as keyword in the
|
yading@7
|
35 :py:class:`musixmatch.api.JsonResponseMessage` body.
|
yading@7
|
36 """
|
yading@7
|
37 return getattr(cls, '__label__', cls.__name__.lower())
|
yading@7
|
38
|
yading@7
|
39 @classmethod
|
yading@7
|
40 def apiMethod(cls):
|
yading@7
|
41 """
|
yading@7
|
42 Returns the :py:class:`musixmatch.api.Method` that should be used to
|
yading@7
|
43 build the object. Defaults to *label.get* where *label* is the result
|
yading@7
|
44 from :py:meth:`label`
|
yading@7
|
45 """
|
yading@7
|
46 api_method = getattr(cls, '__api_method__', '%s.%s' % (cls.label(),'get'))
|
yading@7
|
47 if not isinstance(api_method, api.Method):
|
yading@7
|
48 api_method = api.Method(str(api_method))
|
yading@7
|
49 return api_method
|
yading@7
|
50
|
yading@7
|
51 def __str__(self):
|
yading@7
|
52 raise NotImplementedError
|
yading@7
|
53
|
yading@7
|
54 def __repr__(self):
|
yading@7
|
55 raise NotImplementedError
|
yading@7
|
56
|
yading@7
|
57 class Item(Base, dict):
|
yading@7
|
58 """
|
yading@7
|
59 This is the base class for any entity in musixmatch package. Even if
|
yading@7
|
60 response messages may have XML format, the JSON representation will be the
|
yading@7
|
61 main data format, so the :py:class:`dict` sounds like the best base class.
|
yading@7
|
62 It fetches the item data by guessing the :py:class:`musixmatch.api.Method`
|
yading@7
|
63 and building the query based on a given keyword argument. Positional
|
yading@7
|
64 argument is meant to be used by collection classes. Use only keyword
|
yading@7
|
65 arguments.
|
yading@7
|
66 """
|
yading@7
|
67 __api_method__ = None
|
yading@7
|
68
|
yading@7
|
69 def __init__(self, dictionary=None, **keywords):
|
yading@7
|
70 if dictionary:
|
yading@7
|
71 dict.update(self, dictionary)
|
yading@7
|
72 elif keywords:
|
yading@7
|
73 message = self.apiMethod()(**keywords)
|
yading@7
|
74 dict.update(self, self.fromResponseMessage(message))
|
yading@7
|
75
|
yading@7
|
76 def __str__(self):
|
yading@7
|
77 return pprint.pformat(dict(self),4,1)
|
yading@7
|
78
|
yading@7
|
79 def __repr__(self):
|
yading@7
|
80 return '%s(%r)' % (type(self).__name__, dict(self))
|
yading@7
|
81
|
yading@7
|
82 def __hash__(self):
|
yading@7
|
83 return int(self['%s_id' % self.label()])
|
yading@7
|
84
|
yading@7
|
85 @classmethod
|
yading@7
|
86 def fromResponseMessage(cls, message):
|
yading@7
|
87 """
|
yading@7
|
88 Returns an object instance, built from a
|
yading@7
|
89 :py:class:`musixmatch.api.ResponseMessage`
|
yading@7
|
90 """
|
yading@7
|
91 if not message.status_code:
|
yading@7
|
92 raise api.Error(str(message.status_code))
|
yading@7
|
93 return cls.fromDictionary(message['body'][cls.label()])
|
yading@7
|
94
|
yading@7
|
95 @classmethod
|
yading@7
|
96 def fromDictionary(cls, dictionary, **keywords):
|
yading@7
|
97 """
|
yading@7
|
98 Returns an object instance, built from a :py:class:`dict`
|
yading@7
|
99 """
|
yading@7
|
100 item = cls()
|
yading@7
|
101 dict.update(item, dictionary, **keywords)
|
yading@7
|
102 return item
|
yading@7
|
103
|
yading@7
|
104 class ItemsCollection(Base, list):
|
yading@7
|
105 """
|
yading@7
|
106 This is the base class for collections of items, like search results, or
|
yading@7
|
107 charts. It behaves like :py:class:`list`, but enforce new items to be
|
yading@7
|
108 instance of appropriate class checking against :py:meth:`allowedin`.
|
yading@7
|
109 """
|
yading@7
|
110
|
yading@7
|
111 __allowedin__ = Item
|
yading@7
|
112
|
yading@7
|
113 def __init__(self, *items):
|
yading@7
|
114 self.extend(items)
|
yading@7
|
115
|
yading@7
|
116 def __repr__(self):
|
yading@7
|
117 items = [ repr(i) for i in self ]
|
yading@7
|
118 return '%s(%s)' % (type(self).__name__, ', '.join(items))
|
yading@7
|
119
|
yading@7
|
120 def __str__(self):
|
yading@7
|
121 return list.__str__(self)
|
yading@7
|
122
|
yading@7
|
123 def __add__(self, iterable):
|
yading@7
|
124 collection = self.copy()
|
yading@7
|
125 collection.extend(iterable)
|
yading@7
|
126 return collection
|
yading@7
|
127
|
yading@7
|
128 def __iadd__(self, iterable):
|
yading@7
|
129 raise NotImplementedError
|
yading@7
|
130
|
yading@7
|
131 def __mul__(self, by):
|
yading@7
|
132 raise NotImplementedError
|
yading@7
|
133
|
yading@7
|
134 def __imul__(self, by):
|
yading@7
|
135 raise NotImplementedError
|
yading@7
|
136
|
yading@7
|
137 def __setitem__(self, key, item):
|
yading@7
|
138 raise NotImplementedError
|
yading@7
|
139
|
yading@7
|
140 def __setslice__(self, *indices):
|
yading@7
|
141 raise NotImplementedError
|
yading@7
|
142
|
yading@7
|
143 def __getslice__(self, i=0, j=-1):
|
yading@7
|
144 return self.__getitem__(slice(i,j))
|
yading@7
|
145
|
yading@7
|
146 def __getitem__(self, i):
|
yading@7
|
147 if type(i) is int:
|
yading@7
|
148 return list.__getitem__(self, i)
|
yading@7
|
149 elif type(i) is slice:
|
yading@7
|
150 collection = type(self)()
|
yading@7
|
151 list.extend(collection, list.__getitem__(self, i))
|
yading@7
|
152 return collection
|
yading@7
|
153 else:
|
yading@7
|
154 raise TypeError, i
|
yading@7
|
155
|
yading@7
|
156 def append(self, item):
|
yading@7
|
157 self.insert(len(self), item)
|
yading@7
|
158
|
yading@7
|
159 def extend(self, iterable):
|
yading@7
|
160 for item in iterable:
|
yading@7
|
161 self.append(item)
|
yading@7
|
162
|
yading@7
|
163 def count(self, item):
|
yading@7
|
164 return int(item in self)
|
yading@7
|
165
|
yading@7
|
166 def copy(self):
|
yading@7
|
167 """Returns a shallow copy of the collection."""
|
yading@7
|
168 collection = type(self)()
|
yading@7
|
169 list.extend(collection, self)
|
yading@7
|
170 return collection
|
yading@7
|
171
|
yading@7
|
172 def index(self, item, *indices):
|
yading@7
|
173 return list.index(self, item, *indices[:2])
|
yading@7
|
174
|
yading@7
|
175 def insert(self, key, item):
|
yading@7
|
176 allowed = self.allowedin()
|
yading@7
|
177 if not isinstance(item, allowed):
|
yading@7
|
178 item = allowed.fromDictionary(item)
|
yading@7
|
179 if not item in self:
|
yading@7
|
180 list.insert(self, key, item)
|
yading@7
|
181
|
yading@7
|
182 def paged(self, page_size=3):
|
yading@7
|
183 """
|
yading@7
|
184 Returns self, paged by **page_size**. That is, a list of
|
yading@7
|
185 sub-collections which contain "at most" **page_size** items.
|
yading@7
|
186 """
|
yading@7
|
187 return [ self.page(i,page_size)
|
yading@7
|
188 for i in range(self.pages(page_size)) ]
|
yading@7
|
189
|
yading@7
|
190 def page(self, page_index, page_size=3):
|
yading@7
|
191 """
|
yading@7
|
192 Returns a specific page, considering pages that contain "at most"
|
yading@7
|
193 **page_size** items.
|
yading@7
|
194 """
|
yading@7
|
195 page = type(self)()
|
yading@7
|
196 i = page_index * page_size
|
yading@7
|
197 list.extend(page, self[i:i+page_size])
|
yading@7
|
198 return page
|
yading@7
|
199
|
yading@7
|
200 def pager(self, page_size=3):
|
yading@7
|
201 """
|
yading@7
|
202 A generator of pages, considering pages that contain "at most"
|
yading@7
|
203 **page_size** items.
|
yading@7
|
204 """
|
yading@7
|
205 for i in xrange(self.pages(page_size)):
|
yading@7
|
206 yield self.page(i, page_size)
|
yading@7
|
207
|
yading@7
|
208 def pages(self,page_size=3):
|
yading@7
|
209 """
|
yading@7
|
210 Returns the number of pages, considering pages that contain "at most"
|
yading@7
|
211 **page_size** items.
|
yading@7
|
212 """
|
yading@7
|
213 pages, more = divmod(len(self), page_size)
|
yading@7
|
214 return more and pages + 1 or pages
|
yading@7
|
215
|
yading@7
|
216 @classmethod
|
yading@7
|
217 def fromResponseMessage(cls, message):
|
yading@7
|
218 """
|
yading@7
|
219 Returns an object instance, built on a
|
yading@7
|
220 :py:class:`musixmatch.api.ResponseMessage`
|
yading@7
|
221 """
|
yading@7
|
222 if not message.status_code:
|
yading@7
|
223 raise api.Error(str(message.status_code))
|
yading@7
|
224 list_label = cls.label()
|
yading@7
|
225 item_label = cls.allowedin().label()
|
yading@7
|
226 items = [ i[item_label] for i in message['body'][list_label] ]
|
yading@7
|
227 return cls(*items)
|
yading@7
|
228
|
yading@7
|
229 @classmethod
|
yading@7
|
230 def allowedin(cls):
|
yading@7
|
231 """
|
yading@7
|
232 Returns the allowed content class. Defaults to :py:class:`Item`
|
yading@7
|
233 """
|
yading@7
|
234 return cls.__allowedin__
|
yading@7
|
235
|
yading@7
|
236 @classmethod
|
yading@7
|
237 def label(cls):
|
yading@7
|
238 item_name = cls.allowedin().label()
|
yading@7
|
239 return '%s_list' % item_name
|
yading@7
|
240
|