Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Поддержка Python 3 #5

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
*.py[co]

# Packages
*.egg
*.egg-info
dist
MANIFEST
*.pyc
vkontakte.egg-info
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg

# Installer logs
pip-log.txt

# Unit test / coverage reports
.coverage
.tox

#Translations
*.mo

#Mr Developer
.mr.developer.cfg

#Other files
*sublime*
.DS_Store
11 changes: 4 additions & 7 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
[tox]
envlist = py25,py26,py27,pypy
envlist = py26,py27,py33,pypy

[testenv]
deps=
mock == 1.0.1
requests

commands=
python vkontakte/tests.py

[testenv:py25]
deps=
mock == 1.0.1
simplejson

[testenv:py26]
deps=
mock == 1.0.1
simplejson
simplejson
requests
56 changes: 34 additions & 22 deletions vkontakte/api.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
# coding: utf-8

from functools import partial
from hashlib import md5
import random
import time
import urllib
import warnings
from hashlib import md5
from functools import partial

try:
import simplejson as json
except ImportError:
import json
from vkontakte import http

import requests

__all__ = (
'VKError',
'signature',
'API',
)


API_URL = 'http://api.vk.com/api.php'
SECURE_API_URL = 'https://api.vkontakte.ru/method/'
DEFAULT_TIMEOUT = 1
REQUEST_ENCODING = 'utf8'


# See full list of VK API methods here:
Expand Down Expand Up @@ -48,27 +56,34 @@ def params(self):
def __str__(self):
return "Error(code = '%s', description = '%s', params = '%s')" % (self.code, self.description, self.params)

def _encode(s):

def _prepare_non_str_objs(s):
if isinstance(s, (dict, list, tuple)):
s = json.dumps(s, ensure_ascii=False, encoding=REQUEST_ENCODING)
s = json.dumps(s, ensure_ascii=False)

if isinstance(s, unicode):
s = s.encode(REQUEST_ENCODING)
return s # this can be number, etc.

return s # this can be number, etc.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А как requests справляется с корявым ответом ВКонтакте, который отдает 2 json-ответа в одном? В первом - ошибка, во втором - собственно ответ (это проверял удаленный тест IterparseTest). Или там что-то изменилось у ВКонтакте и они совсем старый метод авторизации отрубили? См. #3

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Справлялся плохо, вернул к старому состоянию плюс оставил тест который уже был.

def _json_iterparse(response):
response = response.strip()
decoder = json.JSONDecoder(encoding="utf8", strict=False)
try:
decoder = json.JSONDecoder(encoding='utf8', strict=False)
except TypeError:
decoder = json.JSONDecoder(strict=False)
idx = 0
while idx < len(response):
obj, idx = decoder.raw_decode(response, idx)
yield obj


def signature(api_secret, params):
keys = sorted(params.keys())
param_str = "".join(["%s=%s" % (str(key), _encode(params[key])) for key in keys])
return md5(param_str + str(api_secret)).hexdigest()
param_str = ''.join(['%s=%s' % (str(key), _prepare_non_str_objs(params[key])) for key in keys])
hashing_str = param_str + str(api_secret)
try:
return md5(hashing_str).hexdigest()
except (TypeError, UnicodeEncodeError):
return md5(hashing_str.encode('utf8')).hexdigest()

# We have to support this:
#
Expand Down Expand Up @@ -134,9 +149,6 @@ def _signature(self, params):

def _request(self, method, timeout=DEFAULT_TIMEOUT, **kwargs):

for key, value in kwargs.iteritems():
kwargs[key] = _encode(value)

