Yading/musixmatch-master/musixmatch/api.py
Go to the documentation of this file.
1 """ This module define the base API classes.
2 """
3 import musixmatch
4 from urllib import urlencode, urlopen
5 from contextlib import contextmanager
6 import os
7 try:
8  import json
9 except ImportError:
10  import simplejson as json
11 try:
12  from lxml import etree
13 except ImportError:
14  try:
15  import xml.etree.cElementTree as etree
16  except ImportError:
17  try:
18  import xml.etree.ElementTree as etree
19  except ImportError:
20  try:
21  import cElementTree as etree
22  except ImportError:
23  import elementtree.ElementTree as etree
24 
25 __license__ = musixmatch.__license__
26 __author__ = musixmatch.__author__
27 
28 class Error(Exception):
29  """Base musiXmatch API error.
30 
31  >>> import musixmatch
32  >>> raise musixmatch.api.Error('Error message')
33  Traceback (most recent call last):
34  ...
35  Error: Error message
36  """
37  def __str__(self):
38  return ': '.join(map(str, self.args))
39 
40  def __repr__(self):
41  name = self.__class__.__name__
42  return '%s%r' % (name, self.args)
43 
45  """Represents errors occurred while parsing the response messages."""
46 
47 class ResponseStatusCode(int):
48  """
49  Represents response message status code. Casting a
50  :py:class:`ResponseStatusCode` to :py:class:`str` returns the message
51  associated with the status code:
52 
53  >>> from musixmatch.api import ResponseStatusCode
54  >>> str(ResponseStatusCode(200))
55  'The request was successful.'
56  >>> str(ResponseStatusCode(401))
57  'Authentication failed, probably because of a bad API key.'
58 
59  The status code to description mapping is:
60 
61  +------+-----------------------------------------------------------+
62  | Code | Description |
63  +======+===========================================================+
64  | 200 | The request was successful. |
65  +------+-----------------------------------------------------------+
66  | 400 | The request had bad syntax or was inherently |
67  | | impossible to be satisfied. |
68  +------+-----------------------------------------------------------+
69  | 401 | Authentication failed, probably because of a bad API key. |
70  +------+-----------------------------------------------------------+
71  | 402 | A limit was reached, either you exceeded per hour |
72  | | requests limits or your balance is insufficient. |
73  +------+-----------------------------------------------------------+
74  | 403 | You are not authorized to perform this operation or |
75  | | the api version you're trying to use has been shut down. |
76  +------+-----------------------------------------------------------+
77  | 404 | Requested resource was not found. |
78  +------+-----------------------------------------------------------+
79  | 405 | Requested method was not found. |
80  +------+-----------------------------------------------------------+
81 
82  Any other status code will produce a default message:
83 
84  >>> from musixmatch.api import ResponseStatusCode
85  >>> str(ResponseStatusCode(666))
86  'Unknown status code 666!'
87 
88  Casting a :py:class:`ResponseStatusCode` to :py:class:`bool` returns True if
89  status code is 200, False otherwise:
90 
91  >>> from musixmatch.api import ResponseStatusCode
92  >>> bool(ResponseStatusCode(200))
93  True
94  >>> bool(ResponseStatusCode(400))
95  False
96  >>> bool(ResponseStatusCode(666))
97  False
98 
99  """
100  __status__ = {
101  200: "The request was successful.",
102  400: "The request had bad syntax or was inherently " + \
103  "impossible to be satisfied.",
104  401: "Authentication failed, probably because of a bad API key.",
105  402: "A limit was reached, either you exceeded per hour " + \
106  "requests limits or your balance is insufficient.",
107  403: "You are not authorized to perform this operation or " + \
108  "the api version you're trying to use has been shut down.",
109  404: "Requested resource was not found.",
110  405: "Requested method was not found.",
111  }
112 
113  def __str__(self):
114  return self.__status__.get(self, 'Unknown status code %i!' % self)
115 
116  def __repr__(self):
117  return 'ResponseStatusCode(%i)' % self
118 
119  def __nonzero__(self):
120  return self == 200
121 
122 class ResponseMessage(dict):
123  """
124  Abstract class which provides a base class for formatted response.
125  """
126  def __init__(self, response):
127  raise NotImplementedError
128 
129  @property
130  def status_code(self):
131  """
132  Is the :py:class:`ResponseStatusCode` object representing the
133  message status code.
134 
135  :raises: :py:exc:`ValueError` if not set.
136  """
137  raise NotImplementedError
138 
139  def __repr__(self):
140  return "%s('...')" % type(self).__name__
141 
143  """
144  A :py:class:`ResponseMessage` subclass which behaves like a
145  :py:class:`dict` to expose the Json structure contained in the response
146  message. Parses the Json response message and build a proper python
147  :py:class:`dict` containing all the information. Also, setup a
148  :py:class:`ResponseStatusCode` by querying the :py:class:`dict` for the
149  *['header']['status_code']* item.
150  """
151  def __init__(self, response):
152  try:
153  parsed = json.load(response)
154  except Exception, e:
155  raise ResponseMessageError(u'Invalid Json response message', e)
156  self.update(parsed['message'])
157 
158  def __str__(self):
159  s = json.dumps({ 'message': self }, sort_keys=True, indent=4)
160  return '\n'.join([l.rstrip() for l in s.splitlines()])
161 
162  @property
163  def status_code(self):
164  """Overload :py:meth:`ResponseMessage.status_code`"""
165  return ResponseStatusCode(self['header']['status_code'])
166 
167 class XMLResponseMessage(ResponseMessage, etree.ElementTree):
168  """
169  A :py:class:`ResponseMessage` subclass which exposes
170  :py:class:`ElementTree` methods to handle XML response
171  messages. Parses the XML response message and build a
172  :py:class:`ElementTree` instance. Also setup the a
173  :py:class:`ResponseStatusCode` by querying the *status_code* tag content.
174 
175  Casting a :py:class:`XMLResponseMessage` returns (actually re-builds) a
176  pretty printed string representing the XML API response message.
177  """
178 
179  def __init__(self, response):
180  etree.ElementTree.__init__(self, None, response)
181 
182  def __str__(self):
183  s = StringIO()
184  self.wite(s)
185  return s.getvalue()
186 
187  @property
188  def status_code(self):
189  """Overload :py:meth:`ResponseMessage.status_code`"""
190  return ResponseStatusCode(self.findtext('header/status_code'))
191 
192 class QueryString(dict):
193  """
194  A class representing the keyword arguments to be used in HTTP requests as
195  query string. Takes a :py:class:`dict` of keywords, and encode values
196  using utf-8. Also, the query string is sorted by keyword name, so that its
197  string representation is always the same, thus can be used in hashes.
198 
199  Casting a :py:class:`QueryString` to :py:class:`str` returns the urlencoded
200  query string:
201 
202  >>> from musixmatch.api import QueryString
203  >>> str(QueryString({ 'country': 'it', 'page': 1, 'page_size': 3 }))
204  'country=it&page=1&page_size=3'
205 
206  Using :py:func:`repr` on :py:class:`QueryString` returns an evaluable
207  representation of the current instance, excluding apikey value:
208 
209  >>> from musixmatch.api import QueryString
210  >>> repr(QueryString({ 'country': 'it', 'page': 1, 'apikey': 'whatever'}))
211  "QueryString({'country': 'it', 'page': '1'})"
212  """
213  def __init__(self, items=(), **keywords):
214  dict.__init__(self, items, **keywords)
215  for k in self:
216  self[k] = str(self[k]).encode('utf-8')
217 
218  def __str__(self):
219  return urlencode(self)
220 
221  def __repr__(self):
222  query = self.copy()
223  if 'apikey' in query:
224  del query['apikey']
225  return 'QueryString(%r)' % query
226 
227  def __iter__(self):
228  """
229  Returns an iterator method which will yield keys sorted by name.
230  Sorting allow the query strings to be used (reasonably) as caching key.
231  """
232  keys = dict.keys(self)
233  keys.sort()
234  for key in keys:
235  yield key
236 
237  def values(self):
238  """Overloads :py:meth:`dict.values` using :py:meth:`__iter__`."""
239  return tuple(self[k] for k in self)
240 
241  def keys(self):
242  """Overloads :py:meth:`dict.keys` using :py:meth:`__iter__`."""
243  return tuple(k for k in self)
244 
245  def items(self):
246  """Overloads :py:meth:`dict.item` using :py:meth:`__iter__`."""
247  return tuple((k, self[k]) for k in self)
248 
249  def __hash__(self):
250  return hash(str(self))
251 
252  def __cmp__(self, other):
253  return cmp(hash(self), hash(other))
254 
255 class Method(str):
256  """
257  Utility class to build API methods name and call them as functions.
258 
259  :py:class:`Method` has custom attribute access to build method names like
260  those specified in the API. Each attribute access builds a new Method with
261  a new name.
262 
263  Calling a :py:class:`Method` as a function with keyword arguments,
264  builds a :py:class:`Request`, runs it and returns the result. If **apikey**
265  is undefined, environment variable **musixmatch_apikey** will be used. If
266  **format** is undefined, environment variable **musixmatch_format** will be
267  used. If **musixmatch_format** is undefined, jason format will be used.
268 
269  >>> import musixmatch
270  >>> artist = musixmatch.api.Method('artist')
271  >>>
272  >>> try:
273  ... chart = artist.chart.get(country='it', page=1, page_size=3)
274  ... except musixmatch.api.Error, e:
275  ... pass
276  """
277  __separator__ = '.'
278 
279  def __getattribute__(self, name):
280  if name.startswith('_'):
281  return super(Method, self).__getattribute__(name)
282  else:
283  return Method(self.__separator__.join([self, name]))
284 
285  def __call__ (self, apikey=None, format=None, **query):
286  query['apikey'] = apikey or musixmatch.apikey
287  query['format'] = format or musixmatch.format
288  return Request(self, query).response
289 
290  def __repr__(self):
291  return "Method('%s')" % self
292 
293 class Request(object):
294  """
295  This is the main API class. Given a :py:class:`Method` or a method name, a
296  :py:class:`QueryString` or a :py:class:`dict`, it can build the API query
297  URL, run the request and return the response either as a string or as a
298  :py:class:`ResponseMessage` subclass. Assuming the default web services
299  location, this class try to build a proper request:
300 
301  >>> from musixmatch.api import Request, Method, QueryString
302  >>> method_name = 'artist.chart.get'
303  >>> method = Method(method_name)
304  >>> keywords = { 'country': 'it', 'page': 1, 'page_size': 3 }
305  >>> query_string = QueryString(keywords)
306  >>>
307  >>> r1 = Request(method_name, keywords)
308  >>> r2 = Request(method_name, **keywords)
309  >>> r3 = Request(method_name, query_string)
310  >>> r4 = Request(method, keywords)
311  >>> r5 = Request(method, **keywords)
312  >>> r6 = Request(method, query_string)
313 
314  If **method** is string, try to cast it into a :py:class:`Method`. If
315  **query_string** is a :py:class:`dict`, try to cast it into a
316  :py:class:`QueryString`. If **query_string** is not specified, try to
317  use **keywords** arguments as a :py:class:`dict` and cast it into a
318  :py:class:`QueryString`.
319 
320  Turning the :py:class:`Request` into a :py:class:`str` returns the URL
321  representing the API request:
322 
323  >>> str(Request('artist.chart.get', { 'country': 'it', 'page': 1 }))
324  'http://api.musixmatch.com/ws/1.1/artist.chart.get?country=it&page=1'
325  """
326  def __init__ (self, api_method, query=(), **keywords):
327  self.__api_method = isinstance(api_method, Method) and \
328  api_method or Method(api_method)
329  self.__query_string = isinstance(query, QueryString) and \
330  query or QueryString(query)
331  self.__query_string.update(keywords)
332  self.__response = None
333 
334  @property
335  def api_method(self):
336  """The :py:class:`Method` instance."""
337  return self.__api_method
338 
339  @property
340  def query_string(self):
341  """The :py:class:`QueryString` instance."""
342  return self.__query_string
343 
344  @contextmanager
345  def _received(self):
346  """A context manager to handle url opening"""
347  try:
348  response = urlopen(str(self))
349  yield response
350  finally:
351  response.close()
352 
353  @property
354  def response(self):
355  """
356  The :py:class:`ResponseMessage` based on the **format** key in the
357  :py:class:`QueryString`.
358  """
359  if self.__response is None:
360 
361  format = self.query_string.get('format')
362  ResponseMessageClass = {
363  'json': JsonResponseMessage,
364  'xml': XMLResponseMessage,
365  }.get(format, None)
366 
367  if not ResponseMessageClass:
368  raise ResponseMessageError("Unsupported format `%s'" % format)
369 
370  with self._received() as response:
371  self.__response = ResponseMessageClass(response)
372 
373  return self.__response
374 
375  def __repr__(self):
376  return 'Request(%r, %r)' % (self.api_method, self.query_string)
377 
378  def __str__(self):
379  return '%(ws_location)s/%(api_method)s?%(query_string)s' % {
380  'ws_location': musixmatch.ws.location,
381  'api_method': self.api_method,
382  'query_string': self.query_string
383  }
384 
385  def __hash__(self):
386  return hash(str(self))
387 
388  def __cmp__(self, other):
389  return cmp(hash(self), hash(other))
static int hash(int head, const int add)
Hash function adding character.
Definition: lzwenc.c:74
end Error
Definition: extra/TWM.m:53
def __init__(self, api_method, query=(), keywords)
static av_always_inline int cmp(MpegEncContext *s, const int x, const int y, const int subx, const int suby, const int size, const int h, int ref_index, int src_index, me_cmp_func cmp_func, me_cmp_func chroma_cmp_func, const int flags)
compares a block (either a full macroblock or a partition thereof) against a proposed motion-compensa...
Definition: motion_est.c:251
About Git write you should know how to use GIT properly Luckily Git comes with excellent documentation git help man git shows you the available git< command > help man git< command > shows information about the subcommand< command > The most comprehensive manual is the website Git Reference visit they are quite exhaustive You do not need a special username or password All you need is to provide a ssh public key to the Git server admin What follows now is a basic introduction to Git and some FFmpeg specific guidelines Read it at least if you are granted commit privileges to the FFmpeg project you are expected to be familiar with these rules I if not You can get git from etc no matter how small Every one of them has been saved from looking like a fool by this many times It s very easy for stray debug output or cosmetic modifications to slip please avoid problems through this extra level of scrutiny For cosmetics only commits you should get(almost) empty output from git diff-w-b< filename(s)> Also check the output of git status to make sure you don't have untracked files or deletions.git add[-i|-p|-A]< filenames/dirnames > Make sure you have told git your name and email address
def __call__(self, apikey=None, format=None, query)