Hello community, here is the log from the commit of package python-backoff for openSUSE:Factory checked in at 2019-06-06 18:16:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-backoff (Old) and /work/SRC/openSUSE:Factory/.python-backoff.new.4811 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-backoff" Thu Jun 6 18:16:46 2019 rev:3 rq:707407 version:1.8.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-backoff/python-backoff.changes 2018-12-12 17:25:54.395073315 +0100 +++ /work/SRC/openSUSE:Factory/.python-backoff.new.4811/python-backoff.changes 2019-06-06 18:16:47.116695464 +0200 @@ -1,0 +2,12 @@ +Tue Jun 4 06:33:28 UTC 2019 - [email protected] + +- version update to 1.8.0 + - Change default log level from ERROR to INFO + - Log retries on exception as INFO + - Support Python 3.7 + - Give up on StopIteration raised in wait generators + - Iterable intervals for constant wait_gen for predefined wait sequences + - Nullary jitter signature deprecation warning + - Custom loggers + +------------------------------------------------------------------- Old: ---- backoff-1.5.0.tar.gz New: ---- backoff-1.8.0.tar.gz tests.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-backoff.spec ++++++ --- /var/tmp/diff_new_pack.RJUx2Z/_old 2019-06-06 18:16:47.984695210 +0200 +++ /var/tmp/diff_new_pack.RJUx2Z/_new 2019-06-06 18:16:47.992695208 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-backoff # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,16 +17,21 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} -%bcond_without test Name: python-backoff -Version: 1.5.0 +Version: 1.8.0 Release: 0 Summary: Function decoration for backoff and retry License: MIT Group: Development/Languages/Python Url: https://github.com/litl/backoff -Source: https://files.pythonhosted.org/packages/source/b/backoff/backoff-%{version}.tar.gz +Source0: https://files.pythonhosted.org/packages/source/b/backoff/backoff-%{version}.tar.gz +# https://github.com/litl/backoff/issues/75 +# github repo does not have setup.py +Source1: tests.tar.bz2 BuildRequires: %{python_module setuptools} +# SECTION test requirements +BuildRequires: %{python_module pytest} +# /SECTION BuildRequires: fdupes BuildRequires: python-rpm-macros BuildArch: noarch @@ -46,7 +51,7 @@ for asynchronous code. %prep -%setup -q -n backoff-%{version} +%setup -q -n backoff-%{version} -a1 %build %python_build @@ -55,6 +60,11 @@ %python_install %python_expand %fdupes %{buildroot}%{$python_sitelib} +%check +# will not work with python 2.7 +rm -r tests/python35 +%pytest + %files %{python_files} %defattr(-,root,root,-) %doc README.rst ++++++ backoff-1.5.0.tar.gz -> backoff-1.8.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/PKG-INFO new/backoff-1.8.0/PKG-INFO --- old/backoff-1.5.0/PKG-INFO 2018-04-11 17:03:41.000000000 +0200 +++ new/backoff-1.8.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,350 +1,26 @@ -Metadata-Version: 1.1 +Metadata-Version: 2.1 Name: backoff -Version: 1.5.0 +Version: 1.8.0 Summary: Function decoration for backoff and retry Home-page: https://github.com/litl/backoff Author: Bob Green Author-email: [email protected] -License: MIT -Download-URL: https://github.com/litl/backoff/tarball/v1.5.0 -Description: backoff - ======= - - .. image:: https://travis-ci.org/litl/backoff.svg?branch=master - :target: https://travis-ci.org/litl/backoff?branch=master - .. image:: https://coveralls.io/repos/litl/backoff/badge.svg?branch=master - :target: https://coveralls.io/r/litl/backoff?branch=master - .. image:: https://img.shields.io/pypi/v/backoff.svg - :target: https://pypi.python.org/pypi/backoff - - **Function decoration for backoff and retry** - - This module provides function decorators which can be used to wrap a - function such that it will be retried until some condition is met. It - is meant to be of use when accessing unreliable resources with the - potential for intermittent failures i.e. network resources and external - APIs. Somewhat more generally, it may also be of use for dynamically - polling resources for externally generated content. - - Decorators support both regular functions for synchronous code and - `asyncio <https://docs.python.org/3/library/asyncio.html>`_'s coroutines - for asynchronous code. - - Examples - ======== - - Since Kenneth Reitz's `requests <http://python-requests.org>`_ module - has become a defacto standard for synchronous HTTP clients in Python, - networking examples below are written using it, but it is in no way required - by the backoff module. - - @backoff.on_exception - --------------------- - - The ``on_exception`` decorator is used to retry when a specified exception - is raised. Here's an example using exponential backoff when any - ``requests`` exception is raised: - - .. code-block:: python - - @backoff.on_exception(backoff.expo, - requests.exceptions.RequestException) - def get_url(url): - return requests.get(url) - - The decorator will also accept a tuple of exceptions for cases where - you want the same backoff behavior for more than one exception type: - - .. code-block:: python - - @backoff.on_exception(backoff.expo, - (requests.exceptions.Timeout, - requests.exceptions.ConnectionError)) - def get_url(url): - return requests.get(url) - - **Give Up Conditions** - - Optional keyword arguments can specify conditions under which to give - up. - - The keyword argument ``max_time`` specifies the maximum amount - of total time in seconds that can elapse before giving up. - - .. code-block:: python - - @backoff.on_exception(backoff.expo, - requests.exceptions.RequestException, - max_time=60) - def get_url(url): - return requests.get(url) - - - Keyword argument ``max_tries`` specifies the maximum number of calls - to make to the target function before giving up. - - .. code-block:: python - - @backoff.on_exception(backoff.expo, - requests.exceptions.RequestException, - max_tries=8, - jitter=None) - def get_url(url): - return requests.get(url) - - - In some cases the raised exception instance itself may need to be - inspected in order to determine if it is a retryable condition. The - ``giveup`` keyword arg can be used to specify a function which accepts - the exception and returns a truthy value if the exception should not - be retried: - - .. code-block:: python - - def fatal_code(e): - return 400 <= e.response.status_code < 500 - - @backoff.on_exception(backoff.expo, - requests.exceptions.RequestException, - max_time=300, - giveup=fatal_code) - def get_url(url): - return requests.get(url) - - When a give up event occurs, the exception in question is reraised - and so code calling an `on_exception`-decorated function may still - need to do exception handling. - - @backoff.on_predicate - --------------------- - - The ``on_predicate`` decorator is used to retry when a particular - condition is true of the return value of the target function. This may - be useful when polling a resource for externally generated content. - - Here's an example which uses a fibonacci sequence backoff when the - return value of the target function is the empty list: - - .. code-block:: python - - @backoff.on_predicate(backoff.fibo, lambda x: x == [], max_value=13) - def poll_for_messages(queue): - return queue.get() - - Extra keyword arguments are passed when initializing the - wait generator, so the ``max_value`` param above is passed as a keyword - arg when initializing the fibo generator. - - When not specified, the predicate param defaults to the falsey test, - so the above can more concisely be written: - - .. code-block:: python - - @backoff.on_predicate(backoff.fibo, max_value=13) - def poll_for_message(queue) - return queue.get() - - More simply, a function which continues polling every second until it - gets a non-falsey result could be defined like like this: - - .. code-block:: python - - @backoff.on_predicate(backoff.constant, interval=1) - def poll_for_message(queue) - return queue.get() - - Jitter - ------ - - A jitter algorithm can be supplied with the ``jitter`` keyword arg to - either of the backoff decorators. This argument should be a function - accepting the original unadulterated backoff value and returning it's - jittered counterpart. - - As of version 1.2, the default jitter function ``backoff.full_jitter`` - implements the 'Full Jitter' algorithm as defined in the AWS - Architecture Blog's `Exponential Backoff And Jitter - <https://www.awsarchitectureblog.com/2015/03/backoff.html>`_ post. - Note that with this algorithm, the time yielded by the wait generator - is actually the *maximum* amount of time to wait. - - Previous versions of backoff defaulted to adding some random number of - milliseconds (up to 1s) to the raw sleep value. If desired, this - behavior is now available as ``backoff.random_jitter``. - - Using multiple decorators - ------------------------- - - The backoff decorators may also be combined to specify different - backoff behavior for different cases: - - .. code-block:: python - - @backoff.on_predicate(backoff.fibo, max_value=13) - @backoff.on_exception(backoff.expo, - requests.exceptions.HTTPError, - max_time=60) - @backoff.on_exception(backoff.expo, - requests.exceptions.TimeoutError, - max_time=300) - def poll_for_message(queue): - return queue.get() - - Runtime Configuration - --------------------- - - The decorator functions ``on_exception`` and ``on_predicate`` are - generally evaluated at import time. This is fine when the keyword args - are passed as constant values, but suppose we want to consult a - dictionary with configuration options that only become available at - runtime. The relevant values are not available at import time. Instead, - decorator functions can be passed callables which are evaluated at - runtime to obtain the value: - - .. code-block:: python - - def lookup_max_time(): - # pretend we have a global reference to 'app' here - # and that it has a dictionary-like 'config' property - return app.config["BACKOFF_MAX_TIME"] - - @backoff.on_exception(backoff.expo, - ValueError, - max_time=lookup_max_time) - - Event handlers - -------------- - - Both backoff decorators optionally accept event handler functions - using the keyword arguments ``on_success``, ``on_backoff``, and ``on_giveup``. - This may be useful in reporting statistics or performing other custom - logging. - - Handlers must be callables with a unary signature accepting a dict - argument. This dict contains the details of the invocation. Valid keys - include: - - * *target*: reference to the function or method being invoked - * *args*: positional arguments to func - * *kwargs*: keyword arguments to func - * *tries*: number of invocation tries so far - * *elapsed*: elapsed time in seconds so far - * *wait*: seconds to wait (``on_backoff`` handler only) - * *value*: value triggering backoff (``on_predicate`` decorator only) - - A handler which prints the details of the backoff event could be - implemented like so: - - .. code-block:: python - - def backoff_hdlr(details): - print ("Backing off {wait:0.1f} seconds afters {tries} tries " - "calling function {target} with args {args} and kwargs " - "{kwargs}".format(**details)) - - @backoff.on_exception(backoff.expo, - requests.exceptions.RequestException, - on_backoff=backoff_hdlr) - def get_url(url): - return requests.get(url) - - **Multiple handlers per event type** - - In all cases, iterables of handler functions are also accepted, which - are called in turn. For example, you might provide a simple list of - handler functions as the value of the ``on_backoff`` keyword arg: - - .. code-block:: python - - @backoff.on_exception(backoff.expo, - requests.exceptions.RequestException, - on_backoff=[backoff_hdlr1, backoff_hdlr2]) - def get_url(url): - return requests.get(url) - - **Getting exception info** - - In the case of the ``on_exception`` decorator, all ``on_backoff`` and - ``on_giveup`` handlers are called from within the except block for the - exception being handled. Therefore exception info is available to the - handler functions via the python standard library, specifically - ``sys.exc_info()`` or the ``traceback`` module. - - Asynchronous code - ----------------- - - To use backoff in asynchronous code based on - `asyncio <https://docs.python.org/3/library/asyncio.html>`_ - you simply need to apply ``backoff.on_exception`` or ``backoff.on_predicate`` - to coroutines. - You can also use coroutines for the ``on_success``, ``on_backoff``, and - ``on_giveup`` event handlers, with the interface otherwise being identical. - - The following examples use `aiohttp <https://aiohttp.readthedocs.io/>`_ - asynchronous HTTP client/server library. - - On Python 3.5 and above with ``async def`` and ``await`` syntax: - - .. code-block:: python - - @backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60) - async def get_url(url): - async with aiohttp.ClientSession() as session: - 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() - - Logging configuration - --------------------- - - Errors and backoff and retry attempts are logged to the 'backoff' - logger. By default, this logger is configured with a NullHandler, so - there will be nothing output unless you configure a handler. - Programmatically, this might be accomplished with something as simple - as: - - .. code-block:: python - - logging.getLogger('backoff').addHandler(logging.StreamHandler()) - - The default logging level is ERROR, which corresponds to logging - anytime a giveup event occurs. If you would instead like to log - anytime a retry occurs, set the logger level to INFO. - - .. code-block:: python - - logging.getLogger('backoff').setLevel(logging.INFO) - -Keywords: backoff function decorator -Platform: UNKNOWN +Keywords: retry,backoff,decorators +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers -Classifier: Programming Language :: Python Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: Implementation -Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Utilities +Project-URL: Repository, https://github.com/litl/backoff diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/README.rst new/backoff-1.8.0/README.rst --- old/backoff-1.5.0/README.rst 2018-04-11 16:55:35.000000000 +0200 +++ new/backoff-1.8.0/README.rst 2018-12-15 18:22:25.000000000 +0100 @@ -44,7 +44,7 @@ return requests.get(url) The decorator will also accept a tuple of exceptions for cases where -you want the same backoff behavior for more than one exception type: +the same backoff behavior is desired for more than one exception type: .. code-block:: python @@ -264,6 +264,8 @@ Asynchronous code ----------------- +Backoff supports asynchronous execution in Python 3.5 and above. + To use backoff in asynchronous code based on `asyncio <https://docs.python.org/3/library/asyncio.html>`_ you simply need to apply ``backoff.on_exception`` or ``backoff.on_predicate`` @@ -274,8 +276,6 @@ The following examples use `aiohttp <https://aiohttp.readthedocs.io/>`_ asynchronous HTTP client/server library. -On Python 3.5 and above with ``async def`` and ``await`` syntax: - .. code-block:: python @backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60) @@ -284,27 +284,10 @@ 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() - Logging configuration --------------------- -Errors and backoff and retry attempts are logged to the 'backoff' +By default, backoff and retry attempts are logged to the 'backoff' logger. By default, this logger is configured with a NullHandler, so there will be nothing output unless you configure a handler. Programmatically, this might be accomplished with something as simple @@ -314,10 +297,40 @@ logging.getLogger('backoff').addHandler(logging.StreamHandler()) -The default logging level is ERROR, which corresponds to logging -anytime a giveup event occurs. If you would instead like to log -anytime a retry occurs, set the logger level to INFO. +The default logging level is INFO, which corresponds to logging +anytime a retry event occurs. If you would instead like to log +only when a giveup event occurs, set the logger level to ERROR. .. code-block:: python - logging.getLogger('backoff').setLevel(logging.INFO) + logging.getLogger('backoff').setLevel(logging.ERROR) + +It is also possible to specify an alternate logger with the ``logger`` +keyword argument. If a string value is specified the logger will be +looked up by name. + +.. code-block:: python + + @backoff.on_exception(backoff.expo, + requests.exception.RequestException, + logger='my_logger') + # ... + +It is also supported to specify a Logger (or LoggerAdapter) object +directly. + +.. code-block:: python + + my_logger = logging.getLogger('my_logger') + my_handler = logging.StreamHandler() + my_logger.add_handler(my_handler) + my_logger.setLevel(logging.ERROR) + + @backoff.on_exception(backoff.expo, + requests.exception.RequestException, + logger=my_logger) + # ... + +Default logging can be disabled all together by specifying +``logger=None``. In this case, if desired alternative logging behavior +could be defined by using custom event handlers. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/backoff/__init__.py new/backoff-1.8.0/backoff/__init__.py --- old/backoff-1.5.0/backoff/__init__.py 2018-04-11 16:55:35.000000000 +0200 +++ new/backoff-1.8.0/backoff/__init__.py 2018-12-21 06:19:40.000000000 +0100 @@ -26,4 +26,4 @@ 'random_jitter' ] -__version__ = '1.5.0' +__version__ = '1.8.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/backoff/_async.py new/backoff-1.8.0/backoff/_async.py --- old/backoff-1.5.0/backoff/_async.py 2018-04-11 16:55:35.000000000 +0200 +++ new/backoff-1.8.0/backoff/_async.py 2018-12-15 18:22:25.000000000 +0100 @@ -3,10 +3,9 @@ import functools # Python 3.4 code and syntax is allowed in this module! import asyncio +from datetime import timedelta -from backoff._common import (_handlers, _init_wait_gen, - _log_backoff, _log_giveup, _maybe_call, - _next_wait, _total_seconds) +from backoff._common import (_init_wait_gen, _maybe_call, _next_wait) def _ensure_coroutine(coro_or_func): @@ -20,8 +19,7 @@ return [_ensure_coroutine(f) for f in coros_or_funcs] [email protected] -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, @@ -31,16 +29,16 @@ } details.update(extra) for hdlr in hdlrs: - yield from hdlr(details) + await hdlr(details) def retry_predicate(target, wait_gen, predicate, max_tries, max_time, jitter, on_success, on_backoff, on_giveup, wait_gen_kwargs): - success_hdlrs = _ensure_coroutines(_handlers(on_success)) - backoff_hdlrs = _ensure_coroutines(_handlers(on_backoff, _log_backoff)) - giveup_hdlrs = _ensure_coroutines(_handlers(on_giveup, _log_giveup)) + on_success = _ensure_coroutines(on_success) + on_backoff = _ensure_coroutines(on_backoff) + on_giveup = _ensure_coroutines(on_giveup) # Easy to implement, please report if you need this. assert not asyncio.iscoroutinefunction(max_tries) @@ -49,8 +47,7 @@ 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) @@ -61,24 +58,27 @@ wait = _init_wait_gen(wait_gen, wait_gen_kwargs) while True: tries += 1 - elapsed = _total_seconds(datetime.datetime.now() - start) + elapsed = timedelta.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( - giveup_hdlrs, *details, value=ret) + await _call_handlers(on_giveup, *details, value=ret) break - seconds = _next_wait(wait, jitter, elapsed, max_time_) + try: + seconds = _next_wait(wait, jitter, elapsed, max_time_) + except StopIteration: + await _call_handlers(on_giveup, *details, value=ret) + break - yield from _call_handlers( - backoff_hdlrs, *details, value=ret, wait=seconds) + await _call_handlers(on_backoff, *details, value=ret, + wait=seconds) # Note: there is no convenient way to pass explicit event # loop to decorator, so here we assume that either default @@ -89,10 +89,10 @@ # 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(on_success, *details, value=ret) break return ret @@ -104,9 +104,9 @@ max_tries, max_time, jitter, giveup, on_success, on_backoff, on_giveup, wait_gen_kwargs): - success_hdlrs = _ensure_coroutines(_handlers(on_success)) - backoff_hdlrs = _ensure_coroutines(_handlers(on_backoff, _log_backoff)) - giveup_hdlrs = _ensure_coroutines(_handlers(on_giveup, _log_giveup)) + on_success = _ensure_coroutines(on_success) + on_backoff = _ensure_coroutines(on_backoff) + on_giveup = _ensure_coroutines(on_giveup) giveup = _ensure_coroutine(giveup) # Easy to implement, please report if you need this. @@ -114,8 +114,7 @@ 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) @@ -125,25 +124,28 @@ wait = _init_wait_gen(wait_gen, wait_gen_kwargs) while True: tries += 1 - elapsed = _total_seconds(datetime.datetime.now() - start) + elapsed = timedelta.total_seconds(datetime.datetime.now() - start) 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(on_giveup, *details) raise - seconds = _next_wait(wait, jitter, elapsed, max_time_) + try: + seconds = _next_wait(wait, jitter, elapsed, max_time_) + except StopIteration: + await _call_handlers(on_giveup, *details) + raise e - yield from _call_handlers( - backoff_hdlrs, *details, wait=seconds) + await _call_handlers(on_backoff, *details, wait=seconds) # Note: there is no convenient way to pass explicit event # loop to decorator, so here we assume that either default @@ -154,9 +156,9 @@ # 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(on_success, *details) return ret return retry diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/backoff/_common.py new/backoff-1.8.0/backoff/_common.py --- old/backoff-1.5.0/backoff/_common.py 2018-04-11 16:55:35.000000000 +0200 +++ new/backoff-1.8.0/backoff/_common.py 2018-12-15 18:22:25.000000000 +0100 @@ -1,22 +1,16 @@ # coding:utf-8 +import functools import logging import sys import traceback +import warnings # Use module-specific logger with a default null handler. -logger = logging.getLogger('backoff') - -if sys.version_info < (2, 7, 0): # pragma: no cover - class NullHandler(logging.Handler): - def emit(self, record): - pass - logger.addHandler(NullHandler()) -else: - logger.addHandler(logging.NullHandler()) # pragma: no cover - -logger.setLevel(logging.ERROR) +_logger = logging.getLogger('backoff') +_logger.addHandler(logging.NullHandler()) # pragma: no cover +_logger.setLevel(logging.INFO) # Evaluate arg that can be either a fixed value or a callable. @@ -26,8 +20,7 @@ def _init_wait_gen(wait_gen, wait_gen_kwargs): # there are no dictionary comprehensions in python 2.6 - kwargs = dict((k, _maybe_call(v)) - for k, v in wait_gen_kwargs.items()) + kwargs = {k: _maybe_call(v) for k, v in wait_gen_kwargs.items()} return wait_gen(**kwargs) @@ -39,8 +32,14 @@ else: seconds = value except TypeError: - # support deprecated nullary jitter function signature - # which returns a delta rather than a jittered value + warnings.warn( + "Nullary jitter function signature is deprecated. Use " + "unary signature accepting a wait value in seconds and " + "returning a jittered version of it.", + DeprecationWarning, + stacklevel=2, + ) + seconds = value + jitter() # don't sleep longer than remaining alloted max_time @@ -50,52 +49,54 @@ return seconds -# Create default handler list from keyword argument -def _handlers(hdlr, default=None): - defaults = [default] if default is not None else [] - - if hdlr is None: - return defaults - - if hasattr(hdlr, '__iter__'): - return defaults + list(hdlr) +# Configure handler list with user specified handler and optionally +# with a default handler bound to the specified logger. +def _config_handlers(user_handlers, default_handler=None, logger=None): + handlers = [] + if logger is not None: + # bind the specified logger to the default log handler + log_handler = functools.partial(default_handler, logger=logger) + handlers.append(log_handler) + + if user_handlers is None: + return handlers + + # user specified handlers can either be an iterable of handlers + # or a single handler. either way append them to the list. + if hasattr(user_handlers, '__iter__'): + # add all handlers in the iterable + handlers += list(user_handlers) + else: + # append a single handler + handlers.append(user_handlers) - return defaults + [hdlr] + return handlers # Default backoff handler -def _log_backoff(details): +def _log_backoff(details, logger): fmt = "Backing off {0}(...) for {1:.1f}s" msg = fmt.format(details['target'].__name__, details['wait']) exc_typ, exc, _ = sys.exc_info() if exc is not None: exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1] - msg = "{0} ({1})".format(msg, exc_fmt.rstrip("\n")) - logger.error(msg) + msg = "{} ({})".format(msg, exc_fmt.rstrip("\n")) else: - msg = "{0} ({1})".format(msg, details['value']) - logger.info(msg) + msg = "{} ({})".format(msg, details['value']) + logger.info(msg) # Default giveup handler -def _log_giveup(details): +def _log_giveup(details, logger): fmt = "Giving up {0}(...) after {1} tries" msg = fmt.format(details['target'].__name__, details['tries']) exc_typ, exc, _ = sys.exc_info() if exc is not None: exc_fmt = traceback.format_exception_only(exc_typ, exc)[-1] - msg = "{0} ({1})".format(msg, exc_fmt.rstrip("\n")) + msg = "{} ({})".format(msg, exc_fmt.rstrip("\n")) else: - msg = "{0} ({1})".format(msg, details['value']) + msg = "{} ({})".format(msg, details['value']) logger.error(msg) - - -# Python 2.6 datetime.timedelta does not have total_seconds() -# so we do our own implementation here. -def _total_seconds(timedelta): - return ( - (timedelta.microseconds + 0.0 + - (timedelta.seconds + timedelta.days * 24 * 3600) * 10**6) / 10**6) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/backoff/_decorator.py new/backoff-1.8.0/backoff/_decorator.py --- old/backoff-1.5.0/backoff/_decorator.py 2018-04-11 16:55:35.000000000 +0200 +++ new/backoff-1.8.0/backoff/_decorator.py 2018-12-21 06:08:23.000000000 +0100 @@ -1,13 +1,22 @@ # coding:utf-8 from __future__ import unicode_literals +import logging import operator import sys +from backoff._common import (_config_handlers, _log_backoff, _log_giveup) from backoff._jitter import full_jitter from backoff import _sync +# python 2.7 -> 3.x compatibility for str and unicode +try: + basestring +except NameError: # pragma: python=3.5 + basestring = str + + def on_predicate(wait_gen, predicate=operator.not_, max_tries=None, @@ -16,6 +25,7 @@ on_success=None, on_backoff=None, on_giveup=None, + logger='backoff', **wait_gen_kwargs): """Returns decorator for backoff and retry triggered by predicate. @@ -51,43 +61,45 @@ signature to be called in the event that max_tries is exceeded. The parameter is a dict containing details about the invocation. + logger: Name of logger or Logger object to log to. Defaults to + 'backoff'. **wait_gen_kwargs: Any additional keyword args specified will be passed to wait_gen when it is initialized. Any callable args will first be evaluated and their return values passed. This is useful for runtime configuration. """ def decorate(target): + # change names because python 2.x doesn't have nonlocal + logger_ = logger + if isinstance(logger_, basestring): + logger_ = logging.getLogger(logger_) + on_success_ = _config_handlers(on_success) + on_backoff_ = _config_handlers(on_backoff, _log_backoff, logger_) + on_giveup_ = _config_handlers(on_giveup, _log_giveup, logger_) + retry = None - if sys.version_info[:2] >= (3, 4): # pragma: python=3.4 + if sys.version_info >= (3, 5): # pragma: python=3.5 import asyncio if asyncio.iscoroutinefunction(target): import backoff._async retry = backoff._async.retry_predicate - else: + elif _is_event_loop() and _is_current_task(): # Verify that sync version is not being run from coroutine # (that would lead to event loop hiccups). - try: - asyncio.get_event_loop() - except RuntimeError: - # Event loop not set for this thread. - pass - else: - if asyncio.Task.current_task() is not None: - raise TypeError( - "backoff.on_predicate applied to a regular " - "function inside coroutine, this will lead " - "to event loop hiccups. " - "Use backoff.on_predicate on coroutines in " - "asynchronous code.") + raise TypeError( + "backoff.on_predicate applied to a regular function " + "inside coroutine, this will lead to event loop " + "hiccups. Use backoff.on_predicate on coroutines in " + "asynchronous code.") if retry is None: retry = _sync.retry_predicate return retry(target, wait_gen, predicate, max_tries, max_time, jitter, - on_success, on_backoff, on_giveup, + on_success_, on_backoff_, on_giveup_, wait_gen_kwargs) # Return a function which decorates a target with a retry loop. @@ -103,6 +115,7 @@ on_success=None, on_backoff=None, on_giveup=None, + logger='backoff', **wait_gen_kwargs): """Returns decorator for backoff and retry triggered by exception. @@ -139,43 +152,66 @@ signature to be called in the event that max_tries is exceeded. The parameter is a dict containing details about the invocation. + logger: Name or Logger object to log to. Defaults to 'backoff'. **wait_gen_kwargs: Any additional keyword args specified will be passed to wait_gen when it is initialized. Any callable args will first be evaluated and their return values passed. This is useful for runtime configuration. """ def decorate(target): + # change names because python 2.x doesn't have nonlocal + logger_ = logger + if isinstance(logger_, basestring): + logger_ = logging.getLogger(logger_) + on_success_ = _config_handlers(on_success) + on_backoff_ = _config_handlers(on_backoff, _log_backoff, logger_) + on_giveup_ = _config_handlers(on_giveup, _log_giveup, logger_) + retry = None - if sys.version_info[:2] >= (3, 4): # pragma: python=3.4 + if sys.version_info[:2] >= (3, 5): # pragma: python=3.5 import asyncio if asyncio.iscoroutinefunction(target): import backoff._async retry = backoff._async.retry_exception - else: + elif _is_event_loop() and _is_current_task(): # Verify that sync version is not being run from coroutine # (that would lead to event loop hiccups). - try: - asyncio.get_event_loop() - except RuntimeError: - # Event loop not set for this thread. - pass - else: - if asyncio.Task.current_task() is not None: - raise TypeError( - "backoff.on_exception applied to a regular " - "function inside coroutine, this will lead " - "to event loop hiccups. " - "Use backoff.on_exception on coroutines in " - "asynchronous code.") + raise TypeError( + "backoff.on_exception applied to a regular function " + "inside coroutine, this will lead to event loop " + "hiccups. Use backoff.on_exception on coroutines in " + "asynchronous code.") if retry is None: retry = _sync.retry_exception return retry(target, wait_gen, exception, max_tries, max_time, jitter, giveup, - on_success, on_backoff, on_giveup, + on_success_, on_backoff_, on_giveup_, wait_gen_kwargs) # Return a function which decorates a target with a retry loop. return decorate + + +def _is_event_loop(): # pragma: no cover + import asyncio + + try: + if sys.version_info >= (3, 7): + asyncio.get_running_loop() + + asyncio.get_event_loop() + except RuntimeError: + return False + else: + return True + + +def _is_current_task(): # pragma: no cover + import asyncio + if sys.version_info >= (3, 7): + return asyncio.current_task() is not None + + return asyncio.Task.current_task() is not None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/backoff/_sync.py new/backoff-1.8.0/backoff/_sync.py --- old/backoff-1.5.0/backoff/_sync.py 2018-04-11 16:55:35.000000000 +0200 +++ new/backoff-1.8.0/backoff/_sync.py 2018-12-15 18:22:25.000000000 +0100 @@ -2,10 +2,9 @@ import datetime import functools import time +from datetime import timedelta -from backoff._common import (_handlers, _init_wait_gen, _log_backoff, - _log_giveup, _maybe_call, _next_wait, - _total_seconds) +from backoff._common import (_init_wait_gen, _maybe_call, _next_wait) def _call_handlers(hdlrs, target, args, kwargs, tries, elapsed, **extra): @@ -26,10 +25,6 @@ on_success, on_backoff, on_giveup, wait_gen_kwargs): - success_hdlrs = _handlers(on_success) - backoff_hdlrs = _handlers(on_backoff, _log_backoff) - giveup_hdlrs = _handlers(on_giveup, _log_giveup) - @functools.wraps(target) def retry(*args, **kwargs): @@ -42,7 +37,7 @@ wait = _init_wait_gen(wait_gen, wait_gen_kwargs) while True: tries += 1 - elapsed = _total_seconds(datetime.datetime.now() - start) + elapsed = timedelta.total_seconds(datetime.datetime.now() - start) details = (target, args, kwargs, tries, elapsed) ret = target(*args, **kwargs) @@ -52,18 +47,22 @@ elapsed >= max_time_) if max_tries_exceeded or max_time_exceeded: - _call_handlers(giveup_hdlrs, *details, value=ret) + _call_handlers(on_giveup, *details, value=ret) break - seconds = _next_wait(wait, jitter, elapsed, max_time_) + try: + seconds = _next_wait(wait, jitter, elapsed, max_time_) + except StopIteration: + _call_handlers(on_giveup, *details) + break - _call_handlers(backoff_hdlrs, *details, + _call_handlers(on_backoff, *details, value=ret, wait=seconds) time.sleep(seconds) continue else: - _call_handlers(success_hdlrs, *details, value=ret) + _call_handlers(on_success, *details, value=ret) break return ret @@ -76,10 +75,6 @@ on_success, on_backoff, on_giveup, wait_gen_kwargs): - success_hdlrs = _handlers(on_success) - backoff_hdlrs = _handlers(on_backoff, _log_backoff) - giveup_hdlrs = _handlers(on_giveup, _log_giveup) - @functools.wraps(target) def retry(*args, **kwargs): @@ -92,7 +87,7 @@ wait = _init_wait_gen(wait_gen, wait_gen_kwargs) while True: tries += 1 - elapsed = _total_seconds(datetime.datetime.now() - start) + elapsed = timedelta.total_seconds(datetime.datetime.now() - start) details = (target, args, kwargs, tries, elapsed) try: @@ -103,16 +98,20 @@ elapsed >= max_time_) if giveup(e) or max_tries_exceeded or max_time_exceeded: - _call_handlers(giveup_hdlrs, *details) + _call_handlers(on_giveup, *details) raise - seconds = _next_wait(wait, jitter, elapsed, max_time_) + try: + seconds = _next_wait(wait, jitter, elapsed, max_time_) + except StopIteration: + _call_handlers(on_giveup, *details) + raise e - _call_handlers(backoff_hdlrs, *details, wait=seconds) + _call_handlers(on_backoff, *details, wait=seconds) time.sleep(seconds) else: - _call_handlers(success_hdlrs, *details) + _call_handlers(on_success, *details) return ret return retry diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/backoff/_wait_gen.py new/backoff-1.8.0/backoff/_wait_gen.py --- old/backoff-1.5.0/backoff/_wait_gen.py 2018-03-30 13:12:58.000000000 +0200 +++ new/backoff-1.8.0/backoff/_wait_gen.py 2018-11-29 02:00:40.000000000 +0100 @@ -1,5 +1,7 @@ # coding:utf-8 +import itertools + def expo(base=2, factor=1, max_value=None): """Generator for exponential decay. @@ -43,7 +45,12 @@ """Generator for constant intervals. Args: - interval: The constant value in seconds to yield. + interval: A constant value to yield or an iterable of such values. """ - while True: - yield interval + try: + itr = iter(interval) + except TypeError: + itr = itertools.repeat(interval) + + for val in itr: + yield val diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/pyproject.toml new/backoff-1.8.0/pyproject.toml --- old/backoff-1.5.0/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/backoff-1.8.0/pyproject.toml 2018-12-21 06:19:07.000000000 +0100 @@ -0,0 +1,44 @@ +[tool.poetry] +name = "backoff" +version = "1.8.0" +description = "Function decoration for backoff and retry" +authors = ["Bob Green <[email protected]>"] +readme = "README.rst" +repository = "https://github.com/litl/backoff" +license = "MIT" +keywords = ["retry", "backoff", "decorators"] +classifiers = ['Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Programming Language :: Python', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Utilities'] +packages = [ + { include = "backoff" }, + { include = "README.rst" }, + { include = "LICENSE" }, +] + +[tool.poetry.dependencies] +python = "^2.7 || ^3.4" + +[tool.poetry.dev-dependencies] +flake8 = "^3.6" +pytest = "^4.0" +pytest-cov = "^2.6" +pytest-asyncio = {version = "^0.9.0",python = "^3.5"} + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/backoff-1.5.0/setup.py new/backoff-1.8.0/setup.py --- old/backoff-1.5.0/setup.py 2018-03-10 18:06:06.000000000 +0100 +++ new/backoff-1.8.0/setup.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,45 +1,27 @@ -# coding:utf-8 +# -*- coding: utf-8 -*- +from distutils.core import setup -import backoff +packages = \ +['backoff'] -from distutils import core +package_data = \ +{'': ['*']} +modules = \ +['README', 'LICENSE'] +setup_kwargs = { + 'name': 'backoff', + 'version': '1.8.0', + 'description': 'Function decoration for backoff and retry', + 'long_description': 'backoff\n=======\n\n.. image:: https://travis-ci.org/litl/backoff.svg?branch=master\n :target: https://travis-ci.org/litl/backoff?branch=master\n.. image:: https://coveralls.io/repos/litl/backoff/badge.svg?branch=master\n :target: https://coveralls.io/r/litl/backoff?branch=master\n.. image:: https://img.shields.io/pypi/v/backoff.svg\n :target: https://pypi.python.org/pypi/backoff\n\n**Function decoration for backoff and retry**\n\nThis module provides function decorators which can be used to wrap a\nfunction such that it will be retried until some condition is met. It\nis meant to be of use when accessing unreliable resources with the\npotential for intermittent failures i.e. network resources and external\nAPIs. Somewhat more generally, it may also be of use for dynamically\npolling resources for externally generated content.\n\nDecorators support both regular functions for synchronous code and\n`asyncio <https://docs.python.org/3/library/asyncio.html>`_\'s coroutines\nfor asynchronous code.\n\nExamples\n========\n\nSince Kenneth Reitz\'s `requests <http://python-requests.org>`_ module\nhas become a defacto standard for synchronous HTTP clients in Python,\nnetworking examples below are written using it, but it is in no way required\nby the backoff module.\n\[email protected]_exception\n---------------------\n\nThe ``on_exception`` decorator is used to retry when a specified exception\nis raised. Here\'s an example using exponential backoff when any\n``requests`` exception is raised:\n\n.. code-block:: python\n\n @backoff.on_exception(backoff.expo,\n requests.exceptions.RequestException)\n def get_url(url):\n return requests.get(url)\n\nThe decorator will also accept a tuple of exceptions for cases where\nthe same backoff behavior is desired for more than one exception type:\n\n.. code-block:: python\n\n @backoff.on_exception(backoff.expo,\n (requests.exceptions.Timeout,\n requests.exceptions.ConnectionError))\n def get_url(url):\n return requests.get(url)\n\n**Give Up Conditions**\n\nOptional keyword arguments can specify conditions under which to give\nup.\n\nThe keyword argument ``max_time`` specifies the maximum amount\nof total time in seconds that can elapse before giving up.\n\n.. code-block:: python\n\n @backoff.on_exception(backoff.expo,\n requests.exceptions.RequestException,\n max_time=60)\n def get_url(url):\n return requests.get(url)\n\n\nKeyword argument ``max_tries`` specifies the maximum number of calls\nto make to the target function before giving up.\n\n.. code-block:: python\n\n @backoff.on_exception(backoff.expo,\n requests.exceptions.RequestException,\n max_tries=8,\n jitter=None)\n def get_url(url):\n return requests.get(url)\n\n\nIn some cases the raised exception instance itself may need to be\ninspected in order to determine if it is a retryable condition. The\n``giveup`` keyword arg can be used to specify a function which accepts\nthe exception and returns a truthy value if the exception should not\nbe retried:\n\n.. code-block:: python\n\n def fatal_code(e):\n return 400 <= e.response.status_code < 500\n\n @backoff.on_exception(backoff.expo,\n requests.exceptions.RequestException,\n max_time=300,\n giveup=fatal_code)\n def get_url(url):\n return requests.get(url)\n\nWhen a give up event occurs, the exception in question is reraised\nand so code calling an `on_exception`-decorated function may still\nneed to do exception handling.\n\[email protected]_predicate\n---------------------\n\nThe ``on_predicate`` decorator is used to retry when a particular\ncondition is true of the return value of the target function. This may\nbe useful when polling a resource for externally generated content.\n\nHere\'s an example which uses a fibonacci sequence backoff when the\nreturn value of the target function is the empty list:\n\n.. code-block:: python\n\n @backoff.on_predicate(backoff.fibo, lambda x: x == [], max_value=13)\n def poll_for_messages(queue):\n return queue.get()\n\nExtra keyword arguments are passed when initializing the\nwait generator, so the ``max_value`` param above is passed as a keyword\narg when initializing the fibo generator.\n\nWhen not specified, the predicate param defaults to the falsey test,\nso the above can more concisely be written:\n\n.. code-block:: python\n\n @backoff.on_predicate(backoff.fibo, max_value=13)\n def poll_for_message(queue)\n return queue.get()\n\nMore simply, a function which continues polling every second until it\ngets a non-falsey result could be defined like like this:\n\n.. code-block:: python\n\n @backoff.on_predicate(backoff.constant, interval=1)\n def poll_for_message(queue)\n return queue.get()\n\nJitter\n------\n\nA jitter algorithm can be supplied with the ``jitter`` keyword arg to\neither of the backoff decorators. This argument should be a function\naccepting the original unadulterated backoff value and returning it\'s\njittered counterpart.\n\nAs of version 1.2, the default jitter function ``backoff.full_jitter``\nimplements the \'Full Jitter\' algorithm as defined in the AWS\nArchitecture Blog\'s `Exponential Backoff And Jitter\n<https://www.awsarchitectureblog.com/2015/03/backoff.html>`_ post.\nNote that with this algorithm, the time yielded by the wait generator\nis actually the *maximum* amount of time to wait.\n\nPrevious versions of backoff defaulted to adding some random number of\nmilliseconds (up to 1s) to the raw sleep value. If desired, this\nbehavior is now available as ``backoff.random_jitter``.\n\nUsing multiple decorators\n-------------------------\n\nThe backoff decorators may also be combined to specify different\nbackoff behavior for different cases:\n\n.. code-block:: python\n\n @backoff.on_predicate(backoff.fibo, max_value=13)\n @backoff.on_exception(backoff.expo,\n requests.exceptions.HTTPError,\n max_time=60)\n @backoff.on_exception(backoff.expo,\n requests.exceptions.TimeoutError,\n max_time=300)\n def poll_for_message(queue):\n return queue.get()\n\nRuntime Configuration\n---------------------\n\nThe decorator functions ``on_exception`` and ``on_predicate`` are\ngenerally evaluated at import time. This is fine when the keyword args\nare passed as constant values, but suppose we want to consult a\ndictionary with configuration options that only become available at\nruntime. The relevant values are not available at import time. Instead,\ndecorator functions can be passed callables which are evaluated at\nruntime to obtain the value:\n\n.. code-block:: python\n\n def lookup_max_time():\n # pretend we have a global reference to \'app\' here\n # and that it has a dictionary-like \'config\' property\n return app.config["BACKOFF_MAX_TIME"]\n\n @backoff.on_exception(backoff.expo,\n ValueError,\n max_time=lookup_max_time)\n\nEvent handlers\n--------------\n\nBoth backoff decorators optionally accept event handler functions\nusing the keyword arguments ``on_success``, ``on_backoff``, and ``on_giveup``.\nThis may be useful in reporting statistics or performing other custom\nlogging.\n\nHandlers must be callables with a unary signature accepting a dict\nargument. This dict contains the details of the invocation. Valid keys\ninclude:\n\n* *target*: reference to the function or method being invoked\n* *args*: positional arguments to func\n* *kwargs*: keyword arguments to func\n* *tries*: number of invocation tries so far\n* *elapsed*: elapsed time in seconds so far\n* *wait*: seconds to wait (``on_backoff`` handler only)\n* *value*: value triggering backoff (``on_predicate`` decorator only)\n\nA handler which prints the details of the backoff event could be\nimplemented like so:\n\n.. code-block:: python\n\n def backoff_hdlr(details):\n print ("Backing off {wait:0.1f} seconds afters {tries} tries "\n "calling function {target} with args {args} and kwargs "\n "{kwargs}".format(**details))\n\n @backoff.on_exception(backoff.expo,\n requests.exceptions.RequestException,\n on_backoff=backoff_hdlr)\n def get_url(url):\n return requests.get(url)\n\n**Multiple handlers per event type**\n\nIn all cases, iterables of handler functions are also accepted, which\nare called in turn. For example, you might provide a simple list of\nhandler functions as the value of the ``on_backoff`` keyword arg:\n\n.. code-block:: python\n\n @backoff.on_exception(backoff.expo,\n requests.exceptions.RequestException,\n on_backoff=[backoff_hdlr1, backoff_hdlr2])\n def get_url(url):\n return requests.get(url)\n\n**Getting exception info**\n\nIn the case of the ``on_exception`` decorator, all ``on_backoff`` and\n``on_giveup`` handlers are called from within the except block for the\nexception being handled. Therefore exception info is available to the\nhandler functions via the python standard library, specifically\n``sys.exc_info()`` or the ``traceback`` module.\n\nAsynchronous code\n-----------------\n\nBackoff supports asynchronous execution in Python 3.5 and above.\n\nTo use backoff in asynchronous code based on\n`asyncio <https://docs.python.org/3/library/asyncio.html>`_\nyou simply need to apply ``backoff.on_exception`` or ``backoff.on_predicate``\nto coroutines.\nYou can also use coroutines for the ``on_success``, ``on_backoff``, and\n``on_giveup`` event handlers, with the interface otherwise being identical.\n\nThe following examples use `aiohttp <https://aiohttp.readthedocs.io/>`_\nasynchronous HTTP client/server library.\n\n.. code-block:: python\n\n @backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60)\n async def get_url(url):\n async with aiohttp.ClientSession() as session:\n async with session.get(url) as response:\n return await response.text()\n\nLogging configuration\n---------------------\n\nBy default, backoff and retry attempts are logged to the \'backoff\'\nlogger. By default, this logger is configured with a NullHandler, so\nthere will be nothing output unless you configure a handler.\nProgrammatically, this might be accomplished with something as simple\nas:\n\n.. code-block:: python\n\n logging.getLogger(\'backoff\').addHandler(logging.StreamHandler())\n\nThe default logging level is INFO, which corresponds to logging\nanytime a retry event occurs. If you would instead like to log\nonly when a giveup event occurs, set the logger level to ERROR.\n\n.. code-block:: python\n\n logging.getLogger(\'backoff\').setLevel(logging.ERROR)\n\nIt is also possible to specify an alternate logger with the ``logger``\nkeyword argument. If a string value is specified the logger will be\nlooked up by name.\n\n.. code-block:: python\n\n @backoff.on_exception(backoff.expo,\n requests.exception.RequestException,\n\t\t\t logger=\'my_logger\')\n # ...\n\nIt is also supported to specify a Logger (or LoggerAdapter) object\ndirectly.\n\n.. code-block:: python\n\n my_logger = logging.getLogger(\'my_logger\')\n my_handler = logging.StreamHandler()\n my_logger.add_handler(my_handler)\n my_logger.setLevel(logging.ERROR)\n\n @backoff.on_exception(backoff.expo,\n requests.exception.RequestException,\n\t\t\t logger=my_logger)\n # ...\n\nDefault logging can be disabled all together by specifying\n``logger=None``. In this case, if desired alternative logging behavior\ncould be defined by using custom event handlers.\n', + 'author': 'Bob Green', + 'author_email': '[email protected]', + 'url': 'https://github.com/litl/backoff', + 'packages': packages, + 'package_data': package_data, + 'py_modules': modules, + 'python_requires': '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', +} -classifiers = ['Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Programming Language :: Python', - 'License :: OSI Approved :: MIT License', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: Implementation', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Utilities'] -version = backoff.__version__ -url = "https://github.com/litl/backoff" -tarball_url = "%s/tarball/v%s" % (url, version) - - -def readme(): - with open("README.rst", "r") as infile: - return infile.read() - - -core.setup(name='backoff', - version=version, - description="Function decoration for backoff and retry", - long_description=readme(), - packages=['backoff'], - author="Bob Green", - author_email="[email protected]", - keywords = "backoff function decorator", - url=url, - download_url=tarball_url, - license="MIT", - classifiers=classifiers) +setup(**setup_kwargs)