if self.token:
# http://vkontakte.ru/developers.php?oid=-1&p=Выполнение_запросов_к_API
params = dict(
Expand All @@ -145,7 +157,6 @@ def _request(self, method, timeout=DEFAULT_TIMEOUT, **kwargs):
params.update(kwargs)
params['timestamp'] = int(time.time())
url = SECURE_API_URL + method
secure = True
else:
# http://vkontakte.ru/developers.php?oid=-1&p=Взаимодействие_приложения_с_API
params = dict(
Expand All @@ -159,15 +170,16 @@ def _request(self, method, timeout=DEFAULT_TIMEOUT, **kwargs):
params['timestamp'] = int(time.time())
params['sig'] = self._signature(params)
url = API_URL
secure = False
data = urllib.urlencode(params)

headers = {"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"}
headers = {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
}

# urllib2 doesn't support timeouts for python 2.5 so
# custom function is used for making http requests
return http.post(url, data, headers, timeout, secure=secure)
response = requests.post(url, params=params, headers=headers, timeout=timeout)
return response.status_code, response.text


class API(_API):
Expand Down
27 changes: 0 additions & 27 deletions vkontakte/http.py

This file was deleted.

31 changes: 7 additions & 24 deletions vkontakte/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,24 @@

import os
import sys
import urllib
sys.path.insert(0, os.path.abspath('..'))

import unittest
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Без sys.path.insert тесты, запущенные не через tox, могут импортировать глобально установленный vkontakte вместо того, что у нас тут.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вернул на место.


import mock

import vkontakte
import vkontakte.api


API_ID = 'api_id'
API_SECRET = 'api_secret'


class VkontakteTest(unittest.TestCase):
def test_api_creation_error(self):
self.assertRaises(ValueError, lambda: vkontakte.API())


class SignatureTest(unittest.TestCase):
def test_signature_supports_unicode(self):
params = {'foo': u'клен'}
Expand All @@ -30,6 +33,7 @@ def test_signature_supports_unicode(self):
'560b3f1e09ff65167b8dc211604fed2b'
)


class IterparseTest(unittest.TestCase):
def test_iterparse(self):
data = '{"error":{"error_code":8,"error_msg":"Invalid request: this auth method is obsolete, please use oauth. vk.com\/developers","request_params":[{"key":"sig","value":"97aasff03cc81d5db25de67893e207"},{"key":"uids","value":"1,2"},{"key":"timestamp","value":"1355095295"},{"key":"v","value":"3.0"},{"key":"fields","value":"education"},{"key":"format","value":"JSON"},{"key":"random","value":"937530097"},{"key":"method","value":"getProfiles"},{"key":"api_id","value":"3267523"}]}}{"error":{"error_code":8,"error_msg":"Invalid request: this auth method is obsolete, please use oauth. vk.com\/developers","request_params":[{"key":"sig","value":"97aasff03cc81d5db25de67893e207"},{"key":"uids","value":"1,2"},{"key":"timestamp","value":"1355095295"},{"key":"v","value":"3.0"},{"key":"fields","value":"education"},{"key":"format","value":"JSON"},{"key":"random","value":"937530097"},{"key":"method","value":"getProfiles"},{"key":"api_id","value":"3267523"}]}}{"response":[{"uid":1,"first_name":"Павел","last_name":"Дуров","university":1,"university_name":"СПбГУ","faculty":15,"faculty_name":"Филологический","graduation":2006},{"uid":2,"first_name":"Александра","last_name":"Владимирова"}]}'
Expand All @@ -46,7 +50,6 @@ def test_iterparse_edge(self):
self.assertEqual(parses[1]["foo"], "bar")



class VkontakteMagicTest(unittest.TestCase):

def setUp(self):
Expand All @@ -73,14 +76,6 @@ def test_with_arguments_get(self, _get):
self.assertEqual(res, _get.return_value)
_get.assert_called_once_with('getProfiles', vkontakte.api.DEFAULT_TIMEOUT, uids='1,2', fields='education')

@mock.patch('vkontakte.http.post')
def test_timeout(self, post):
post.return_value = 200, '{"response":123}'
api = vkontakte.API(API_ID, API_SECRET, timeout=5)
res = api.getServerTime()
self.assertEqual(res, 123)
self.assertEqual(post.call_args[0][3], 5)

@mock.patch('vkontakte.api._API._get')
def test_magic(self, _get):
for method in vkontakte.api.COMPLEX_METHODS:
Expand All @@ -97,18 +92,6 @@ def test_magic_get(self, _get):
self.assertEqual(res, 'foo')
_get.assert_called_once_with('friends.get', uid=642177)

@mock.patch('vkontakte.http.post')
def test_urlencode_bug(self, post):
post.return_value = 200, '{"response":123}'
res = self.api.search(q=u'клен')
self.assertEqual(res, 123)

@mock.patch('vkontakte.http.post')
def test_valid_quoted_json(self, post):
post.return_value = 200, '{"response": 123}'
self.api.ads.getStat(data={'type': '1', 'id': 1})
posted_data = urllib.unquote(post.call_args[0][1])
self.assertTrue('data={"type":+"1",+"id":+1}' in posted_data, posted_data)

if __name__ == '__main__':
unittest.main()
unittest.main()