Skip to content

Commit

Permalink
Merge 51150c4 into b070525
Browse files Browse the repository at this point in the history
  • Loading branch information
stj committed Aug 15, 2018
2 parents b070525 + 51150c4 commit cfd3586
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 144 deletions.
File renamed without changes.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ language: python
python:
- "2.6"
- "2.7"
- "3.4"
- "3.5"
- "3.6"
matrix:
Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PY_VERSION := $(wordlist 2,4,$(subst ., ,$(shell python --version 2>&1)))
PY_MAJOR := $(word 1,${PY_VERSION})
PY_MINOR := $(word 2,${PY_VERSION})
PY_GTE_34 = $(shell echo $(PY_MAJOR).$(PY_MINOR)\>=3.4 | bc)
PY_GTE_35 = $(shell echo $(PY_MAJOR).$(PY_MINOR)\>=3.5 | bc)
PY_GTE_27 = $(shell echo $(PY_MAJOR).$(PY_MINOR)\>=2.7 | bc)


Expand All @@ -18,10 +18,10 @@ pep8:
@pep8 backoff tests

flake8:
ifeq ($(PY_GTE_34),1)
ifeq ($(PY_GTE_35),1)
@flake8 backoff tests
else ifeq ($(PY_GTE_27),1)
@flake8 --exclude tests/python34,backoff/_async.py backoff tests
@flake8 --exclude tests/python35,backoff/_async.py backoff tests
else
@echo 'Not running flake8 for Python < 2.7'
endif
Expand All @@ -32,8 +32,8 @@ clean:
@rm -rf build dist .coverage MANIFEST

test: clean
ifeq ($(PY_GTE_34),1)
@PYTHONPATH=. py.test --cov-config .coveragerc-py34 --cov backoff tests
ifeq ($(PY_GTE_35),1)
@PYTHONPATH=. py.test --cov-config .coveragerc-py35 --cov backoff tests
else
@PYTHONPATH=. py.test --cov-config .coveragerc-py2 --cov backoff tests/test_*.py
endif
Expand Down
17 changes: 1 addition & 16 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -284,22 +284,7 @@ On Python 3.5 and above with ``async def`` and ``await`` syntax:
async with session.get(url) as response:
return await response.text()
In case you use Python 3.4 you can use `@asyncio.coroutine` and `yield from`:

.. code-block:: python
@backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60)
@asyncio.coroutine
def get_url_py34(url):
with aiohttp.ClientSession() as session:
response = yield from session.get(url)
try:
return (yield from response.text())
except Exception:
response.close()
raise
finally:
yield from response.release()
Python 3.4 is not supported.

Logging configuration
---------------------
Expand Down
33 changes: 15 additions & 18 deletions backoff/_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ def _ensure_coroutines(coros_or_funcs):
return [_ensure_coroutine(f) for f in coros_or_funcs]


@asyncio.coroutine
def _call_handlers(hdlrs, target, args, kwargs, tries, elapsed, **extra):
async def _call_handlers(hdlrs, target, args, kwargs, tries, elapsed, **extra):
details = {
'target': target,
'args': args,
Expand All @@ -31,7 +30,7 @@ def _call_handlers(hdlrs, target, args, kwargs, tries, elapsed, **extra):
}
details.update(extra)
for hdlr in hdlrs:
yield from hdlr(details)
await hdlr(details)


