Skip to content
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
6 changes: 4 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
dist: trusty
language: python
python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "3.7"
- "3.8"
- "3.9"
- "3.10"
install:
- pip install -U setuptools
- pip install -U pip
Expand Down
10 changes: 4 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,16 @@ Usage
counter = 0

@retry
@asyncio.coroutine
def fn():
async def fn():
global counter

counter += 1

if counter == 1:
raise RuntimeError

@asyncio.coroutine
def main():
yield from fn()
async def main():
await fn()

loop = asyncio.get_event_loop()

Expand All @@ -50,4 +48,4 @@ Usage
loop.close()


Python 3.3+ is required
Python 3.5+ is required
76 changes: 37 additions & 39 deletions async_retrying.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

logger = logging.getLogger(__name__)

__version__ = '0.2.2'
__version__ = "0.3.0"


propagate = forever = ...
Expand All @@ -23,32 +23,27 @@ class ConditionError(Exception):


def unpartial(fn):
while hasattr(fn, 'func'):
while hasattr(fn, "func"):
fn = fn.func

return fn


def isexception(obj):
return (
isinstance(obj, Exception) or
(inspect.isclass(obj) and (issubclass(obj, Exception)))
return isinstance(obj, Exception) or (
inspect.isclass(obj) and (issubclass(obj, Exception))
)


@asyncio.coroutine
def callback(attempt, exc, args, kwargs, delay=None, *, loop):
async def callback(attempt, exc, args, kwargs, delay=None, *, loop):
if delay is None:
delay = callback.delay
delay = getattr(callback, "delay", 0.5)

yield from asyncio.sleep(attempt * delay, loop=loop)
await asyncio.sleep(attempt * delay)

return retry


callback.delay = 0.5


def retry(
fn=None,
*,
Expand All @@ -65,30 +60,26 @@ def retry(
):
def wrapper(fn):
@wraps(fn)
@asyncio.coroutine
def wrapped(*fn_args, **fn_kwargs):
async def wrapped(*fn_args, **fn_kwargs):
if isinstance(loop, str):
assert cls ^ kwargs, 'choose self.loop or kwargs["loop"]'

if cls:
_self = getattr(unpartial(fn), '__self__', None)
_self = getattr(unpartial(fn), "__self__", None)

if _self is None:
assert fn_args, 'seems not unbound function'
assert fn_args, "seems not unbound function"
_self = fn_args[0]

_loop = getattr(_self, loop)
elif kwargs:
_loop = fn_kwargs[loop]
elif loop is None:
_loop = asyncio.get_event_loop()
_loop = asyncio.get_running_loop()
else:
_loop = loop

if (
timeout is not None and
asyncio.TimeoutError not in retry_exceptions
):
if timeout is not None and asyncio.TimeoutError not in retry_exceptions:
_retry_exceptions = (asyncio.TimeoutError,) + retry_exceptions
else:
_retry_exceptions = retry_exceptions
Expand Down Expand Up @@ -126,15 +117,20 @@ def wrapped(*fn_args, **fn_kwargs):

if timeout is None:
if asyncio.iscoroutinefunction(unpartial(fn)):
ret = yield from ret
ret = await ret
else:
if not asyncio.iscoroutinefunction(unpartial(fn)):
raise ConditionError(
'Can\'t set timeout for non coroutinefunction',
"Can't set timeout for non coroutinefunction",
)

with async_timeout.timeout(timeout, loop=_loop):
ret = yield from ret
# Note no async_timeout shortcuts here
# because we must keep a loop passed from the outside.
async with async_timeout.Timeout(
_loop.time() + timeout,
loop=_loop,
):
ret = await ret

return ret

Expand All @@ -143,20 +139,17 @@ def wrapped(*fn_args, **fn_kwargs):
except fatal_exceptions:
raise
except _retry_exceptions as exc:
_attempts = 'infinity' if attempts is forever else attempts
_attempts = "infinity" if attempts is forever else attempts
context = {
'fn': fn,
'attempt': attempt,
'attempts': _attempts,
"fn": fn,
"attempt": attempt,
"attempts": _attempts,
}

if (
_loop.get_debug() or
(attempts is not forever and attempt == attempts)
):

if attempts is not forever and attempt == attempts:
logger.warning(
exc.__class__.__name__ + ' -> Attempts (%(attempt)d) are over for %(fn)r', # noqa
exc.__class__.__name__
+ " -> Attempts (%(attempt)d) are over for %(fn)r", # noqa
context,
exc_info=exc,
)
Expand All @@ -170,26 +163,31 @@ def wrapped(*fn_args, **fn_kwargs):
ret = fallback(fn_args, fn_kwargs, loop=_loop)

if asyncio.iscoroutinefunction(unpartial(fallback)): # noqa
ret = yield from ret
ret = await ret
else:
ret = fallback

return ret

logger.debug(
exc.__class__.__name__ + ' -> Tried attempt #%(attempt)d from total %(attempts)s for %(fn)r', # noqa
exc.__class__.__name__
+ " -> Tried attempt #%(attempt)d from total %(attempts)s for %(fn)r", # noqa
context,
exc_info=exc,
)

ret = callback(
attempt, exc, fn_args, fn_kwargs, loop=_loop,
attempt,
exc,
fn_args,
fn_kwargs,
loop=_loop,
)

attempt += 1

if asyncio.iscoroutinefunction(unpartial(callback)):
ret = yield from ret
ret = await ret

if ret is not retry:
return ret
Expand Down
50 changes: 21 additions & 29 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
appnope==0.1.0
async-timeout==1.2.1
coverage==4.4.1
decorator==4.0.11
flake8==3.3.0
ipdb==0.10.3
ipython==6.1.0
ipython-genutils==0.2.0
isort==4.2.15
jedi==0.10.2
mccabe==0.6.1
pexpect==4.2.1
pickleshare==0.7.4
pluggy==0.4.0
prompt-toolkit==1.0.14
ptyprocess==0.5.2
py==1.4.34
pycodestyle==2.3.1
pyflakes==1.5.0
Pygments==2.2.0
pytest==3.1.3
pytest-cov==2.5.1
pytest-mock==1.6.3
simplegeneric==0.8.1
six==1.10.0
tox==2.7.0
traitlets==4.3.2
virtualenv==15.1.0
wcwidth==0.1.7
async-timeout==4.0.2
attrs==22.1.0
black==22.10.0
click==8.1.3
colorama==0.4.6
coverage==6.5.0
exceptiongroup==1.0.4
iniconfig==1.1.1
mypy==0.991
mypy-extensions==0.4.3
packaging==21.3
pathspec==0.10.2
platformdirs==2.5.4
pluggy==1.0.0
pyparsing==3.0.9
pytest==7.2.0
pytest-asyncio==0.20.2
pytest-cov==4.0.0
pytest-mock==3.10.0
tomli==2.0.1
typing_extensions==4.4.0
61 changes: 28 additions & 33 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
import io
import os
import re
from pathlib import Path

from setuptools import setup

home = Path(__file__).parent
readme = home / "README.rst"

def get_version():
regex = r"__version__\s=\s\'(?P<version>[\d\.]+?)\'"

path = ('async_retrying.py',)

return re.search(regex, read(*path)).group('version')


def read(*parts):
filename = os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts)

with io.open(filename, encoding='utf-8', mode='rt') as fp:
return fp.read()
def get_version():
regex = re.compile(r'__version__ = "(?P<version>.+)"', re.M)
match = regex.search((home / "async_retrying.py").read_text())
return match.group("version")


setup(
name='async_retrying',
name="async_retrying",
version=get_version(),
author='OCEAN S.A.',
author_email='osf@ocean.io',
url='https://github.com/wikibusiness/async_retrying',
description='Simple retrying for asyncio',
long_description=read('README.rst'),
author="OCEAN S.A.",
author_email="osf@ocean.io",
url="https://github.com/wikibusiness/async_retrying",
description="Simple retrying for asyncio",
long_description=readme.read_text(),
install_requires=[
'async_timeout',
"async_timeout",
],
extras_require={
':python_version=="3.3"': ['asyncio'],
':python_version=="3.5"': ["asyncio"],
},
py_modules=['async_retrying'],
py_modules=["async_retrying"],
include_package_data=True,
zip_safe=False,
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
keywords=['asyncio', 'retrying'],
keywords=["asyncio", "retrying"],
)
49 changes: 0 additions & 49 deletions tests/conftest.py

This file was deleted.

Loading