Skip to content

Commit

Permalink
Merge pull request #6 from lorien/issue_5
Browse files Browse the repository at this point in the history
Retry network request if it has been failed
  • Loading branch information
lorien committed Mar 8, 2017
2 parents b09a0c6 + 6e3afe7 commit df733c1
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.1.0
current_version = 0.1.1
files = setup.py captcha_solver/__init__.py
commit = True
tag = True
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
/.cache/
/captcha.jpg
/test.py
/var/
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ python: 2.7

env:
- TOX_ENV=py27
- TOX_ENV=py33
- TOX_ENV=py34

matrix:
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Change Log of user_agent Library

## [0.1.2] - Unreleased

## [0.1.1] - 2017-03-08
### Changed
- Retry network request if network error occured

## [0.1.0] - 2017-01-25
### Added
- Add rucaptcha backend

### Removed
- Remove Grab support

## [0.0.3] - 2015-08-10
### Fixed
- Fix GUI backend

## [0.0.2] - 2015-08-10
### Fixed
- Fix py3 issues

### Changed
- Multiple improvements, by @kalombo
2 changes: 1 addition & 1 deletion captcha_solver/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from captcha_solver.solver import CaptchaSolver # noqa
from captcha_solver.error import * # noqa

__version__ = '0.1.0'
__version__ = '0.1.1'
4 changes: 2 additions & 2 deletions captcha_solver/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
from six.moves.urllib.parse import urlencode


def request(url, data):
def request(url, data, timeout):
if data:
req_data = urlencode(data).encode('ascii')
else:
req_data = None
req = Request(url, req_data)
try:
response = urlopen(req)
response = urlopen(req, timeout=timeout)
body = response.read()
code = response.getcode()
except HTTPError as e:
Expand Down
56 changes: 43 additions & 13 deletions captcha_solver/solver.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import logging
import time
import socket

import six
from six.moves.urllib.error import URLError

from .error import (SolutionNotReady, SolutionTimeoutError,
ServiceTooBusy, InvalidServiceBackend)
Expand All @@ -18,6 +21,7 @@
'browser': BrowserBackend,
'gui': GuiBackend,
}
DEFAULT_NETWORK_TIMEOUT = 5


class InvalidBackend(Exception):
Expand All @@ -37,6 +41,11 @@ class inherited from SolverBackend
"""
self.backend = self._initialize_backend(backend)
self.backend.setup(**kwargs)
self.network_config = {}
self.setup_network_config()

def setup_network_config(self, timeout=DEFAULT_NETWORK_TIMEOUT):
self.network_config['timeout'] = timeout

def _initialize_backend(self, backend):
if isinstance(backend, ServiceBackend):
Expand All @@ -50,7 +59,8 @@ def submit_captcha(self, image_data, **kwargs):
logger.debug('Submiting captcha')
data = self.backend.get_submit_captcha_request_data(image_data,
**kwargs)
response = request(data['url'], data['post_data'])
response = request(data['url'], data['post_data'],
timeout=self.network_config['timeout'])
return self.backend.parse_submit_captcha_response(response)

def check_solution(self, captcha_id):
Expand All @@ -61,28 +71,48 @@ def check_solution(self, captcha_id):
"""

data = self.backend.get_check_solution_request_data(captcha_id)
response = request(data['url'], data['post_data'])
response = request(data['url'], data['post_data'],
timeout=self.network_config['timeout'])
return self.backend.parse_check_solution_response(response)

def solve_captcha(self, data, submiting_time=30, submiting_delay=3,
recognition_time=120, recognition_delay=5, **kwargs):

delay = submiting_delay or 1
for _ in range(0, submiting_time, delay):
assert submiting_delay > 0
assert recognition_delay > 0

for _ in range(0, submiting_time, submiting_delay):
fail = None
try:
captcha_id = self.submit_captcha(image_data=data, **kwargs)
break
except ServiceTooBusy:
except (ServiceTooBusy, URLError, socket.error) as ex:
fail = ex
time.sleep(submiting_delay)
else:
break
else:
raise SolutionTimeoutError("Service has not available slots after "
"%s seconds" % submiting_time)
if isinstance(fail, ServiceTooBusy):
raise SolutionTimeoutError('Service has not available slots'
' after %s seconds'
% submiting_time)
else:
raise SolutionTimeoutError('Service is not available.'
' Error: %s' % ex)