def retry_predicate(target, wait_gen, predicate,
Expand All @@ -49,8 +48,7 @@ def retry_predicate(target, wait_gen, predicate,
assert asyncio.iscoroutinefunction(target)

@functools.wraps(target)
@asyncio.coroutine
def retry(*args, **kwargs):
async def retry(*args, **kwargs):

# change names because python 2.x doesn't have nonlocal
max_tries_ = _maybe_call(max_tries)
Expand All @@ -64,20 +62,20 @@ def retry(*args, **kwargs):
elapsed = _total_seconds(datetime.datetime.now() - start)
details = (target, args, kwargs, tries, elapsed)

ret = yield from target(*args, **kwargs)
ret = await target(*args, **kwargs)
if predicate(ret):
max_tries_exceeded = (tries == max_tries_)
max_time_exceeded = (max_time_ is not None and
elapsed >= max_time_)

if max_tries_exceeded or max_time_exceeded:
yield from _call_handlers(
await _call_handlers(
giveup_hdlrs, *details, value=ret)
break

seconds = _next_wait(wait, jitter, elapsed, max_time_)

yield from _call_handlers(
await _call_handlers(
backoff_hdlrs, *details, value=ret, wait=seconds)

# Note: there is no convenient way to pass explicit event
Expand All @@ -89,10 +87,10 @@ def retry(*args, **kwargs):
# See for details:
# <https://groups.google.com/forum/#!topic/python-tulip/yF9C-rFpiKk>
# <https://bugs.python.org/issue28613>
yield from asyncio.sleep(seconds)
await asyncio.sleep(seconds)
continue
else:
yield from _call_handlers(success_hdlrs, *details, value=ret)
await _call_handlers(success_hdlrs, *details, value=ret)
break

return ret
Expand All @@ -114,8 +112,7 @@ def retry_exception(target, wait_gen, exception,
assert not asyncio.iscoroutinefunction(jitter)

@functools.wraps(target)
@asyncio.coroutine
def retry(*args, **kwargs):
async def retry(*args, **kwargs):
# change names because python 2.x doesn't have nonlocal
max_tries_ = _maybe_call(max_tries)
max_time_ = _maybe_call(max_time)
Expand All @@ -129,20 +126,20 @@ def retry(*args, **kwargs):
details = (target, args, kwargs, tries, elapsed)

try:
ret = yield from target(*args, **kwargs)
ret = await target(*args, **kwargs)
except exception as e:
giveup_result = yield from giveup(e)
giveup_result = await giveup(e)
max_tries_exceeded = (tries == max_tries_)
max_time_exceeded = (max_time_ is not None and
elapsed >= max_time_)

if giveup_result or max_tries_exceeded or max_time_exceeded:
yield from _call_handlers(giveup_hdlrs, *details)
await _call_handlers(giveup_hdlrs, *details)
raise

seconds = _next_wait(wait, jitter, elapsed, max_time_)

yield from _call_handlers(
await _call_handlers(
backoff_hdlrs, *details, wait=seconds)

# Note: there is no convenient way to pass explicit event
Expand All @@ -154,9 +151,9 @@ def retry(*args, **kwargs):
# See for details:
# <https://groups.google.com/forum/#!topic/python-tulip/yF9C-rFpiKk>
# <https://bugs.python.org/issue28613>
yield from asyncio.sleep(seconds)
await asyncio.sleep(seconds)
else:
yield from _call_handlers(success_hdlrs, *details)
await _call_handlers(success_hdlrs, *details)

return ret
return retry
37 changes: 37 additions & 0 deletions tests/python35/test_aiohttp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pytest

import backoff

aiohttp = pytest.importorskip("aiohttp")
aiohttp.web = pytest.importorskip("aiohttp.web")
aiohttp.web_exceptions = pytest.importorskip("aiohttp.web_exceptions")


async def test_backoff_on_all_session_requests(aiohttp_client):
call_count = 0

async def handler(request):
nonlocal call_count
call_count += 1
raise aiohttp.web_exceptions.HTTPUnauthorized

app = aiohttp.web.Application()
app.router.add_get('/', handler)
client = await aiohttp_client(
app,
connector=aiohttp.TCPConnector(limit=1),
raise_for_status=True,
)

# retry every session request
client._session._request = backoff.on_exception(
wait_gen=backoff.expo,
exception=aiohttp.client_exceptions.ClientResponseError,
max_tries=2,
)(client._session._request)

with pytest.raises(aiohttp.client_exceptions.ClientResponseError) as exc:
await client.get('/')

assert exc.value.code == 401
assert call_count == 2

0 comments on commit cfd3586

Please sign in to comment.