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.
Files changed (90) hide show
  1. {tenacity-9.0.0 → tenacity-9.1.3}/.github/workflows/ci.yaml +10 -10
  2. tenacity-9.1.3/.github/workflows/release.yml +34 -0
  3. tenacity-9.1.3/.mergify.yml +21 -0
  4. {tenacity-9.0.0/tenacity.egg-info → tenacity-9.1.3}/PKG-INFO +12 -6
  5. {tenacity-9.0.0 → tenacity-9.1.3}/README.rst +0 -14
  6. {tenacity-9.0.0 → tenacity-9.1.3}/doc/source/index.rst +0 -14
  7. {tenacity-9.0.0 → tenacity-9.1.3}/pyproject.toml +1 -1
  8. tenacity-9.1.3/releasenotes/notes/add-re-pattern-to-match-types-6a4c1d9e64e2a5e1.yaml +4 -0
  9. tenacity-9.1.3/releasenotes/notes/async-sleep-retrying-32de5866f5d041.yaml +7 -0
  10. tenacity-9.1.3/releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml +5 -0
  11. tenacity-9.1.3/releasenotes/notes/logging-protocol-a4cf0f786f21e4ee.yaml +5 -0
  12. tenacity-9.1.3/releasenotes/notes/support-py3.14-14928188cab53b99.yaml +3 -0
  13. {tenacity-9.0.0 → tenacity-9.1.3}/setup.cfg +3 -3
  14. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/__init__.py +39 -25
  15. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/_utils.py +12 -0
  16. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/after.py +2 -4
  17. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/asyncio/__init__.py +5 -1
  18. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/before.py +1 -3
  19. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/before_sleep.py +5 -6
  20. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/retry.py +2 -2
  21. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/tornadoweb.py +1 -1
  22. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/wait.py +42 -3
  23. {tenacity-9.0.0 → tenacity-9.1.3/tenacity.egg-info}/PKG-INFO +12 -6
  24. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity.egg-info/SOURCES.txt +6 -1
  25. {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_after.py +1 -1
  26. {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_asyncio.py +27 -3
  27. {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_issue_478.py +1 -2
  28. {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_tenacity.py +19 -10
  29. {tenacity-9.0.0 → tenacity-9.1.3}/tox.ini +5 -4
  30. tenacity-9.0.0/.github/workflows/deploy.yaml +0 -34
  31. tenacity-9.0.0/.mergify.yml +0 -41
  32. {tenacity-9.0.0 → tenacity-9.1.3}/.editorconfig +0 -0
  33. {tenacity-9.0.0 → tenacity-9.1.3}/.github/dependabot.yml +0 -0
  34. {tenacity-9.0.0 → tenacity-9.1.3}/.gitignore +0 -0
  35. {tenacity-9.0.0 → tenacity-9.1.3}/.readthedocs.yml +0 -0
  36. {tenacity-9.0.0 → tenacity-9.1.3}/LICENSE +0 -0
  37. {tenacity-9.0.0 → tenacity-9.1.3}/doc/source/api.rst +0 -0
  38. {tenacity-9.0.0 → tenacity-9.1.3}/doc/source/changelog.rst +0 -0
  39. {tenacity-9.0.0 → tenacity-9.1.3}/doc/source/conf.py +0 -0
  40. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml +0 -0
  41. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml +0 -0
  42. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-async-actions-b249c527d99723bb.yaml +0 -0
  43. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-reno-d1ab5710f272650a.yaml +0 -0
  44. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-retry_except_exception_type-31b31da1924d55f4.yaml +0 -0
  45. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml +0 -0
  46. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add-test-extra-55e869261b03e56d.yaml +0 -0
  47. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add_omitted_modules_to_import_all-2ab282f20a2c22f7.yaml +0 -0
  48. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/add_retry_if_exception_cause_type-d16b918ace4ae0ad.yaml +0 -0
  49. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/added_a_link_to_documentation-eefaf8f074b539f8.yaml +0 -0
  50. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/after_log-50f4d73b24ce9203.yaml +0 -0
  51. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml +0 -0
  52. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/annotate_code-197b93130df14042.yaml +0 -0
  53. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/before_sleep_log-improvements-d8149274dfb37d7c.yaml +0 -0
  54. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml +0 -0
  55. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/dependabot-for-github-actions-4d2464f3c0928463.yaml +0 -0
  56. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml +0 -0
  57. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml +0 -0
  58. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml +0 -0
  59. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml +0 -0
  60. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml +0 -0
  61. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml +0 -0
  62. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml +0 -0
  63. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml +0 -0
  64. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml +0 -0
  65. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml +0 -0
  66. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/make-logger-more-compatible-5da1ddf1bab77047.yaml +0 -0
  67. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/no-async-iter-6132a42e52348a75.yaml +0 -0
  68. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/pr320-py3-only-wheel-tag.yaml +0 -0
  69. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/py36_plus-c425fb3aa17c6682.yaml +0 -0
  70. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/remove-py36-876c0416cf279d15.yaml +0 -0
  71. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml +0 -0
  72. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/some-slug-for-preserve-defaults-86682846dfa18005.yaml +0 -0
  73. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml +0 -0
  74. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml +0 -0
  75. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml +0 -0
  76. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml +0 -0
  77. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml +0 -0
  78. {tenacity-9.0.0 → tenacity-9.1.3}/releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml +0 -0
  79. {tenacity-9.0.0 → tenacity-9.1.3}/reno.yaml +0 -0
  80. {tenacity-9.0.0 → tenacity-9.1.3}/setup.py +0 -0
  81. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/asyncio/retry.py +0 -0
  82. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/nap.py +0 -0
  83. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/py.typed +0 -0
  84. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity/stop.py +0 -0
  85. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity.egg-info/dependency_links.txt +0 -0
  86. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity.egg-info/requires.txt +0 -0
  87. {tenacity-9.0.0 → tenacity-9.1.3}/tenacity.egg-info/top_level.txt +0 -0
  88. {tenacity-9.0.0 → tenacity-9.1.3}/tests/__init__.py +0 -0
  89. {tenacity-9.0.0 → tenacity-9.1.3}/tests/test_tornado.py +0 -0
  90. {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-20.04
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,py312-trio
31
- - python: "3.12"
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.11"
33
+ - python: "3.14"
34
34
  tox: mypy
35
35
  steps:
36
36
  - name: Checkout 🛎️
37
- uses: actions/checkout@v4.1.7
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@v5.1.0
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
1
+ Metadata-Version: 2.4
2
2
  Name: tenacity
3
- Version: 9.0.0
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.8
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
- License-File: LICENSE
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
@@ -11,7 +11,7 @@ build-backend="setuptools.build_meta"
11
11
  [tool.ruff]
12
12
  line-length = 88
13
13
  indent-width = 4
14
- target-version = "py38"
14
+ target-version = "py310"
15
15
 
16
16
  [tool.mypy]
17
17
  strict = true
@@ -0,0 +1,4 @@
1
+ ---
2
+ fixes:
3
+ - |
4
+ Added `re.Pattern` to allowed match types.
@@ -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.
@@ -0,0 +1,5 @@
1
+ ---
2
+ upgrade:
3
+ - |
4
+ Python 3.9 has reached end-of-life and is no longer supported.
5
+ The minimum supported version is now Python 3.10.
@@ -0,0 +1,5 @@
1
+ ---
2
+ other:
3
+ - |
4
+ Accept non-standard logger in helpers logging something (eg: structlog, loguru...)
5
+
@@ -0,0 +1,3 @@
1
+ ---
2
+ features:
3
+ - Python 3.14 support has been added.
@@ -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.8
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 after strategies for easier usage.
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
- dataclass_kwargs = {}
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
- ) -> "BaseRetrying":
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
- try:
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
- return self._local.statistics
310
+ return self._local.statistics # type: ignore[no-any-return]
313
311
 
314
312
  @property
315
313
  def iter_state(self) -> IterState:
316
- try:
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
- return self._local.iter_state
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
- if sys.version_info >= (3, 9):
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
- sleep: t.Callable[[t.Union[int, float]], t.Union[None, t.Awaitable[None]]] = sleep,
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
- if _utils.is_coroutine_callable(f):
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: "logging.Logger",
30
+ logger: _utils.LoggerProtocol,
33
31
  log_level: int,
34
- sec_format: str = "%0.3f",
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
- result = await fn(*args, **kwargs)
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: "logging.Logger", log_level: int
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 call strategy that does nothing."""
26
+ """Before sleep strategy that does nothing."""
29
27
 
30
28
 
31
29
  def before_sleep_log(
32
- logger: "logging.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 call strategy that logs to some logger the attempt."""
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.Optional[str] = None,
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.Optional[str] = None,
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[misc]
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
- thereafter.")
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
1
+ Metadata-Version: 2.4
2
2
  Name: tenacity
3
- Version: 9.0.0
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.8
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
- License-File: LICENSE
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/deploy.yaml
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 = "%0.3f"
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 NoIOErrorAfterCount, current_time_ms
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
- loop = asyncio.get_event_loop()
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
- loop = asyncio.get_event_loop()
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
- envlist = py3{8,9,10,11,12,12-trio}, pep8, pypy3
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{8,9,10,11,12},pypy3: pytest {posargs}
14
- py3{8,9,10,11,12},pypy3: sphinx-build -a -E -W -b doctest doc/source doc/build
15
- py3{8,9,10,11,12},pypy3: sphinx-build -a -E -W -b html doc/source doc/build
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/*
@@ -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
File without changes
File without changes
File without changes
File without changes