PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/vkontakte/api.py

https://bitbucket.org/kmike/vkontakte/
Python | 176 lines | 158 code | 14 blank | 4 comment | 7 complexity | 7b2c762eeb3583ec456c89296b7bf9db MD5 | raw file
  1. # coding: utf-8
  2. import random
  3. import time
  4. import urllib
  5. import warnings
  6. from hashlib import md5
  7. from functools import partial
  8. try:
  9. import simplejson as json
  10. except ImportError:
  11. import json
  12. from vkontakte import http
  13. API_URL = 'http://api.vk.com/api.php'
  14. SECURE_API_URL = 'https://api.vk.com/method/'
  15. DEFAULT_TIMEOUT = 1
  16. REQUEST_ENCODING = 'utf8'
  17. # See full list of VK API methods here:
  18. # http://vk.com/developers.php?o=-1&p=%D0%A0%D0%B0%D1%81%D1%88%D0%B8%D1%80%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D1%8B_API&s=0
  19. # http://vk.com/developers.php?o=-1&p=%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2_API&s=0
  20. COMPLEX_METHODS = ['secure', 'ads', 'messages', 'likes', 'friends',
  21. 'groups', 'photos', 'wall', 'newsfeed', 'notifications', 'audio',
  22. 'video', 'docs', 'places', 'storage', 'notes', 'pages',
  23. 'activity', 'offers', 'questions', 'subscriptions',
  24. 'users', 'status', 'polls', 'account', 'auth', 'stats', 'database']
  25. class VKError(Exception):
  26. __slots__ = ["error"]
  27. def __init__(self, error_data):
  28. self.error = error_data
  29. Exception.__init__(self, str(self))
  30. @property
  31. def code(self):
  32. return self.error['error_code']
  33. @property
  34. def description(self):
  35. return self.error['error_msg']
  36. @property
  37. def params(self):
  38. return self.error['request_params']
  39. def __str__(self):
  40. return "Error(code = '%s', description = '%s', params = '%s')" % (self.code, self.description, self.params)
  41. def _encode(s):
  42. if isinstance(s, (dict, list, tuple)):
  43. s = json.dumps(s, ensure_ascii=False, encoding=REQUEST_ENCODING)
  44. if isinstance(s, unicode):
  45. s = s.encode(REQUEST_ENCODING)
  46. return s # this can be number, etc.
  47. def _json_iterparse(response):
  48. response = response.strip().decode('utf8', 'ignore').encode('utf8')
  49. decoder = json.JSONDecoder(encoding="utf8", strict=False)
  50. idx = 0
  51. while idx < len(response):
  52. obj, idx = decoder.raw_decode(response, idx)
  53. yield obj
  54. def signature(api_secret, params):
  55. keys = sorted(params.keys())
  56. param_str = "".join(["%s=%s" % (str(key), _encode(params[key])) for key in keys])
  57. return md5(param_str + str(api_secret)).hexdigest()
  58. # We have to support this:
  59. #
  60. # >>> vk = API(key, secret)
  61. # >>> vk.get('getServerTime') # "get" is a method of API class
  62. # >>> vk.friends.get(uid=123) # "get" is a part of vkontakte method name
  63. #
  64. # It works this way: API class has 'get' method but _API class doesn't.
  65. class _API(object):
  66. def __init__(self, api_id=None, api_secret=None, token=None, **defaults):
  67. if not (api_id and api_secret or token):
  68. raise ValueError("Arguments api_id and api_secret or token are required")
  69. self.api_id = api_id
  70. self.api_secret = api_secret
  71. self.token = token
  72. self.defaults = defaults
  73. self.method_prefix = ''
  74. def _get(self, method, timeout=DEFAULT_TIMEOUT, **kwargs):
  75. status, response = self._request(method, timeout=timeout, **kwargs)
  76. if not (200 <= status <= 299):
  77. raise VKError({
  78. 'error_code': status,
  79. 'error_msg': "HTTP error",
  80. 'request_params': kwargs,
  81. })
  82. # there may be a response after errors
  83. errors = []
  84. for data in _json_iterparse(response):
  85. if "error" in data:
  86. errors.append(data["error"])
  87. if "response" in data:
  88. for error in errors:
  89. warnings.warn("%s" % error)
  90. return data["response"]
  91. raise VKError(errors[0])
  92. def __getattr__(self, name):
  93. '''
  94. Support for api.<method>.<methodName> syntax
  95. '''
  96. if name in COMPLEX_METHODS:
  97. api = _API(api_id=self.api_id, api_secret=self.api_secret, token=self.token, **self.defaults)
  98. api.method_prefix = name + '.'
  99. return api
  100. # the magic to convert instance attributes into method names
  101. return partial(self, method=name)
  102. def __call__(self, **kwargs):
  103. method = kwargs.pop('method')
  104. params = self.defaults.copy()
  105. params.update(kwargs)
  106. return self._get(self.method_prefix + method, **params)
  107. def _signature(self, params):
  108. return signature(self.api_secret, params)
  109. def _request(self, method, timeout=DEFAULT_TIMEOUT, **kwargs):
  110. for key, value in kwargs.iteritems():
  111. kwargs[key] = _encode(value)
  112. if self.token:
  113. # https://vk.com/dev/api_requests
  114. params = dict(
  115. access_token=self.token,
  116. )
  117. params.update(kwargs)
  118. params['timestamp'] = int(time.time())
  119. url = SECURE_API_URL + method
  120. secure = True
  121. else:
  122. # http://vkontakte.ru/developers.php?oid=-1&p=Взаимодействие_приложения_с_API
  123. params = dict(
  124. api_id=str(self.api_id),
  125. method=method,
  126. format='JSON',
  127. v='3.0',
  128. random=random.randint(0, 2 ** 30),
  129. )
  130. params.update(kwargs)
  131. params['timestamp'] = int(time.time())
  132. params['sig'] = self._signature(params)
  133. url = API_URL
  134. secure = False
  135. data = urllib.urlencode(params)
  136. headers = {"Accept": "application/json",
  137. "Content-Type": "application/x-www-form-urlencoded"}
  138. # urllib2 doesn't support timeouts for python 2.5 so
  139. # custom function is used for making http requests
  140. return http.post(url, data, headers, timeout, secure=secure)
  141. class API(_API):
  142. def get(self, method, timeout=DEFAULT_TIMEOUT, **kwargs):
  143. return self._get(method, timeout, **kwargs)