delay = recognition_delay or 1
for _ in range(0, recognition_time, delay):
for _ in range(0, recognition_time, recognition_delay):
fail = None
try:
return self.check_solution(captcha_id)
except SolutionNotReady:
except (SolutionNotReady, ServiceTooBusy,
URLError, socket.error) as ex:
fail = ex
time.sleep(recognition_delay)
raise SolutionTimeoutError("Captcha is not ready after "
"%s seconds" % recognition_time)
else:
break
else:
if isinstance(fail, (ServiceTooBusy, SolutionNotReady)):
raise SolutionTimeoutError('Captcha is not ready after'
' %s seconds' % recognition_time)
else:
raise SolutionTimeoutError('Service is not available.'
' Error: %s' % ex)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name = 'captcha-solver',
version = '0.1.0',
version = '0.1.1',
description = 'Universal API to captcha solving services',
long_description = open(os.path.join(ROOT, 'README.rst')).read(),
author = 'Gregory Petukhov',
Expand Down
85 changes: 79 additions & 6 deletions test/test_solver.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
from unittest import TestCase
from copy import copy
import time

from six import string_types
from test_server import TestServer

from captcha_solver import error
from captcha_solver import CaptchaSolver
from .base import BaseSolverTestCase, NO_DELAY
from six import string_types

NO_DELAY = {'recognition_time': 1,
'recognition_delay': 1,
'submiting_time': 1,
'submiting_delay': 1}


class BaseSolverTestCase(TestCase):
@classmethod
def setUpClass(cls):
cls.server = TestServer()
cls.server.start()

@classmethod
def tearDownClass(cls):
cls.server.stop()

def setUp(self):
self.server.reset()


class AntigateTestCase(BaseSolverTestCase):
def setup_solver(self):
self.solver = CaptchaSolver('antigate',
service_url=self.server.get_url(),
api_key='does not matter')
def setUp(self):
super(AntigateTestCase, self).setUp()
self.solver = self.create_solver()

def create_solver(self, **kwargs):
config = {
'service_url': self.server.get_url(),
'api_key': 'does not matter',
}
config.update(kwargs)
return CaptchaSolver('antigate', **config)

def test_post_data(self):
data = b'foo'
Expand Down Expand Up @@ -73,3 +104,45 @@ def handler():
self.server.response['data'] = handler()
self.assertRaises(error.CaptchaServiceError, self.solver.solve_captcha,
b'image_data', **NO_DELAY)

def test_network_error_while_sending_captcha(self):
def handler():
yield b'that would be timed out'
yield b'OK|captcha_id'
yield b'OK|decoded_captcha'

solver = self.create_solver()
solver.setup_network_config(timeout=1)
self.server.response['data'] = handler()
self.server.response_once['sleep'] = 1.005
delays = copy(NO_DELAY)
delays.update(dict(submiting_time=2, submiting_delay=1))
solver.solve_captcha(b'image_data', **delays)

def test_network_error_while_receiving_solution(self):

class Callback(object):
def __init__(self):
self.step = 0

def __call__(self, server):
self.step += 1
if self.step == 1:
server.write(b'OK|captcha_id')
server.finish()
elif self.step in (2, 3, 4):
time.sleep(1.005)
server.write(b'that would be timed out')
server.finish()
elif self.step > 4:
server.write(b'OK|solution')
server.finish()

solver = self.create_solver()
solver.setup_network_config(timeout=1)
self.server.response['callback'] = Callback()
delays = copy(NO_DELAY)
delays.update(dict(submiting_time=2, submiting_delay=1,
recognition_time=4, recognition_delay=1))
solution = solver.solve_captcha(b'image_data', **delays)
assert solution == 'solution'
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27,py34
envlist = py2,py3

[testenv]
commands =
Expand Down

0 comments on commit df733c1

Please sign in to comment.