tenacity 9.0.0__tar.gz → 9.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tenacity-9.0.0 → tenacity-9.1.3}/.github/workflows/ci.yaml +10 -10
- tenacity-9.1.3/.github/workflows/release.yml +34 -0
- tenacity-9.1.3/.mergify.yml +21 -0
- {tenacity-9.0.0/tenacity.egg-info → tenacity-9.1.3}/PKG-INFO +12 -6
- {tenacity-9.0.0 → tenacity-9.1.3}/README.rst +0 -14
- {tenacity-9.0.0 → tenacity-9.1.3}/doc/source/index.rst +0 -14
- {tenacity-9.0.0 → tenacity-9.1.3}/pyproject.toml +1 -1
- tenacity-9.1.3/releasenotes/notes/add-re-pattern-to-match-types-6a4c1d9e64e2a5e1.yaml +4 -0
- tenacity-9.1.3/releasenotes/notes/async-sleep-retrying-32de5866f5d041.yaml +7 -0
- tenacity-9.1.3/releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml +5 -0
- tenacity-9.1.3/releasenotes/notes/logging-protocol-a4cf0f786f21e4ee.yaml +5 -0
- tenacity-9.1.3/releasenotes/notes/support-py3.14-14928188cab53b99.yaml +3 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/setup.cfg +3 -3
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/__init__.py +39 -25
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/_utils.py +12 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/after.py +2 -4
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/asyncio/__init__.py +5 -1
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/before.py +1 -3
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/before_sleep.py +5 -6
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/retry.py +2 -2
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/tornadoweb.py +1 -1
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/wait.py +42 -3
- {tenacity-9.0.0 → tenacity-9.1.3/tenacity.egg-info}/PKG-INFO +12 -6
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity.egg-info/SOURCES.txt +6 -1
- {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_after.py +1 -1
- {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_asyncio.py +27 -3
- {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_issue_478.py +1 -2
- {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_tenacity.py +19 -10
- {tenacity-9.0.0 → tenacity-9.1.3}/tox.ini +5 -4
- tenacity-9.0.0/.github/workflows/deploy.yaml +0 -34
- tenacity-9.0.0/.mergify.yml +0 -41
- {tenacity-9.0.0 → tenacity-9.1.3}/.editorconfig +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/.github/dependabot.yml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/.gitignore +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/.readthedocs.yml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/LICENSE +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/doc/source/api.rst +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/doc/source/changelog.rst +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/doc/source/conf.py +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-async-actions-b249c527d99723bb.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-reno-d1ab5710f272650a.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-retry_except_exception_type-31b31da1924d55f4.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-test-extra-55e869261b03e56d.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add_omitted_modules_to_import_all-2ab282f20a2c22f7.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add_retry_if_exception_cause_type-d16b918ace4ae0ad.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/added_a_link_to_documentation-eefaf8f074b539f8.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/after_log-50f4d73b24ce9203.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/annotate_code-197b93130df14042.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/before_sleep_log-improvements-d8149274dfb37d7c.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/dependabot-for-github-actions-4d2464f3c0928463.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/make-logger-more-compatible-5da1ddf1bab77047.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/no-async-iter-6132a42e52348a75.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/pr320-py3-only-wheel-tag.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/py36_plus-c425fb3aa17c6682.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/remove-py36-876c0416cf279d15.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/some-slug-for-preserve-defaults-86682846dfa18005.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/reno.yaml +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/setup.py +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/asyncio/retry.py +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/nap.py +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/py.typed +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/stop.py +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity.egg-info/dependency_links.txt +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity.egg-info/requires.txt +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tenacity.egg-info/top_level.txt +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tests/__init__.py +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_tornado.py +0 -0
- {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_utils.py +0 -0
|
@@ -14,32 +14,32 @@ concurrency:
|
|
|
14
14
|
jobs:
|
|
15
15
|
test:
|
|
16
16
|
timeout-minutes: 20
|
|
17
|
-
runs-on: ubuntu-
|
|
17
|
+
runs-on: ubuntu-24.04
|
|
18
18
|
strategy:
|
|
19
19
|
matrix:
|
|
20
20
|
include:
|
|
21
|
-
- python: "3.8"
|
|
22
|
-
tox: py38
|
|
23
|
-
- python: "3.9"
|
|
24
|
-
tox: py39
|
|
25
21
|
- python: "3.10"
|
|
26
22
|
tox: py310
|
|
27
23
|
- python: "3.11"
|
|
28
24
|
tox: py311
|
|
29
25
|
- python: "3.12"
|
|
30
|
-
tox: py312
|
|
31
|
-
- python: "3.
|
|
26
|
+
tox: py312
|
|
27
|
+
- python: "3.13"
|
|
28
|
+
tox: py313
|
|
29
|
+
- python: "3.14"
|
|
30
|
+
tox: py314,py314-trio
|
|
31
|
+
- python: "3.14"
|
|
32
32
|
tox: pep8
|
|
33
|
-
- python: "3.
|
|
33
|
+
- python: "3.14"
|
|
34
34
|
tox: mypy
|
|
35
35
|
steps:
|
|
36
36
|
- name: Checkout 🛎️
|
|
37
|
-
uses: actions/checkout@
|
|
37
|
+
uses: actions/checkout@v6.0.2
|
|
38
38
|
with:
|
|
39
39
|
fetch-depth: 0
|
|
40
40
|
|
|
41
41
|
- name: Setup Python 🔧
|
|
42
|
-
uses: actions/setup-python@
|
|
42
|
+
uses: actions/setup-python@v6.2.0
|
|
43
43
|
with:
|
|
44
44
|
python-version: ${{ matrix.python }}
|
|
45
45
|
allow-prereleases: true
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: upload release to PyPI
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types:
|
|
5
|
+
- published
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
pypi-publish:
|
|
9
|
+
name: upload release to PyPI
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: release
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
contents: write
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6.0.2
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
fetch-tags: true
|
|
20
|
+
|
|
21
|
+
- uses: actions/setup-python@v6.2.0
|
|
22
|
+
with:
|
|
23
|
+
python-version: 3.14
|
|
24
|
+
|
|
25
|
+
- name: Install build
|
|
26
|
+
run: |
|
|
27
|
+
pip install setuptools-scm wheel
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: |
|
|
31
|
+
python setup.py sdist bdist_wheel
|
|
32
|
+
|
|
33
|
+
- name: Publish package distributions to PyPI
|
|
34
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
queue_rules:
|
|
2
|
+
- name: default
|
|
3
|
+
merge_method: squash
|
|
4
|
+
autoqueue: true
|
|
5
|
+
queue_conditions:
|
|
6
|
+
- or:
|
|
7
|
+
- author = jd
|
|
8
|
+
- "#approved-reviews-by >= 1"
|
|
9
|
+
- author = dependabot[bot]
|
|
10
|
+
- "check-success=test (3.10, py310)"
|
|
11
|
+
- "check-success=test (3.11, py311)"
|
|
12
|
+
- "check-success=test (3.12, py312)"
|
|
13
|
+
- "check-success=test (3.13, py313)"
|
|
14
|
+
- "check-success=test (3.14, py314,py314-trio)"
|
|
15
|
+
- "check-success=test (3.14, pep8)"
|
|
16
|
+
|
|
17
|
+
pull_request_rules:
|
|
18
|
+
- name: dismiss reviews
|
|
19
|
+
conditions: []
|
|
20
|
+
actions:
|
|
21
|
+
dismiss_reviews: {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tenacity
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.1.3
|
|
4
4
|
Summary: Retry code until it succeeds
|
|
5
5
|
Home-page: https://github.com/jd/tenacity
|
|
6
6
|
Author: Julien Danjou
|
|
@@ -11,15 +11,21 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
11
11
|
Classifier: Programming Language :: Python
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
19
|
Classifier: Topic :: Utilities
|
|
20
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
License-File: LICENSE
|
|
21
22
|
Provides-Extra: doc
|
|
23
|
+
Requires-Dist: reno; extra == "doc"
|
|
24
|
+
Requires-Dist: sphinx; extra == "doc"
|
|
22
25
|
Provides-Extra: test
|
|
23
|
-
|
|
26
|
+
Requires-Dist: pytest; extra == "test"
|
|
27
|
+
Requires-Dist: tornado>=4.5; extra == "test"
|
|
28
|
+
Requires-Dist: typeguard; extra == "test"
|
|
29
|
+
Dynamic: license-file
|
|
24
30
|
|
|
25
31
|
Tenacity is a general-purpose retrying library to simplify the task of adding retry behavior to just about anything.
|
|
@@ -637,17 +637,3 @@ Contribute
|
|
|
637
637
|
#. Make the docs better (or more detailed, or more easier to read, or ...)
|
|
638
638
|
|
|
639
639
|
.. _`the repository`: https://github.com/jd/tenacity
|
|
640
|
-
|
|
641
|
-
Changelogs
|
|
642
|
-
~~~~~~~~~~
|
|
643
|
-
|
|
644
|
-
`reno`_ is used for managing changelogs. Take a look at their usage docs.
|
|
645
|
-
|
|
646
|
-
The doc generation will automatically compile the changelogs. You just need to add them.
|
|
647
|
-
|
|
648
|
-
.. code-block:: sh
|
|
649
|
-
|
|
650
|
-
# Opens a template file in an editor
|
|
651
|
-
tox -e reno -- new some-slug-for-my-change --edit
|
|
652
|
-
|
|
653
|
-
.. _`reno`: https://docs.openstack.org/reno/latest/user/usage.html
|
|
@@ -637,17 +637,3 @@ Contribute
|
|
|
637
637
|
#. Make the docs better (or more detailed, or more easier to read, or ...)
|
|
638
638
|
|
|
639
639
|
.. _`the repository`: https://github.com/jd/tenacity
|
|
640
|
-
|
|
641
|
-
Changelogs
|
|
642
|
-
~~~~~~~~~~
|
|
643
|
-
|
|
644
|
-
`reno`_ is used for managing changelogs. Take a look at their usage docs.
|
|
645
|
-
|
|
646
|
-
The doc generation will automatically compile the changelogs. You just need to add them.
|
|
647
|
-
|
|
648
|
-
.. code-block:: sh
|
|
649
|
-
|
|
650
|
-
# Opens a template file in an editor
|
|
651
|
-
tox -e reno -- new some-slug-for-my-change --edit
|
|
652
|
-
|
|
653
|
-
.. _`reno`: https://docs.openstack.org/reno/latest/user/usage.html
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
fixes:
|
|
3
|
+
- |
|
|
4
|
+
Passing an async ``sleep`` callable (e.g. ``trio.sleep``) to ``@retry``
|
|
5
|
+
now correctly uses ``AsyncRetrying``, even when the decorated function is
|
|
6
|
+
synchronous. Previously, the async sleep would silently not be awaited,
|
|
7
|
+
resulting in no delay between retries.
|
|
@@ -13,16 +13,16 @@ classifier =
|
|
|
13
13
|
Programming Language :: Python
|
|
14
14
|
Programming Language :: Python :: 3
|
|
15
15
|
Programming Language :: Python :: 3 :: Only
|
|
16
|
-
Programming Language :: Python :: 3.8
|
|
17
|
-
Programming Language :: Python :: 3.9
|
|
18
16
|
Programming Language :: Python :: 3.10
|
|
19
17
|
Programming Language :: Python :: 3.11
|
|
20
18
|
Programming Language :: Python :: 3.12
|
|
19
|
+
Programming Language :: Python :: 3.13
|
|
20
|
+
Programming Language :: Python :: 3.14
|
|
21
21
|
Topic :: Utilities
|
|
22
22
|
|
|
23
23
|
[options]
|
|
24
24
|
install_requires =
|
|
25
|
-
python_requires = >=3.
|
|
25
|
+
python_requires = >=3.10
|
|
26
26
|
packages = find:
|
|
27
27
|
|
|
28
28
|
[options.packages.find]
|
|
@@ -59,6 +59,7 @@ from .stop import stop_when_event_set # noqa
|
|
|
59
59
|
# Import all built-in wait strategies for easier usage.
|
|
60
60
|
from .wait import wait_chain # noqa
|
|
61
61
|
from .wait import wait_combine # noqa
|
|
62
|
+
from .wait import wait_exception # noqa
|
|
62
63
|
from .wait import wait_exponential # noqa
|
|
63
64
|
from .wait import wait_fixed # noqa
|
|
64
65
|
from .wait import wait_incrementing # noqa
|
|
@@ -76,7 +77,7 @@ from .before import before_nothing # noqa
|
|
|
76
77
|
from .after import after_log # noqa
|
|
77
78
|
from .after import after_nothing # noqa
|
|
78
79
|
|
|
79
|
-
# Import all built-in
|
|
80
|
+
# Import all built-in before sleep strategies for easier usage.
|
|
80
81
|
from .before_sleep import before_sleep_log # noqa
|
|
81
82
|
from .before_sleep import before_sleep_nothing # noqa
|
|
82
83
|
|
|
@@ -88,6 +89,8 @@ except ImportError:
|
|
|
88
89
|
if t.TYPE_CHECKING:
|
|
89
90
|
import types
|
|
90
91
|
|
|
92
|
+
from typing_extensions import Self
|
|
93
|
+
|
|
91
94
|
from . import asyncio as tasyncio
|
|
92
95
|
from .retry import RetryBaseT
|
|
93
96
|
from .stop import StopBaseT
|
|
@@ -96,14 +99,11 @@ if t.TYPE_CHECKING:
|
|
|
96
99
|
|
|
97
100
|
WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
|
|
98
101
|
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any])
|
|
102
|
+
P = t.ParamSpec("P")
|
|
103
|
+
R = t.TypeVar("R")
|
|
99
104
|
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
if sys.version_info >= (3, 10):
|
|
103
|
-
dataclass_kwargs.update({"slots": True})
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
@dataclasses.dataclass(**dataclass_kwargs)
|
|
106
|
+
@dataclasses.dataclass(slots=True)
|
|
107
107
|
class IterState:
|
|
108
108
|
actions: t.List[t.Callable[["RetryCallState"], t.Any]] = dataclasses.field(
|
|
109
109
|
default_factory=list
|
|
@@ -255,7 +255,7 @@ class BaseRetrying(ABC):
|
|
|
255
255
|
retry_error_callback: t.Union[
|
|
256
256
|
t.Optional[t.Callable[["RetryCallState"], t.Any]], object
|
|
257
257
|
] = _unset,
|
|
258
|
-
) -> "
|
|
258
|
+
) -> "Self":
|
|
259
259
|
"""Copy this object with some parameters changed if needed."""
|
|
260
260
|
return self.__class__(
|
|
261
261
|
sleep=_first_set(sleep, self.sleep),
|
|
@@ -305,19 +305,15 @@ class BaseRetrying(ABC):
|
|
|
305
305
|
future we may provide a way to aggregate the various
|
|
306
306
|
statistics from each thread).
|
|
307
307
|
"""
|
|
308
|
-
|
|
309
|
-
return self._local.statistics # type: ignore[no-any-return]
|
|
310
|
-
except AttributeError:
|
|
308
|
+
if not hasattr(self._local, "statistics"):
|
|
311
309
|
self._local.statistics = t.cast(t.Dict[str, t.Any], {})
|
|
312
|
-
|
|
310
|
+
return self._local.statistics # type: ignore[no-any-return]
|
|
313
311
|
|
|
314
312
|
@property
|
|
315
313
|
def iter_state(self) -> IterState:
|
|
316
|
-
|
|
317
|
-
return self._local.iter_state # type: ignore[no-any-return]
|
|
318
|
-
except AttributeError:
|
|
314
|
+
if not hasattr(self._local, "iter_state"):
|
|
319
315
|
self._local.iter_state = IterState()
|
|
320
|
-
|
|
316
|
+
return self._local.iter_state # type: ignore[no-any-return]
|
|
321
317
|
|
|
322
318
|
def wraps(self, f: WrappedFn) -> WrappedFn:
|
|
323
319
|
"""Wrap a function for retrying.
|
|
@@ -487,13 +483,7 @@ class Retrying(BaseRetrying):
|
|
|
487
483
|
return do # type: ignore[no-any-return]
|
|
488
484
|
|
|
489
485
|
|
|
490
|
-
|
|
491
|
-
FutureGenericT = futures.Future[t.Any]
|
|
492
|
-
else:
|
|
493
|
-
FutureGenericT = futures.Future
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
class Future(FutureGenericT):
|
|
486
|
+
class Future(futures.Future[t.Any]):
|
|
497
487
|
"""Encapsulates a (future or past) attempted call to a target function."""
|
|
498
488
|
|
|
499
489
|
def __init__(self, attempt_number: int) -> None:
|
|
@@ -601,7 +591,27 @@ def retry(func: WrappedFn) -> WrappedFn: ...
|
|
|
601
591
|
|
|
602
592
|
@t.overload
|
|
603
593
|
def retry(
|
|
604
|
-
|
|
594
|
+
*,
|
|
595
|
+
sleep: t.Callable[[t.Union[int, float]], t.Awaitable[None]],
|
|
596
|
+
stop: "StopBaseT" = ...,
|
|
597
|
+
wait: "WaitBaseT" = ...,
|
|
598
|
+
retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = ...,
|
|
599
|
+
before: t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]] = ...,
|
|
600
|
+
after: t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]] = ...,
|
|
601
|
+
before_sleep: t.Optional[
|
|
602
|
+
t.Callable[["RetryCallState"], t.Union[None, t.Awaitable[None]]]
|
|
603
|
+
] = ...,
|
|
604
|
+
reraise: bool = ...,
|
|
605
|
+
retry_error_cls: t.Type["RetryError"] = ...,
|
|
606
|
+
retry_error_callback: t.Optional[
|
|
607
|
+
t.Callable[["RetryCallState"], t.Union[t.Any, t.Awaitable[t.Any]]]
|
|
608
|
+
] = ...,
|
|
609
|
+
) -> t.Callable[[t.Callable[P, R | t.Awaitable[R]]], t.Callable[P, t.Awaitable[R]]]: ...
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
@t.overload
|
|
613
|
+
def retry(
|
|
614
|
+
sleep: t.Callable[[t.Union[int, float]], None] = sleep,
|
|
605
615
|
stop: "StopBaseT" = stop_never,
|
|
606
616
|
wait: "WaitBaseT" = wait_none(),
|
|
607
617
|
retry: "t.Union[RetryBaseT, tasyncio.retry.RetryBaseT]" = retry_if_exception_type(),
|
|
@@ -640,7 +650,10 @@ def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
|
|
|
640
650
|
f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)"
|
|
641
651
|
)
|
|
642
652
|
r: "BaseRetrying"
|
|
643
|
-
|
|
653
|
+
sleep = dkw.get("sleep")
|
|
654
|
+
if _utils.is_coroutine_callable(f) or (
|
|
655
|
+
sleep is not None and _utils.is_coroutine_callable(sleep)
|
|
656
|
+
):
|
|
644
657
|
r = AsyncRetrying(*dargs, **dkw)
|
|
645
658
|
elif (
|
|
646
659
|
tornado
|
|
@@ -688,6 +701,7 @@ __all__ = [
|
|
|
688
701
|
"stop_when_event_set",
|
|
689
702
|
"wait_chain",
|
|
690
703
|
"wait_combine",
|
|
704
|
+
"wait_exception",
|
|
691
705
|
"wait_exponential",
|
|
692
706
|
"wait_fixed",
|
|
693
707
|
"wait_incrementing",
|
|
@@ -25,6 +25,18 @@ from datetime import timedelta
|
|
|
25
25
|
MAX_WAIT = sys.maxsize / 2
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
class LoggerProtocol(typing.Protocol):
|
|
29
|
+
"""
|
|
30
|
+
Protocol used by utils expecting a logger (eg: before_log).
|
|
31
|
+
|
|
32
|
+
Compatible with logging, structlog, loguru, etc...
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def log(
|
|
36
|
+
self, level: int, msg: str, /, *args: typing.Any, **kwargs: typing.Any
|
|
37
|
+
) -> typing.Any: ...
|
|
38
|
+
|
|
39
|
+
|
|
28
40
|
def find_ordinal(pos_num: int) -> str:
|
|
29
41
|
# See: https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers
|
|
30
42
|
if pos_num == 0:
|
|
@@ -19,8 +19,6 @@ import typing
|
|
|
19
19
|
from tenacity import _utils
|
|
20
20
|
|
|
21
21
|
if typing.TYPE_CHECKING:
|
|
22
|
-
import logging
|
|
23
|
-
|
|
24
22
|
from tenacity import RetryCallState
|
|
25
23
|
|
|
26
24
|
|
|
@@ -29,9 +27,9 @@ def after_nothing(retry_state: "RetryCallState") -> None:
|
|
|
29
27
|
|
|
30
28
|
|
|
31
29
|
def after_log(
|
|
32
|
-
logger:
|
|
30
|
+
logger: _utils.LoggerProtocol,
|
|
33
31
|
log_level: int,
|
|
34
|
-
sec_format: str = "
|
|
32
|
+
sec_format: str = "%.3g",
|
|
35
33
|
) -> typing.Callable[["RetryCallState"], None]:
|
|
36
34
|
"""After call strategy that logs to some logger the finished attempt."""
|
|
37
35
|
|
|
@@ -107,11 +107,15 @@ class AsyncRetrying(BaseRetrying):
|
|
|
107
107
|
self.begin()
|
|
108
108
|
|
|
109
109
|
retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
|
|
110
|
+
is_async = _utils.is_coroutine_callable(fn)
|
|
110
111
|
while True:
|
|
111
112
|
do = await self.iter(retry_state=retry_state)
|
|
112
113
|
if isinstance(do, DoAttempt):
|
|
113
114
|
try:
|
|
114
|
-
|
|
115
|
+
if is_async:
|
|
116
|
+
result = await fn(*args, **kwargs)
|
|
117
|
+
else:
|
|
118
|
+
result = fn(*args, **kwargs)
|
|
115
119
|
except BaseException: # noqa: B902
|
|
116
120
|
retry_state.set_exception(sys.exc_info()) # type: ignore[arg-type]
|
|
117
121
|
else:
|
|
@@ -19,8 +19,6 @@ import typing
|
|
|
19
19
|
from tenacity import _utils
|
|
20
20
|
|
|
21
21
|
if typing.TYPE_CHECKING:
|
|
22
|
-
import logging
|
|
23
|
-
|
|
24
22
|
from tenacity import RetryCallState
|
|
25
23
|
|
|
26
24
|
|
|
@@ -29,7 +27,7 @@ def before_nothing(retry_state: "RetryCallState") -> None:
|
|
|
29
27
|
|
|
30
28
|
|
|
31
29
|
def before_log(
|
|
32
|
-
logger:
|
|
30
|
+
logger: _utils.LoggerProtocol, log_level: int
|
|
33
31
|
) -> typing.Callable[["RetryCallState"], None]:
|
|
34
32
|
"""Before call strategy that logs to some logger the attempt."""
|
|
35
33
|
|
|
@@ -19,21 +19,20 @@ import typing
|
|
|
19
19
|
from tenacity import _utils
|
|
20
20
|
|
|
21
21
|
if typing.TYPE_CHECKING:
|
|
22
|
-
import logging
|
|
23
|
-
|
|
24
22
|
from tenacity import RetryCallState
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
def before_sleep_nothing(retry_state: "RetryCallState") -> None:
|
|
28
|
-
"""Before
|
|
26
|
+
"""Before sleep strategy that does nothing."""
|
|
29
27
|
|
|
30
28
|
|
|
31
29
|
def before_sleep_log(
|
|
32
|
-
logger:
|
|
30
|
+
logger: _utils.LoggerProtocol,
|
|
33
31
|
log_level: int,
|
|
34
32
|
exc_info: bool = False,
|
|
33
|
+
sec_format: str = "%.3g",
|
|
35
34
|
) -> typing.Callable[["RetryCallState"], None]:
|
|
36
|
-
"""Before
|
|
35
|
+
"""Before sleep strategy that logs to some logger the attempt."""
|
|
37
36
|
|
|
38
37
|
def log_it(retry_state: "RetryCallState") -> None:
|
|
39
38
|
local_exc_info: BaseException | bool | None
|
|
@@ -65,7 +64,7 @@ def before_sleep_log(
|
|
|
65
64
|
logger.log(
|
|
66
65
|
log_level,
|
|
67
66
|
f"Retrying {fn_name} "
|
|
68
|
-
f"in {retry_state.next_action.sleep} seconds as it {verb} {value}.",
|
|
67
|
+
f"in {sec_format % retry_state.next_action.sleep} seconds as it {verb} {value}.",
|
|
69
68
|
exc_info=local_exc_info,
|
|
70
69
|
)
|
|
71
70
|
|
|
@@ -207,7 +207,7 @@ class retry_if_exception_message(retry_if_exception):
|
|
|
207
207
|
def __init__(
|
|
208
208
|
self,
|
|
209
209
|
message: typing.Optional[str] = None,
|
|
210
|
-
match: typing.
|
|
210
|
+
match: typing.Union[None, str, typing.Pattern[str]] = None,
|
|
211
211
|
) -> None:
|
|
212
212
|
if message and match:
|
|
213
213
|
raise TypeError(
|
|
@@ -242,7 +242,7 @@ class retry_if_not_exception_message(retry_if_exception_message):
|
|
|
242
242
|
def __init__(
|
|
243
243
|
self,
|
|
244
244
|
message: typing.Optional[str] = None,
|
|
245
|
-
match: typing.
|
|
245
|
+
match: typing.Union[None, str, typing.Pattern[str]] = None,
|
|
246
246
|
) -> None:
|
|
247
247
|
super().__init__(message, match)
|
|
248
248
|
# invert predicate
|
|
@@ -37,7 +37,7 @@ class TornadoRetrying(BaseRetrying):
|
|
|
37
37
|
super().__init__(**kwargs)
|
|
38
38
|
self.sleep = sleep
|
|
39
39
|
|
|
40
|
-
@gen.coroutine # type: ignore[
|
|
40
|
+
@gen.coroutine # type: ignore[untyped-decorator]
|
|
41
41
|
def __call__(
|
|
42
42
|
self,
|
|
43
43
|
fn: "typing.Callable[..., typing.Union[typing.Generator[typing.Any, typing.Any, _RetValT], Future[_RetValT]]]",
|
|
@@ -98,10 +98,10 @@ class wait_chain(wait_base):
|
|
|
98
98
|
|
|
99
99
|
@retry(wait=wait_chain(*[wait_fixed(1) for i in range(3)] +
|
|
100
100
|
[wait_fixed(2) for j in range(5)] +
|
|
101
|
-
[wait_fixed(5) for k in range(4)))
|
|
101
|
+
[wait_fixed(5) for k in range(4)]))
|
|
102
102
|
def wait_chained():
|
|
103
|
-
print("Wait 1s for 3 attempts, 2s for 5 attempts and 5s
|
|
104
|
-
|
|
103
|
+
print("Wait 1s for 3 attempts, 2s for 5 attempts and 5s "
|
|
104
|
+
"thereafter.")
|
|
105
105
|
"""
|
|
106
106
|
|
|
107
107
|
def __init__(self, *strategies: wait_base) -> None:
|
|
@@ -113,6 +113,45 @@ class wait_chain(wait_base):
|
|
|
113
113
|
return wait_func(retry_state=retry_state)
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
class wait_exception(wait_base):
|
|
117
|
+
"""Wait strategy that waits the amount of time returned by the predicate.
|
|
118
|
+
|
|
119
|
+
The predicate is passed the exception object. Based on the exception, the
|
|
120
|
+
user can decide how much time to wait before retrying.
|
|
121
|
+
|
|
122
|
+
For example::
|
|
123
|
+
|
|
124
|
+
def http_error(exception: BaseException) -> float:
|
|
125
|
+
if (
|
|
126
|
+
isinstance(exception, requests.HTTPError)
|
|
127
|
+
and exception.response.status_code == requests.codes.too_many_requests
|
|
128
|
+
):
|
|
129
|
+
return float(exception.response.headers.get("Retry-After", "1"))
|
|
130
|
+
return 60.0
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@retry(
|
|
134
|
+
stop=stop_after_attempt(3),
|
|
135
|
+
wait=wait_exception(http_error),
|
|
136
|
+
)
|
|
137
|
+
def http_get_request(url: str) -> None:
|
|
138
|
+
response = requests.get(url)
|
|
139
|
+
response.raise_for_status()
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def __init__(self, predicate: typing.Callable[[BaseException], float]) -> None:
|
|
143
|
+
self.predicate = predicate
|
|
144
|
+
|
|
145
|
+
def __call__(self, retry_state: "RetryCallState") -> float:
|
|
146
|
+
if retry_state.outcome is None:
|
|
147
|
+
raise RuntimeError("__call__() called before outcome was set")
|
|
148
|
+
|
|
149
|
+
exception = retry_state.outcome.exception()
|
|
150
|
+
if exception is None:
|
|
151
|
+
raise RuntimeError("outcome failed but the exception is None")
|
|
152
|
+
return self.predicate(exception)
|
|
153
|
+
|
|
154
|
+
|
|
116
155
|
class wait_incrementing(wait_base):
|
|
117
156
|
"""Wait an incremental amount of time after each attempt.
|
|
118
157
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: tenacity
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.1.3
|
|
4
4
|
Summary: Retry code until it succeeds
|
|
5
5
|
Home-page: https://github.com/jd/tenacity
|
|
6
6
|
Author: Julien Danjou
|
|
@@ -11,15 +11,21 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
11
11
|
Classifier: Programming Language :: Python
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
19
|
Classifier: Topic :: Utilities
|
|
20
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
License-File: LICENSE
|
|
21
22
|
Provides-Extra: doc
|
|
23
|
+
Requires-Dist: reno; extra == "doc"
|
|
24
|
+
Requires-Dist: sphinx; extra == "doc"
|
|
22
25
|
Provides-Extra: test
|
|
23
|
-
|
|
26
|
+
Requires-Dist: pytest; extra == "test"
|
|
27
|
+
Requires-Dist: tornado>=4.5; extra == "test"
|
|
28
|
+
Requires-Dist: typeguard; extra == "test"
|
|
29
|
+
Dynamic: license-file
|
|
24
30
|
|
|
25
31
|
Tenacity is a general-purpose retrying library to simplify the task of adding retry behavior to just about anything.
|
|
@@ -11,7 +11,7 @@ setup.py
|
|
|
11
11
|
tox.ini
|
|
12
12
|
.github/dependabot.yml
|
|
13
13
|
.github/workflows/ci.yaml
|
|
14
|
-
.github/workflows/
|
|
14
|
+
.github/workflows/release.yml
|
|
15
15
|
doc/source/api.rst
|
|
16
16
|
doc/source/changelog.rst
|
|
17
17
|
doc/source/conf.py
|
|
@@ -19,6 +19,7 @@ doc/source/index.rst
|
|
|
19
19
|
releasenotes/notes/Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml
|
|
20
20
|
releasenotes/notes/Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml
|
|
21
21
|
releasenotes/notes/add-async-actions-b249c527d99723bb.yaml
|
|
22
|
+
releasenotes/notes/add-re-pattern-to-match-types-6a4c1d9e64e2a5e1.yaml
|
|
22
23
|
releasenotes/notes/add-reno-d1ab5710f272650a.yaml
|
|
23
24
|
releasenotes/notes/add-retry_except_exception_type-31b31da1924d55f4.yaml
|
|
24
25
|
releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml
|
|
@@ -29,11 +30,13 @@ releasenotes/notes/added_a_link_to_documentation-eefaf8f074b539f8.yaml
|
|
|
29
30
|
releasenotes/notes/after_log-50f4d73b24ce9203.yaml
|
|
30
31
|
releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml
|
|
31
32
|
releasenotes/notes/annotate_code-197b93130df14042.yaml
|
|
33
|
+
releasenotes/notes/async-sleep-retrying-32de5866f5d041.yaml
|
|
32
34
|
releasenotes/notes/before_sleep_log-improvements-d8149274dfb37d7c.yaml
|
|
33
35
|
releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml
|
|
34
36
|
releasenotes/notes/dependabot-for-github-actions-4d2464f3c0928463.yaml
|
|
35
37
|
releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml
|
|
36
38
|
releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml
|
|
39
|
+
releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml
|
|
37
40
|
releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml
|
|
38
41
|
releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml
|
|
39
42
|
releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml
|
|
@@ -42,6 +45,7 @@ releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml
|
|
|
42
45
|
releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml
|
|
43
46
|
releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml
|
|
44
47
|
releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml
|
|
48
|
+
releasenotes/notes/logging-protocol-a4cf0f786f21e4ee.yaml
|
|
45
49
|
releasenotes/notes/make-logger-more-compatible-5da1ddf1bab77047.yaml
|
|
46
50
|
releasenotes/notes/no-async-iter-6132a42e52348a75.yaml
|
|
47
51
|
releasenotes/notes/pr320-py3-only-wheel-tag.yaml
|
|
@@ -50,6 +54,7 @@ releasenotes/notes/remove-py36-876c0416cf279d15.yaml
|
|
|
50
54
|
releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml
|
|
51
55
|
releasenotes/notes/some-slug-for-preserve-defaults-86682846dfa18005.yaml
|
|
52
56
|
releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml
|
|
57
|
+
releasenotes/notes/support-py3.14-14928188cab53b99.yaml
|
|
53
58
|
releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml
|
|
54
59
|
releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml
|
|
55
60
|
releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml
|
|
@@ -27,7 +27,7 @@ class TestAfterLogFormat(unittest.TestCase):
|
|
|
27
27
|
log = unittest.mock.MagicMock(spec="logging.Logger.log")
|
|
28
28
|
logger = unittest.mock.MagicMock(spec="logging.Logger", log=log)
|
|
29
29
|
|
|
30
|
-
sec_format = "
|
|
30
|
+
sec_format = "%.3g"
|
|
31
31
|
delay_since_first_attempt = 0.1
|
|
32
32
|
|
|
33
33
|
retry_state = test_tenacity.make_retry_state(
|
|
@@ -34,14 +34,17 @@ from tenacity import asyncio as tasyncio
|
|
|
34
34
|
from tenacity import retry, retry_if_exception, retry_if_result, stop_after_attempt
|
|
35
35
|
from tenacity.wait import wait_fixed
|
|
36
36
|
|
|
37
|
-
from .test_tenacity import
|
|
37
|
+
from .test_tenacity import (
|
|
38
|
+
NoIOErrorAfterCount,
|
|
39
|
+
NoneReturnUntilAfterCount,
|
|
40
|
+
current_time_ms,
|
|
41
|
+
)
|
|
38
42
|
|
|
39
43
|
|
|
40
44
|
def asynctest(callable_):
|
|
41
45
|
@wraps(callable_)
|
|
42
46
|
def wrapper(*a, **kw):
|
|
43
|
-
|
|
44
|
-
return loop.run_until_complete(callable_(*a, **kw))
|
|
47
|
+
return asyncio.run(callable_(*a, **kw))
|
|
45
48
|
|
|
46
49
|
return wrapper
|
|
47
50
|
|
|
@@ -464,5 +467,26 @@ async def foo():
|
|
|
464
467
|
pass
|
|
465
468
|
|
|
466
469
|
|
|
470
|
+
class TestSyncFunctionWithAsyncSleep(unittest.TestCase):
|
|
471
|
+
@asynctest
|
|
472
|
+
async def test_sync_function_with_async_sleep(self):
|
|
473
|
+
"""A sync function with an async sleep callable uses AsyncRetrying."""
|
|
474
|
+
mock_sleep = mock.AsyncMock()
|
|
475
|
+
|
|
476
|
+
thing = NoneReturnUntilAfterCount(2)
|
|
477
|
+
|
|
478
|
+
@retry(
|
|
479
|
+
sleep=mock_sleep,
|
|
480
|
+
wait=wait_fixed(1),
|
|
481
|
+
retry=retry_if_result(lambda x: x is None),
|
|
482
|
+
)
|
|
483
|
+
def sync_function():
|
|
484
|
+
return thing.go()
|
|
485
|
+
|
|
486
|
+
result = await sync_function()
|
|
487
|
+
assert result is True
|
|
488
|
+
assert mock_sleep.await_count == 2
|
|
489
|
+
|
|
490
|
+
|
|
467
491
|
if __name__ == "__main__":
|
|
468
492
|
unittest.main()
|
|
@@ -12,8 +12,7 @@ def asynctest(
|
|
|
12
12
|
) -> typing.Callable[..., typing.Any]:
|
|
13
13
|
@wraps(callable_)
|
|
14
14
|
def wrapper(*a: typing.Any, **kw: typing.Any) -> typing.Any:
|
|
15
|
-
|
|
16
|
-
return loop.run_until_complete(callable_(*a, **kw))
|
|
15
|
+
return asyncio.run(callable_(*a, **kw))
|
|
17
16
|
|
|
18
17
|
return wrapper
|
|
19
18
|
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
import datetime
|
|
18
18
|
import logging
|
|
19
19
|
import re
|
|
20
|
-
import sys
|
|
21
20
|
import time
|
|
22
21
|
import typing
|
|
23
22
|
import unittest
|
|
@@ -28,6 +27,7 @@ from fractions import Fraction
|
|
|
28
27
|
from unittest import mock
|
|
29
28
|
|
|
30
29
|
import pytest
|
|
30
|
+
from typeguard import check_type
|
|
31
31
|
|
|
32
32
|
import tenacity
|
|
33
33
|
from tenacity import RetryCallState, RetryError, Retrying, retry
|
|
@@ -369,6 +369,24 @@ class TestWaitConditions(unittest.TestCase):
|
|
|
369
369
|
self.assertLess(w, 8)
|
|
370
370
|
self.assertGreaterEqual(w, 5)
|
|
371
371
|
|
|
372
|
+
def test_wait_exception(self):
|
|
373
|
+
def predicate(exc):
|
|
374
|
+
if isinstance(exc, ValueError):
|
|
375
|
+
return 3.5
|
|
376
|
+
return 10.0
|
|
377
|
+
|
|
378
|
+
r = Retrying(wait=tenacity.wait_exception(predicate))
|
|
379
|
+
|
|
380
|
+
fut1 = tenacity.Future.construct(1, ValueError(), True)
|
|
381
|
+
self.assertEqual(r.wait(make_retry_state(1, 0, last_result=fut1)), 3.5)
|
|
382
|
+
|
|
383
|
+
fut2 = tenacity.Future.construct(1, KeyError(), True)
|
|
384
|
+
self.assertEqual(r.wait(make_retry_state(1, 0, last_result=fut2)), 10.0)
|
|
385
|
+
|
|
386
|
+
fut3 = tenacity.Future.construct(1, None, False)
|
|
387
|
+
with self.assertRaises(RuntimeError):
|
|
388
|
+
r.wait(make_retry_state(1, 0, last_result=fut3))
|
|
389
|
+
|
|
372
390
|
def test_wait_double_sum(self):
|
|
373
391
|
r = Retrying(wait=tenacity.wait_random(0, 3) + tenacity.wait_fixed(5))
|
|
374
392
|
# Test it a few time since it's random
|
|
@@ -1711,17 +1729,8 @@ class TestRetryException(unittest.TestCase):
|
|
|
1711
1729
|
|
|
1712
1730
|
|
|
1713
1731
|
class TestRetryTyping(unittest.TestCase):
|
|
1714
|
-
@pytest.mark.skipif(
|
|
1715
|
-
sys.version_info < (3, 0), reason="typeguard not supported for python 2"
|
|
1716
|
-
)
|
|
1717
1732
|
def test_retry_type_annotations(self):
|
|
1718
1733
|
"""The decorator should maintain types of decorated functions."""
|
|
1719
|
-
# Just in case this is run with unit-test, return early for py2
|
|
1720
|
-
if sys.version_info < (3, 0):
|
|
1721
|
-
return
|
|
1722
|
-
|
|
1723
|
-
# Function-level import because we can't install this for python 2.
|
|
1724
|
-
from typeguard import check_type
|
|
1725
1734
|
|
|
1726
1735
|
def num_to_str(number):
|
|
1727
1736
|
# type: (int) -> str
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
[tox]
|
|
2
|
-
|
|
2
|
+
# we only test trio on latest python version
|
|
3
|
+
envlist = py3{10,11,12,13,14,14-trio}, pep8, pypy3
|
|
3
4
|
skip_missing_interpreters = True
|
|
4
5
|
|
|
5
6
|
[testenv]
|
|
@@ -10,9 +11,9 @@ deps =
|
|
|
10
11
|
.[doc]
|
|
11
12
|
trio: trio
|
|
12
13
|
commands =
|
|
13
|
-
py3{
|
|
14
|
-
py3{
|
|
15
|
-
py3{
|
|
14
|
+
py3{10,11,12,13,14},pypy3: pytest {posargs}
|
|
15
|
+
py3{10,11,12,13,14},pypy3: sphinx-build -a -E -W -b doctest doc/source doc/build
|
|
16
|
+
py3{10,11,12,13,14},pypy3: sphinx-build -a -E -W -b html doc/source doc/build
|
|
16
17
|
|
|
17
18
|
[testenv:pep8]
|
|
18
19
|
basepython = python3
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
name: Release deploy
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types:
|
|
6
|
-
- published
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
publish:
|
|
10
|
-
timeout-minutes: 20
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- name: Checkout 🛎️
|
|
14
|
-
uses: actions/checkout@v4.1.7
|
|
15
|
-
with:
|
|
16
|
-
fetch-depth: 0
|
|
17
|
-
|
|
18
|
-
- name: Setup Python 🔧
|
|
19
|
-
uses: actions/setup-python@v5.1.0
|
|
20
|
-
with:
|
|
21
|
-
python-version: 3.11
|
|
22
|
-
|
|
23
|
-
- name: Build 🔧 & Deploy 🚀
|
|
24
|
-
env:
|
|
25
|
-
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
26
|
-
run: |
|
|
27
|
-
pip install tox twine wheel
|
|
28
|
-
|
|
29
|
-
echo -e "[pypi]" >> ~/.pypirc
|
|
30
|
-
echo -e "username = __token__" >> ~/.pypirc
|
|
31
|
-
echo -e "password = $PYPI_TOKEN" >> ~/.pypirc
|
|
32
|
-
|
|
33
|
-
python setup.py sdist bdist_wheel
|
|
34
|
-
twine upload dist/*
|
tenacity-9.0.0/.mergify.yml
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
queue_rules:
|
|
2
|
-
- name: default
|
|
3
|
-
merge_method: squash
|
|
4
|
-
queue_conditions:
|
|
5
|
-
- or:
|
|
6
|
-
- author = jd
|
|
7
|
-
- "#approved-reviews-by >= 1"
|
|
8
|
-
- author = dependabot[bot]
|
|
9
|
-
- or:
|
|
10
|
-
- files ~= ^releasenotes/notes/
|
|
11
|
-
- label = no-changelog
|
|
12
|
-
- author = dependabot[bot]
|
|
13
|
-
- "check-success=test (3.8, py38)"
|
|
14
|
-
- "check-success=test (3.9, py39)"
|
|
15
|
-
- "check-success=test (3.10, py310)"
|
|
16
|
-
- "check-success=test (3.11, py311)"
|
|
17
|
-
- "check-success=test (3.12, py312,py312-trio)"
|
|
18
|
-
- "check-success=test (3.12, pep8)"
|
|
19
|
-
|
|
20
|
-
pull_request_rules:
|
|
21
|
-
- name: warn on no changelog
|
|
22
|
-
conditions:
|
|
23
|
-
- -files~=^releasenotes/notes/
|
|
24
|
-
- label!=no-changelog
|
|
25
|
-
- -closed
|
|
26
|
-
actions:
|
|
27
|
-
comment:
|
|
28
|
-
message: >
|
|
29
|
-
⚠️ No release notes detected. Please make sure to use
|
|
30
|
-
[reno](https://docs.openstack.org/reno/latest/user/usage.html) to add
|
|
31
|
-
a changelog entry.
|
|
32
|
-
|
|
33
|
-
- name: automatic queue
|
|
34
|
-
conditions: []
|
|
35
|
-
actions:
|
|
36
|
-
queue:
|
|
37
|
-
|
|
38
|
-
- name: dismiss reviews
|
|
39
|
-
conditions: []
|
|
40
|
-
actions:
|
|
41
|
-
dismiss_reviews: {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-async-actions-b249c527d99723bb.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml
RENAMED
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|