Skip to content

Commit c167d82

Browse files
committed
Error workflow was refactored
1 parent 141bc1f commit c167d82

3 files changed

Lines changed: 80 additions & 44 deletions

File tree

vk/api.py

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from vk.logs import LOGGING_CONFIG
77
from vk.utils import stringify_values, json_iter_parse, LoggingSession
8-
from vk.exceptions import VkAuthError, VkAPIMethodError, CAPTCHA_IS_NEEDED, AUTHORIZATION_FAILED
8+
from vk.exceptions import VkAuthError, VkAPIError, CAPTCHA_IS_NEEDED, AUTHORIZATION_FAILED
99
from vk.mixins import AuthMixin, InteractiveMixin
1010

1111

@@ -60,55 +60,67 @@ def get_access_token(self):
6060
logger.debug('API.get_access_token()')
6161
return self._access_token
6262

63-
def make_request(self, method_request, **method_kwargs):
63+
def make_request(self, method_request, captcha_response=None):
6464

6565
logger.debug('Prepare API Method request')
6666

6767
response = self.send_api_request(method_request)
68+
# todo Replace with something less exceptional
6869
response.raise_for_status()
6970

7071
# there are may be 2 dicts in one JSON
71-
# for example: {'error': ...}{'response': ...}
72-
errors = []
73-
error_codes = []
74-
for data in json_iter_parse(response.text):
75-
if 'error' in data:
76-
error_data = data['error']
77-
if error_data['error_code'] == CAPTCHA_IS_NEEDED:
78-
return self.on_captcha_is_needed(error_data, method_request)
79-
80-
error_codes.append(error_data['error_code'])
81-
errors.append(error_data)
82-
83-
if 'response' in data:
84-
for error in errors:
85-
logger.warning(str(error))
86-
87-
return data['response']
88-
89-
if AUTHORIZATION_FAILED in error_codes: # invalid access token
90-
logger.info('Authorization failed. Access token will be dropped')
91-
self.access_token = None
92-
return self.make_request(method_request)
93-
else:
94-
raise VkAPIMethodError(errors[0])
95-
96-
def send_api_request(self, request):
72+
# for example: "{'error': ...}{'response': ...}"
73+
for response_or_error in json_iter_parse(response.text):
74+
if 'response' in response_or_error:
75+
# todo Can we have error and response simultaneously
76+
# for error in errors:
77+
# logger.warning(str(error))
78+
79+
return response_or_error['response']
80+
81+
elif 'error' in response_or_error:
82+
error_data = response_or_error['error']
83+
error = VkAPIError(error_data)
84+
85+
if error.is_captcha_needed():
86+
captcha_key = self.get_captcha_key(error.captcha_img)
87+
if not captcha_key:
88+
raise error
89+
90+
captcha_response = {
91+
'sid': error.captcha_sid,
92+
'key': captcha_key,
93+
}
94+
return self.make_request(method_request, captcha_response=captcha_response)
95+
96+
elif error.is_access_token_incorrect():
97+
logger.info('Authorization failed. Access token will be dropped')
98+
self.access_token = None
99+
return self.make_request(method_request)
100+
101+
else:
102+
raise error
103+
104+
def send_api_request(self, request, captcha_response=None):
97105
url = self.API_URL + request._method_name
98106
method_args = request._api._method_default_args.copy()
99107
method_args.update(stringify_values(request._method_args))
100-
if self.access_token:
101-
method_args['access_token'] = self.access_token
108+
access_token = self.access_token
109+
if access_token:
110+
method_args['access_token'] = access_token
111+
if captcha_response:
112+
method_args['captcha_sid'] = captcha_response['sid']
113+
method_args['captcha_key'] = captcha_response['key']
102114
timeout = request._api._timeout
103115
response = self.requests_session.post(url, method_args, timeout=timeout)
104116
return response
105117

106-
def on_captcha_is_needed(self, error_data, method_request):
118+
def get_captcha_key(self, captcha_image_url):
107119
"""
108120
Default behavior on CAPTCHA is to raise exception
109121
Reload this in child
110122
"""
111-
raise VkAPIMethodError(error_data)
123+
return None
112124

113125
def auth_code_is_needed(self, content, session):
114126
"""

vk/exceptions.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,39 @@ class VkAuthError(VkException):
1616
pass
1717

1818

19-
class VkAPIMethodError(VkException):
19+
class VkAPIError(VkException):
2020
__slots__ = ['error', 'code', 'message', 'request_params', 'redirect_uri']
2121

22-
def __init__(self, error):
23-
super(VkAPIMethodError, self).__init__()
24-
self.error = error
25-
self.code = error.get('error_code')
26-
self.message = error.get('error_msg')
27-
self.request_params = error.get('request_params')
28-
self.redirect_uri = error.get('redirect_uri')
22+
CAPTCHA_NEEDED = 14
23+
ACCESS_DENIED = 15
24+
25+
def __init__(self, error_data):
26+
super(VkAPIError, self).__init__()
27+
self.error_data = error_data
28+
self.code = error_data.get('error_code')
29+
self.message = error_data.get('error_msg')
30+
self.request_params = self.get_pretty_request_params(error_data)
31+
self.redirect_uri = error_data.get('redirect_uri')
32+
33+
@staticmethod
34+
def get_pretty_request_params(error_data):
35+
request_params = error_data.get('request_params', ())
36+
request_params = {param['key']: param['value'] for param in request_params}
37+
return request_params
38+
39+
def is_access_token_incorrect(self):
40+
return self.code == self.ACCESS_DENIED and 'access_token' in self.message
41+
42+
def is_captcha_needed(self):
43+
return self.code == self.CAPTCHA_NEEDED
44+
45+
@property
46+
def captcha_sid(self):
47+
return self.error_data.get('captcha_sid')
48+
49+
@property
50+
def captcha_img(self):
51+
return self.error_data.get('captcha_img')
2952

3053
def __str__(self):
3154
error_message = '{self.code}. {self.message}. request_params = {self.request_params}'.format(self=self)

vk/mixins.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,12 @@ def auth_captcha_is_needed(self, response, login_form_data):
167167
if not captcha_form_action:
168168
raise VkAuthError('Cannot find form url')
169169

170+
# todo Are we sure that `response_url_dict` doesn't contain CAPTCHA image url?
170171
captcha_url = '%s?s=%s&sid=%s' % (self.CAPTCHA_URI, response_url_dict['s'], response_url_dict['sid'])
171172
# logger.debug('Captcha url %s', captcha_url)
172173

173174
login_form_data['captcha_sid'] = response_url_dict['sid']
174-
login_form_data['captcha_key'] = self.on_captcha_is_needed(captcha_url)
175+
login_form_data['captcha_key'] = self.get_captcha_key(captcha_url)
175176

176177
response = self.auth_session.post(captcha_form_action, login_form_data)
177178

@@ -204,12 +205,12 @@ def get_access_token(self):
204205
access_token = raw_input('VK API access token: ')
205206
return access_token
206207

207-
def on_captcha_is_needed(self, url):
208+
def get_captcha_key(self, captcha_image_url):
208209
"""
209210
Read CAPTCHA key from shell
210211
"""
211-
print('Open captcha url:', url)
212-
captcha_key = raw_input('Enter captcha key: ')
212+
print('Open CAPTCHA image url: ', captcha_image_url)
213+
captcha_key = raw_input('Enter CAPTCHA key: ')
213214
return captcha_key
214215

215216
def get_auth_check_code(self):

0 commit comments

Comments
 (0)