Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from .access_token import AccessToken
- from .structures import AttrDict
- import hashlib
- import logging
- import os
- import pickle
- import re
- import requests
- import sys
- import time
- import urllib.parse
- __author__ = "Sergei Snegirev (tz4678@gmail.com)"
- __copyright__ = "Copyright (C) 2013-2016 Sergei Snegirev"
- __license__ = "MIT"
- __version__ = "1.0"
- __url__ = "https://github.com/tz4678/fetchvk/"
- API_DELAY = 0.34
- API_HOST = "api.vk.com"
- API_PATH = "/method/"
- API_VERSION = 5.44
- API_METHOD_REGEXP = re.compile("[a-z]+([A-Z][a-z]+)*$")
- USER_AGENT = "Mozilla/5.0 (FetchVk/{} Python/{})".format(
- __version__,
- sys.version.split(" ")[0]
- )
- class FetchVk:
- """Клиент для работы с Api Вконтакте.
- Способы вызова методов Api:
- FetchVk.api.<method_name>(**kwargs)
- FetchVk.api.<method_name>(params_dict, **kwargs)
- FetchVk.api_request('<method_name>', params_dict)
- Usage::
- >> from fetchvk import FetchVk
- >> fvk = FetchVk()
- >> users = fvk.api.users.get(user_id=100)
- >> print(users[0].first_name)
- Если имя именованно параметра совпадает с ключевым словом, добавляем
- к нему подчеркивание (global_, from_).
- """
- def __init__(self,
- access_token=None,
- access_token_filename=None,
- api_delay=API_DELAY,
- api_version=API_VERSION,
- extra_params={},
- session=None):
- """Конструктор.
- :param access_token: Токен доступа
- :type access_token: fetchvk.access_token.AccessToken instance
- :param access_token_filename: Имя файла где хранится токен
- :type access_token_filename: str
- :param api_delay: Задержка между вызовами методов Api
- :type api_delay: int
- :param api_version: Версия Api
- :type api_version: float
- :param extra_params: Дополнительные параметры которые будут
- передаваться при каждом запросе к Api (помимо токена и версии Api).
- Например: {'https': 1, 'lang': 'en'}
- :type extra_params: dict
- :param session: Сессия requests
- :type session: requests.Session instance
- """
- self.access_token = access_token
- self.access_token_filename = access_token_filename
- self.api_delay = api_delay
- self.api_version = api_version
- self.extra_params = extra_params
- if not session:
- session = requests.session()
- session.headers['User-Agent'] = USER_AGENT
- self.session = session
- logger_name = '.'.join([__name__, self.__class__.__name__])
- self.logger = logging.getLogger(logger_name)
- self.last_api_request = 0
- # Сахар над вызовом api_request
- self.api = Api(self)
- # Пробуем загрузить access_token
- if self.access_token_filename:
- self.load_access_token()
- def request(self, url, data=None, files=None):
- start_time = time.time()
- if data or files:
- response = self.session.post(url, data, files=files)
- else:
- response = self.session.get(url)
- request_time = (time.time() - start_time) * 1000
- self.logger.debug("Total Request Time: %dms", request_time)
- return response.json(object_hook=AttrDict)
- def api_request(self, method, params={}):
- """Делает запрос к Api.
- Список методов Api: <https://vk.com/dev/methods>
- Подробнее про запросы к Api: <https://vk.com/dev/api_requests>
- :param method: Метод Api
- :type method: str
- :param params: Передаваемые параметры
- :type params: dict
- :return: Возвращает содержимое поля `response`. Заметьте, что к
- элементам словаря можно обращаться как к аттрибутам через точечную
- нотацию. Вместо обычного словаря используется
- :class:``fetchvk.structures.AttrDict``.
- """
- params = dict(params)
- params.update(self.extra_params)
- params['v'] = self.api_version
- if self.access_token:
- params['access_token'] = self.access_token.key
- # >>> re.sub('^(/|)|(/|)$', '/', 'foo')
- # /foo/
- path = re.sub('^(/|)|(/|)$', '/', API_PATH)
- path = urllib.parse.urljoin(path, method)
- # <https://vk.com/dev/api_nohttps>
- if hasattr(self.access_token, "secret") and self.access_token.secret:
- scheme = 'http'
- # Исправлен баг с неверной подписью. Дело в том, что словари в
- # python неупорядоченные. Новый элемент может быть добавлен как в
- # конец так и любое другое место, да и еще элементы могут
- # поменяться местами. И в итоге имеем, что:
- # md5('foo=bar&baz=qux') != md5('baz=qux&foo=bar')
- # Чтобы закрепить порядок элементов, добавим sig
- params['sig'] = ''
- # Сформировали Query String из словаря
- query = urllib.parse.urlencode(params)
- # А теперь вырежем параметр sig
- query = re.sub('^sig=&|&sig=', '', query)
- uri = '{}?{}{}'.format(path, query, self.access_token.secret)
- sig = hashlib.md5(uri.encode('ascii')).hexdigest()
- params["sig"] = sig
- else:
- scheme = 'https'
- # !!! params не должен изменяться после добавления sig
- api_endpoint = "{}://{}{}".format(scheme, API_HOST, path)
- delay = self.api_delay + self.last_api_request - time.time()
- if delay > 0:
- self.logger.debug("Wait %dms", delay * 1000)
- time.sleep(delay)
- self.logger.debug(
- "Calling Api method %r with parameters: %s", method, params)
- response = self.request(api_endpoint, params)
- self.last_api_request = time.time()
- error = response.get('error')
- if error:
- if 'captcha_img' in error:
- return self.handle_captcha(
- error.captcha_img,
- error.captcha_sid,
- method,
- params
- )
- if 'redirect_uri' in error:
- return self.handle_validation(
- error.redirect_uri,
- method,
- params
- )
- # Кастомные ошибки
- error = ApiError(error)
- return self.handle_error(error, method, params)
- return response.response
- def handle_validation(self, redirect_uri, method, params):
- # Для переопределения в классах потомках.
- # ... do smth
- # if ok:
- # return self.api_request(method, params)
- raise FetchError("Validation Required")
- def handle_captcha(self, captcha_img, captcha_sid, method, params):
- # Для переопределения в классах потомках.
- # ... get captcha_key
- # params.update({
- # 'captcha_key': captcha_key,
- # 'captcha_sid': captcha_sid
- # })
- # return self.api_request(method, params)
- raise FetchError("Captcha Required")
- def handle_error(self, error, method, params):
- """Обработчик всех ошибок кроме капчи и валидации"""
- # Для переопределения в классах потомках.
- # if error.code is fetchvk.errors.TOO_MANY_REQUESTS_PER_SECOND:
- # time.sleep(5)
- # return self.api_request(method, params)
- raise error
- @property
- def is_valid_access_token(self):
- """Проверяет access_token."""
- # Можно короче:
- # return self.api.users.get() == []
- # Но этот способ не будет работать с серверными приложениями
- try:
- return self.api.execute(code="return true;")
- except ApiError:
- return False
- def upload(self, upload_url, files):
- response = self.request(upload_url, None, files)
- if 'error' in response:
- raise FetchError(response.error)
- return response
- def set_new_access_token(self,
- key,
- user_id=None,
- expires_in=None,
- secret=None):
- self.access_token = AccessToken(
- key,
- user_id,
- expires_in,
- secret
- )
- def load_access_token(self, filename=None):
- self.access_token_filename = filename or self.access_token_filename
- if (self.access_token_filename and
- os.path.exists(self.access_token_filename)):
- with open(self.access_token_filename, 'rb') as fp:
- self.access_token = pickle.load(fp)
- self.logger.debug("access token loaded from %s",
- self.access_token_filename)
- def save_access_token(self, filename=None):
- self.access_token_filename = filename or self.access_token_filename
- if self.access_token_filename:
- with open(self.access_token_filename, 'wb') as fp:
- data = pickle.dumps(self.access_token)
- fp.write(data)
- self.logger.debug("access token saved %s",
- self.access_token_filename)
- class FetchError(Exception):
- pass
- class ApiError(FetchError):
- def __init__(self, details):
- self.code = details.error_code
- self.msg = details.error_msg
- def __str__(self):
- return "{}: {}".format(self.code, self.msg)
- class Api:
- def __init__(self, client, method=None):
- self._client = client
- self._method = method
- def __getattr__(self, name):
- if API_METHOD_REGEXP.match(name):
- method = name if not self._method \
- else '.'.join([self._method, name])
- return Api(self._client, method)
- raise AttributeError(name)
- def __call__(self, *args, **kwargs):
- if not self._method:
- raise TypeError("Can not call")
- # Если имя именованного параметра совпадает с ключевым словом, то
- # добавляем подчеркивание (from_, global_)
- params = {k[:-1] if len(k) > 1 and k.endswith('_')
- else k: v for k, v in kwargs.items()}
- if len(args):
- # Первым аргументом является словарь
- d = dict(args[0])
- # Если переданы именованные параметры, то обновляем словарь
- d.update(params)
- params = d
- return self._client.api_request(self._method, params)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement