Full Code of jd/tenacity for AI

main 1106b9a168c5 cached
81 files
221.6 KB
56.0k tokens
485 symbols
1 requests
Download .txt
Showing preview only (243K chars total). Download the full file or copy to clipboard to get everything.
Repository: jd/tenacity
Branch: main
Commit: 1106b9a168c5
Files: 81
Total size: 221.6 KB

Directory structure:
gitextract_hx8yszl8/

├── .editorconfig
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yaml
│       └── release.yml
├── .gitignore
├── .mergify.yml
├── .readthedocs.yml
├── LICENSE
├── doc/
│   └── source/
│       ├── api.rst
│       ├── changelog.rst
│       ├── conf.py
│       └── index.rst
├── pyproject.toml
├── releasenotes/
│   └── notes/
│       ├── Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml
│       ├── Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml
│       ├── add-async-actions-b249c527d99723bb.yaml
│       ├── add-re-pattern-to-match-types-6a4c1d9e64e2a5e1.yaml
│       ├── add-reno-d1ab5710f272650a.yaml
│       ├── add-retry_except_exception_type-31b31da1924d55f4.yaml
│       ├── add-stop-before-delay-a775f88ac872c923.yaml
│       ├── add-test-extra-55e869261b03e56d.yaml
│       ├── add_omitted_modules_to_import_all-2ab282f20a2c22f7.yaml
│       ├── add_retry_if_exception_cause_type-d16b918ace4ae0ad.yaml
│       ├── added_a_link_to_documentation-eefaf8f074b539f8.yaml
│       ├── after_log-50f4d73b24ce9203.yaml
│       ├── allow-mocking-of-nap-sleep-6679c50e702446f1.yaml
│       ├── annotate_code-197b93130df14042.yaml
│       ├── async-sleep-retrying-32de5866f5d041.yaml
│       ├── before_sleep_log-improvements-d8149274dfb37d7c.yaml
│       ├── clarify-reraise-option-6829667eacf4f599.yaml
│       ├── dependabot-for-github-actions-4d2464f3c0928463.yaml
│       ├── deprecate-initial-for-multiplier-c7b4e2d9f1a83065.yaml
│       ├── do_not_package_tests-fe5ac61940b0a5ed.yaml
│       ├── drop-deprecated-python-versions-69a05cb2e0f1034c.yaml
│       ├── drop-python-3.9-ecfa2d7db9773e96.yaml
│       ├── drop_deprecated-7ea90b212509b082.yaml
│       ├── export-convenience-symbols-981d9611c8b754f3.yaml
│       ├── fix-async-loop-with-result-f68e913ccb425aca.yaml
│       ├── fix-async-retry-type-overloads-27f3e0c239ed6b.yaml
│       ├── fix-local-context-overwrite-94190ba06a481631.yaml
│       ├── fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml
│       ├── fix-setuptools-config-3af71aa3592b6948.yaml
│       ├── fix-wait-typing-b26eecdb6cc0a1de.yaml
│       ├── fix_async-52b6594c8e75c4bc.yaml
│       ├── logging-protocol-a4cf0f786f21e4ee.yaml
│       ├── make-logger-more-compatible-5da1ddf1bab77047.yaml
│       ├── no-async-iter-6132a42e52348a75.yaml
│       ├── pr320-py3-only-wheel-tag.yaml
│       ├── py36_plus-c425fb3aa17c6682.yaml
│       ├── remove-py36-876c0416cf279d15.yaml
│       ├── retrycallstate-repr-94947f7b00ee15e1.yaml
│       ├── some-slug-for-preserve-defaults-86682846dfa18005.yaml
│       ├── sphinx_define_error-642c9cd5c165d39a.yaml
│       ├── support-py3.14-14928188cab53b99.yaml
│       ├── support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml
│       ├── timedelta-for-stop-ef6bf71b88ce9988.yaml
│       ├── trio-support-retry-22bd544800cd1f36.yaml
│       ├── wait-exponential-jitter-min-timedelta-a8e3c1f4b7d29e50.yaml
│       ├── wait-random-exponential-min-2a4b7eed9f002436.yaml
│       └── wait_exponential_jitter-6ffc81dddcbaa6d3.yaml
├── reno.yaml
├── tenacity/
│   ├── __init__.py
│   ├── _utils.py
│   ├── after.py
│   ├── asyncio/
│   │   ├── __init__.py
│   │   └── retry.py
│   ├── before.py
│   ├── before_sleep.py
│   ├── nap.py
│   ├── py.typed
│   ├── retry.py
│   ├── stop.py
│   ├── tornadoweb.py
│   └── wait.py
└── tests/
    ├── __init__.py
    ├── test_after.py
    ├── test_asyncio.py
    ├── test_issue_478.py
    ├── test_tenacity.py
    ├── test_tornado.py
    └── test_utils.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.{py,pyx,pxd,pyi}]
indent_size = 4
max_line_length = 120

[*.ini]
indent_size = 4

[*.rst]
max_line_length = 150

[Makefile]
indent_style = tab


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  - package-ecosystem: 'github-actions'
    directory: '/'
    schedule:
      interval: 'monthly'
    groups:
      github-actions:
        patterns:
          - '*'


================================================
FILE: .github/workflows/ci.yaml
================================================
name: Continuous Integration
permissions: read-all

on:
  pull_request:
    branches:
      - main

concurrency:
  # yamllint disable-line rule:line-length
  group: "${{ github.workflow }}-${{ github.head_ref || github.run_id }}"
  cancel-in-progress: true

jobs:
  test:
    timeout-minutes: 20
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        include:
          - python: "3.10"
            task: check
          - python: "3.11"
            task: check
          - python: "3.12"
            task: check
          - python: "3.13"
            task: check
          - python: "3.14"
            task: check
          - python: "3.14"
            task: lint
          - python: "3.14"
            task: mypy
    steps:
      - name: Checkout 🛎️
        uses: actions/checkout@v6.0.2
        with:
          fetch-depth: 0

      - name: Setup uv 🔧
        uses: astral-sh/setup-uv@v7

      - name: Build 🔧 & Test 🔍
        run: uv run --python ${{ matrix.python }} poe ${{ matrix.task }}


================================================
FILE: .github/workflows/release.yml
================================================
name: upload release to PyPI
on:
  release:
    types:
      - published

jobs:
  pypi-publish:
    name: upload release to PyPI
    runs-on: ubuntu-24.04
    environment: release
    permissions:
      id-token: write
    steps:
      - uses: actions/checkout@v6.0.2
        with:
          fetch-depth: 0
          fetch-tags: true

      - uses: astral-sh/setup-uv@v7

      - name: Build
        run: uv build

      - name: Publish package distributions to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1


================================================
FILE: .gitignore
================================================
.idea
dist
*.pyc
*.egg-info
build
.venv/
uv.lock
AUTHORS
ChangeLog
doc/_build

tenacity/_version.py
/.pytest_cache


================================================
FILE: .mergify.yml
================================================
queue_rules:
  - name: default
    merge_method: squash
    autoqueue: true
    queue_conditions:
      - or:
        - author = jd
        - "#approved-reviews-by >= 1"
        - author = dependabot[bot]
      - "check-success=test (3.10, check)"
      - "check-success=test (3.11, check)"
      - "check-success=test (3.12, check)"
      - "check-success=test (3.13, check)"
      - "check-success=test (3.14, check)"
      - "check-success=test (3.14, lint)"
      - "check-success=test (3.14, mypy)"

pull_request_rules:
  - name: dismiss reviews
    conditions: []
    actions:
      dismiss_reviews: {}


================================================
FILE: .readthedocs.yml
================================================
version: 2
build:
  os: ubuntu-24.04
  tools:
    python: "3.13"
python:
  install:
    - method: pip
      path: .
      extra_requirements:
        - doc


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

================================================
FILE: doc/source/api.rst
================================================
===============
 API Reference
===============

Retry Main API
--------------

.. autofunction:: tenacity.retry
   :noindex:

.. autoclass:: tenacity.Retrying
   :members:

.. autoclass:: tenacity.AsyncRetrying
   :members:

.. autoclass:: tenacity.tornadoweb.TornadoRetrying
   :members:

.. autoclass:: tenacity.RetryCallState
   :members:

After Functions
---------------

Those functions can be used as the `after` keyword argument of
:py:func:`tenacity.retry`.

.. automodule:: tenacity.after
   :members:

Before Functions
----------------

Those functions can be used as the `before` keyword argument of
:py:func:`tenacity.retry`.

.. automodule:: tenacity.before
   :members:

Before Sleep Functions
----------------------

Those functions can be used as the `before_sleep` keyword argument of
:py:func:`tenacity.retry`.

.. automodule:: tenacity.before_sleep
   :members:

Nap Functions
-------------

Those functions can be used as the `sleep` keyword argument of
:py:func:`tenacity.retry`.

.. automodule:: tenacity.nap
   :members:

Retry Functions
---------------

Those functions can be used as the `retry` keyword argument of
:py:func:`tenacity.retry`.

.. automodule:: tenacity.retry
   :members:

Stop Functions
--------------

Those functions can be used as the `stop` keyword argument of
:py:func:`tenacity.retry`.

.. automodule:: tenacity.stop
   :members:

Wait Functions
--------------

Those functions can be used as the `wait` keyword argument of
:py:func:`tenacity.retry`.

.. automodule:: tenacity.wait
   :members:


================================================
FILE: doc/source/changelog.rst
================================================
Changelog
=========

.. release-notes::


================================================
FILE: doc/source/conf.py
================================================
# Copyright 2016 Étienne Bersac
# Copyright 2016 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from importlib.metadata import version as pkg_version

master_doc = "index"
project = "Tenacity"
release = pkg_version("tenacity")
version = ".".join(release.split(".")[:2])

extensions = [
    "sphinx.ext.doctest",
    "sphinx.ext.autodoc",
    "reno.sphinxext",
]


================================================
FILE: doc/source/index.rst
================================================
Tenacity
========
.. image:: https://img.shields.io/pypi/v/tenacity.svg
    :target: https://pypi.org/project/tenacity

.. image:: https://img.shields.io/pypi/pyversions/tenacity.svg
    :target: https://pypi.org/project/tenacity

.. image:: https://github.com/jd/tenacity/actions/workflows/ci.yaml/badge.svg?branch=main
    :target: https://github.com/jd/tenacity/actions/workflows/ci.yaml

.. image:: https://img.shields.io/endpoint.svg?url=https://api.mergify.com/badges/jd/tenacity&style=flat
   :target: https://mergify.io
   :alt: Mergify Status

**Please refer to the** `tenacity documentation <https://tenacity.readthedocs.io/en/latest/>`_ **for a better experience.**

Tenacity is an Apache 2.0 licensed general-purpose retrying library, written in
Python, to simplify the task of adding retry behavior to just about anything.
It originates from `a fork of retrying
<https://github.com/rholder/retrying/issues/65>`_ which is sadly no longer
`maintained <https://julien.danjou.info/python-tenacity/>`_. Tenacity isn't
api compatible with retrying but adds significant new functionality and
fixes a number of longstanding bugs.

The simplest use case is retrying a flaky function whenever an `Exception`
occurs until a value is returned.

.. testcode::

    import random
    from tenacity import retry

    @retry
    def do_something_unreliable():
        if random.randint(0, 10) > 1:
            raise IOError("Broken sauce, everything is hosed!!!111one")
        else:
            return "Awesome sauce!"

    print(do_something_unreliable())

.. testoutput::
   :hide:

   Awesome sauce!


.. toctree::
    :hidden:
    :maxdepth: 2

    changelog
    api


Features
--------

- Generic Decorator API
- Specify stop condition (i.e. limit by number of attempts)
- Specify wait condition (i.e. exponential backoff sleeping between attempts)
- Customize retrying on Exceptions
- Customize retrying on expected returned result
- Retry on coroutines
- Retry code block with context manager


Installation
------------

To install *tenacity*, simply:

.. code-block:: bash

    $ pip install tenacity


Examples
----------

Basic Retry
~~~~~~~~~~~

.. testsetup::

    import logging
    #
    # Note the following import is used for demonstration convenience only.
    # Production code should always explicitly import the names it needs.
    #
    from tenacity import *

    class MyException(Exception):
        pass

As you saw above, the default behavior is to retry forever without waiting when
an exception is raised.

.. testcode::

    @retry
    def never_gonna_give_you_up():
        print("Retry forever ignoring Exceptions, don't wait between retries")
        raise Exception

Stopping
~~~~~~~~

Let's be a little less persistent and set some boundaries, such as the number
of attempts before giving up.

.. testcode::

    @retry(stop=stop_after_attempt(7))
    def stop_after_7_attempts():
        print("Stopping after 7 attempts")
        raise Exception

We don't have all day, so let's set a boundary for how long we should be
retrying stuff.

.. testcode::

    @retry(stop=stop_after_delay(10))
    def stop_after_10_s():
        print("Stopping after 10 seconds")
        raise Exception

If you're on a tight deadline, and exceeding your delay time isn't ok,
then you can give up on retries one attempt before you would exceed the delay.

.. testcode::

    @retry(stop=stop_before_delay(10))
    def stop_before_10_s():
        print("Stopping 1 attempt before 10 seconds")
        raise Exception

You can combine several stop conditions by using the `|` operator:

.. testcode::

    @retry(stop=(stop_after_delay(10) | stop_after_attempt(5)))
    def stop_after_10_s_or_5_retries():
        print("Stopping after 10 seconds or 5 retries")
        raise Exception

Waiting before retrying
~~~~~~~~~~~~~~~~~~~~~~~

Most things don't like to be polled as fast as possible, so let's just wait 2
seconds between retries.

.. testcode::

    @retry(wait=wait_fixed(2))
    def wait_2_s():
        print("Wait 2 second between retries")
        raise Exception

Some things perform best with a bit of randomness injected.

.. testcode::

    @retry(wait=wait_random(min=1, max=2))
    def wait_random_1_to_2_s():
        print("Randomly wait 1 to 2 seconds between retries")
        raise Exception

Then again, it's hard to beat exponential backoff when retrying distributed
services and other remote endpoints.

.. testcode::

    @retry(wait=wait_exponential(multiplier=1, min=4, max=10))
    def wait_exponential_1():
        print("Wait 2^x * 1 second between each retry starting with 4 seconds, then up to 10 seconds, then 10 seconds afterwards")
        raise Exception


Then again, it's also hard to beat combining fixed waits and jitter (to
help avoid thundering herds) when retrying distributed services and other
remote endpoints.

.. testcode::

    @retry(wait=wait_fixed(3) + wait_random(0, 2))
    def wait_fixed_jitter():
        print("Wait at least 3 seconds, and add up to 2 seconds of random delay")
        raise Exception

When multiple processes are in contention for a shared resource, exponentially
increasing jitter helps minimise collisions.

.. testcode::

    @retry(wait=wait_random_exponential(multiplier=1, max=60))
    def wait_exponential_jitter():
        print("Randomly wait up to 2^x * 1 seconds between each retry until the range reaches 60 seconds, then randomly up to 60 seconds afterwards")
        raise Exception


Sometimes it's necessary to build a chain of backoffs.

.. testcode::

    @retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] +
                           [wait_fixed(7) for i in range(2)] +
                           [wait_fixed(9)]))
    def wait_fixed_chained():
        print("Wait 3s for 3 attempts, 7s for the next 2 attempts and 9s for all attempts thereafter")
        raise Exception

Whether to retry
~~~~~~~~~~~~~~~~

We have a few options for dealing with retries that raise specific or general
exceptions, as in the cases here.

.. testcode::

    class ClientError(Exception):
        """Some type of client error."""

    @retry(retry=retry_if_exception_type(IOError))
    def might_io_error():
        print("Retry forever with no wait if an IOError occurs, raise any other errors")
        raise Exception

    @retry(retry=retry_if_not_exception_type(ClientError))
    def might_client_error():
        print("Retry forever with no wait if any error other than ClientError occurs. Immediately raise ClientError.")
        raise Exception

We can also use the result of the function to alter the behavior of retrying.

.. testcode::

    def is_none_p(value):
        """Return True if value is None"""
        return value is None

    @retry(retry=retry_if_result(is_none_p))
    def might_return_none():
        print("Retry with no wait if return value is None")

See also these methods:

.. testcode::

    retry_if_exception
    retry_if_exception_type
    retry_if_not_exception_type
    retry_unless_exception_type
    retry_if_result
    retry_if_not_result
    retry_if_exception_message
    retry_if_not_exception_message
    retry_any
    retry_all

We can also combine several conditions:

.. testcode::

    def is_none_p(value):
        """Return True if value is None"""
        return value is None

    @retry(retry=(retry_if_result(is_none_p) | retry_if_exception_type()))
    def might_return_none():
        print("Retry forever ignoring Exceptions with no wait if return value is None")

Any combination of stop, wait, etc. is also supported to give you the freedom
to mix and match.

It's also possible to retry explicitly at any time by raising the `TryAgain`
exception:

.. testcode::

   @retry
   def do_something():
       result = something_else()
       if result == 23:
          raise TryAgain

Error Handling
~~~~~~~~~~~~~~

Normally when your function fails its final time (and will not be retried again based on your settings),
a `RetryError` is raised. The exception your code encountered will be shown somewhere in the *middle*
of the stack trace.

If you would rather see the exception your code encountered at the *end* of the stack trace (where it
is most visible), you can set `reraise=True`.

.. testcode::

    @retry(reraise=True, stop=stop_after_attempt(3))
    def raise_my_exception():
        raise MyException("Fail")

    try:
        raise_my_exception()
    except MyException:
        # timed out retrying
        pass

Before and After Retry, and Logging
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

It's possible to execute an action before any attempt of calling the function
by using the before callback function:

.. testcode::

    import logging
    import sys

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    logger = logging.getLogger(__name__)

    @retry(stop=stop_after_attempt(3), before=before_log(logger, logging.DEBUG))
    def raise_my_exception():
        raise MyException("Fail")

In the same spirit, It's possible to execute after a call that failed:

.. testcode::

    import logging
    import sys

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    logger = logging.getLogger(__name__)

    @retry(stop=stop_after_attempt(3), after=after_log(logger, logging.DEBUG))
    def raise_my_exception():
        raise MyException("Fail")

It's also possible to only log failures that are going to be retried. Normally
retries happen after a wait interval, so the keyword argument is called
``before_sleep``:

.. testcode::

    import logging
    import sys

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    logger = logging.getLogger(__name__)

    @retry(stop=stop_after_attempt(3),
           before_sleep=before_sleep_log(logger, logging.DEBUG))
    def raise_my_exception():
        raise MyException("Fail")


Statistics
~~~~~~~~~~

You can access the statistics about the retry made over a function by using the
`statistics` attribute attached to the function:

.. testcode::

    @retry(stop=stop_after_attempt(3))
    def raise_my_exception():
        raise MyException("Fail")

    try:
        raise_my_exception()
    except Exception:
        pass

    print(raise_my_exception.statistics)

.. testoutput::
   :hide:

   ...

Custom Callbacks
~~~~~~~~~~~~~~~~

You can also define your own callbacks. The callback should accept one
parameter called ``retry_state`` that contains all information about current
retry invocation.

For example, you can call a custom callback function after all retries failed,
without raising an exception (or you can re-raise or do anything really)

.. testcode::

    def return_last_value(retry_state):
        """return the result of the last call attempt"""
        return retry_state.outcome.result()

    def is_false(value):
        """Return True if value is False"""
        return value is False

    # will return False after trying 3 times to get a different result
    @retry(stop=stop_after_attempt(3),
           retry_error_callback=return_last_value,
           retry=retry_if_result(is_false))
    def eventually_return_false():
        return False

RetryCallState
~~~~~~~~~~~~~~

``retry_state`` argument is an object of :class:`~tenacity.RetryCallState` class.
Its most useful attributes are:

* ``attempt_number`` — number of the current attempt (starts at 1)
* ``outcome`` — a :class:`concurrent.futures.Future` holding the last result or exception
* ``seconds_since_start`` — total elapsed seconds from the first attempt to the last outcome (``None`` if no outcome yet)
* ``idle_for`` — cumulative seconds spent sleeping between attempts
* ``start_time`` — :func:`time.monotonic` timestamp of the first attempt

For example, to log the total elapsed time after all retries:

.. testcode::

    import logging

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    logger = logging.getLogger(__name__)

    def log_elapsed(retry_state):
        logger.info('Finished after %.3fs', retry_state.seconds_since_start)

    @retry(stop=stop_after_attempt(3), after=log_elapsed)
    def raise_my_exception():
        raise MyException("Fail")

    try:
        raise_my_exception()
    except RetryError:
        pass

Other Custom Callbacks
~~~~~~~~~~~~~~~~~~~~~~

It's also possible to define custom callbacks for other keyword arguments.

.. function:: my_stop(retry_state)

   :param RetryCallState retry_state: info about current retry invocation
   :return: whether or not retrying should stop
   :rtype: bool

.. function:: my_wait(retry_state)

   :param RetryCallState retry_state: info about current retry invocation
   :return: number of seconds to wait before next retry
   :rtype: float

.. function:: my_retry(retry_state)

   :param RetryCallState retry_state: info about current retry invocation
   :return: whether or not retrying should continue
   :rtype: bool

.. function:: my_before(retry_state)

   :param RetryCallState retry_state: info about current retry invocation

.. function:: my_after(retry_state)

   :param RetryCallState retry_state: info about current retry invocation

.. function:: my_before_sleep(retry_state)

   :param RetryCallState retry_state: info about current retry invocation

Here's an example with a custom ``before_sleep`` function:

.. testcode::

    import logging

    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)

    logger = logging.getLogger(__name__)

    def my_before_sleep(retry_state):
        if retry_state.attempt_number < 1:
            loglevel = logging.INFO
        else:
            loglevel = logging.WARNING
        logger.log(
            loglevel, 'Retrying %s: attempt %s ended with: %s',
            retry_state.fn, retry_state.attempt_number, retry_state.outcome)

    @retry(stop=stop_after_attempt(3), before_sleep=my_before_sleep)
    def raise_my_exception():
        raise MyException("Fail")

    try:
        raise_my_exception()
    except RetryError:
        pass


Common Patterns
~~~~~~~~~~~~~~~

**Running setup code between retries** (e.g. reconnecting):

.. testcode::

    def reconnect(retry_state):
        print("Reconnecting before next attempt...")

    @retry(stop=stop_after_attempt(3), before_sleep=reconnect)
    def send_data():
        raise MyException("connection lost")

    try:
        send_data()
    except RetryError:
        pass

.. testoutput::
   :hide:

   ...

The ``before_sleep`` callback runs after a failed attempt and before sleeping,
making it ideal for re-establishing connections, refreshing tokens, or any
other setup that needs to happen before the next attempt.

**Accessing the attempt number inside the function** using the iterator API:

.. testcode::

    from tenacity import Retrying

    for attempt in Retrying(stop=stop_after_attempt(3)):
        with attempt:
            print(f"Attempt {attempt.retry_state.attempt_number}")
            if attempt.retry_state.attempt_number < 3:
                raise MyException("not yet")

.. testoutput::
   :hide:

   ...


Changing Arguments at Run Time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can change the arguments of a retry decorator as needed when calling it by
using the `retry_with` function attached to the wrapped function:

.. testcode::

    @retry(stop=stop_after_attempt(3))
    def raise_my_exception():
        raise MyException("Fail")

    try:
        raise_my_exception.retry_with(stop=stop_after_attempt(4))()
    except Exception:
        pass

    print(raise_my_exception.statistics)

.. testoutput::
   :hide:

   ...

If you want to use variables to set up the retry parameters, you don't have
to use the `retry` decorator - you can instead use `Retrying` directly:

.. testcode::

    def never_good_enough(arg1):
        raise Exception('Invalid argument: {}'.format(arg1))

    def try_never_good_enough(max_attempts=3):
        retryer = Retrying(stop=stop_after_attempt(max_attempts), reraise=True)
        retryer(never_good_enough, 'I really do try')

You may also want to change the behaviour of a decorated function temporarily,
like in tests to avoid unnecessary wait times. You can modify/patch the `retry`
attribute attached to the function. Bear in mind this is a write-only attribute,
statistics should be read from the function `statistics` attribute.

.. testcode::

    @retry(stop=stop_after_attempt(3), wait=wait_fixed(3))
    def raise_my_exception():
        raise MyException("Fail")

    from unittest import mock

    with mock.patch.object(raise_my_exception.retry, "wait", wait_fixed(0)):
        try:
            raise_my_exception()
        except Exception:
            pass

    print(raise_my_exception.statistics)

.. testoutput::
   :hide:

   ...

Disabling Retries
~~~~~~~~~~~~~~~~~

You can disable retrying entirely by passing ``enabled=False``. When disabled,
the decorated function is called directly without any retry logic. This is
useful during development or testing when you want fast feedback on failures:

.. testcode::

    import os

    @retry(
        enabled=os.getenv("ENABLE_RETRIES", "1") != "0",
        stop=stop_after_attempt(5),
        wait=wait_fixed(1),
    )
    def call_api():
        pass  # your code here

    call_api()

You can also use ``retry_with`` to disable retries on a per-call basis:

.. testcode::

    @retry(stop=stop_after_attempt(5))
    def call_api():
        pass  # your code here

    # In tests:
    call_api.retry_with(enabled=False)()

Retrying code block
~~~~~~~~~~~~~~~~~~~

Tenacity allows you to retry a code block without the need to wraps it in an
isolated function. This makes it easy to isolate failing block while sharing
context. The trick is to combine a for loop and a context manager.

.. testcode::

   from tenacity import Retrying, RetryError, stop_after_attempt

   try:
       for attempt in Retrying(stop=stop_after_attempt(3)):
           with attempt:
               raise Exception('My code is failing!')
   except RetryError:
       pass

You can configure every details of retry policy by configuring the Retrying
object.

With async code you can use AsyncRetrying.

.. testcode::

   from tenacity import AsyncRetrying, RetryError, stop_after_attempt

   async def function():
      try:
          async for attempt in AsyncRetrying(stop=stop_after_attempt(3)):
              with attempt:
                  raise Exception('My code is failing!')
      except RetryError:
          pass

In both cases, you may want to set the result to the attempt so it's available
in retry strategies like ``retry_if_result``. This can be done accessing the
``retry_state`` property:

.. testcode::

    from tenacity import AsyncRetrying, retry_if_result

    async def function():
       async for attempt in AsyncRetrying(retry=retry_if_result(lambda x: x < 3)):
           with attempt:
               result = 1  # Some complex calculation, function call, etc.
           if not attempt.retry_state.outcome.failed:
               attempt.retry_state.set_result(result)
       return result

Async and retry
~~~~~~~~~~~~~~~

Finally, ``retry`` works also on asyncio, Trio, and Tornado coroutines.
Sleeps are done asynchronously too.

.. code-block:: python

    @retry
    async def my_asyncio_function():
        await asyncio.getaddrinfo('8.8.8.8', 53)

.. code-block:: python

    @retry
    async def my_async_trio_function():
        await trio.socket.getaddrinfo('8.8.8.8', 53)

.. code-block:: python

    @retry
    async def my_async_tornado_function(http_client, url):
        await http_client.fetch(url)

You can use alternative event loops by passing the correct sleep function:

.. code-block:: python

    @retry(sleep=trio.sleep)
    async def my_async_trio_function_with_sleep():
        ...

Generators
~~~~~~~~~~

``retry`` does not support generator or async generator functions. Decorating a
generator with ``@retry`` will not retry on exceptions raised during iteration
— the decorator wraps the function call itself, which for generators simply
returns a generator object without executing any of the body.

Also note that generators passed *as arguments* to a retried function will be
exhausted after the first attempt and will not be rewound automatically on
retry. If you need to pass a generator as an argument, consider passing a
factory function instead:

.. code-block:: python

    # Bad: generator will be exhausted after the first attempt
    @retry
    def process(items):
        for item in items:
            do_work(item)

    process(my_generator())  # retries will see an empty generator

    # Good: pass a factory so a fresh generator is created on each attempt
    @retry
    def process(items_factory):
        for item in items_factory():
            do_work(item)

    process(my_generator)  # each retry gets a fresh generator

Contribute
----------

#. Check for open issues or open a fresh issue to start a discussion around a
   feature idea or a bug.
#. Fork `the repository`_ on GitHub to start making your changes to the
   **main** branch (or branch off of it).
#. Write a test which shows that the bug was fixed or that the feature works as
   expected.
#. Add a `changelog <#Changelogs>`_
#. Make the docs better (or more detailed, or more easier to read, or ...)

Running the test suite locally::

    uv run poe check    # run tests + build docs
    uv run poe lint     # run ruff linter
    uv run poe mypy     # run type checker
    uv run poe all      # run everything

.. _`the repository`: https://github.com/jd/tenacity


================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "vcs"

[tool.hatch.build.hooks.vcs]
version-file = "tenacity/_version.py"

[project]
name = "tenacity"
dynamic = ["version"]
description = "Retry code until it succeeds"
readme = "README.rst"
license = "Apache-2.0"
requires-python = ">=3.10"
authors = [
    { name = "Julien Danjou", email = "julien@danjou.info" },
]
classifiers = [
    "Intended Audience :: Developers",
    "License :: OSI Approved :: Apache Software License",
    "Programming Language :: Python",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
    "Topic :: Utilities",
]

[project.urls]
Homepage = "https://github.com/jd/tenacity"
Documentation = "https://tenacity.readthedocs.io"
Source = "https://github.com/jd/tenacity"
Issues = "https://github.com/jd/tenacity/issues"
Changelog = "https://tenacity.readthedocs.io/en/latest/changelog.html"

[project.optional-dependencies]
doc = [
    "reno",
    "sphinx",
]
test = [
    "pytest",
    "tornado>=6.0",
    "typeguard",
]

[dependency-groups]
dev = [
    "poethepoet",
    "pytest",
    "tornado>=6.0",
    "typeguard",
    "ruff",
    "mypy",
    "sphinx",
    "reno",
    "trio",
]

[tool.poe.tasks]
test = "pytest"
docs-doctest = "sphinx-build -a -E -W -b doctest doc/source doc/build"
docs-html = "sphinx-build -a -E -W -b html doc/source doc/build"
docs = ["docs-doctest", "docs-html"]
check = ["test", "docs"]
mypy = "mypy"
reno = "reno"

[tool.poe.tasks.fmt]
sequence = [
    { cmd = "ruff check --fix ." },
    { cmd = "ruff format ." },
]

[tool.poe.tasks.lint]
sequence = [
    { cmd = "ruff check ." },
    { cmd = "ruff format --check ." },
]

[tool.poe.tasks.all]
sequence = [
    { ref = "lint" },
    { ref = "mypy" },
    { ref = "check" },
]

[tool.pytest.ini_options]
filterwarnings = [
    "once::DeprecationWarning",
]

[tool.ruff]
line-length = 88
indent-width = 4
target-version = "py310"
exclude = ["tenacity/_version.py"]

[tool.ruff.lint]
select = ["ASYNC", "B", "C4", "DTZ", "E", "EXE", "F", "FLY", "FURB", "I", "ICN", "ISC", "LOG", "PERF", "PGH", "PIE", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "SLOT", "T10", "TC", "UP", "W"]
ignore = [
    "B008",  # function calls in default arguments (intentional API design)
    "B905",  # zip() without strict= (not needed in existing code)
    "E501",  # line too long (formatter handles what it can)
    "PYI036",  # false positive on string-quoted __exit__ annotations
    "RUF003",  # ambiguous unicode characters in comments (copyright names)
    "RUF005",  # iterable unpacking vs concatenation (less readable for tuple +)
    "RUF012",  # mutable class default (test constant, never mutated)
    "SIM108",  # ternary instead of if-else (less readable in context)
]

[tool.mypy]
strict = true
files = ["tenacity", "tests"]
show_error_codes = true
exclude = ["tenacity/_version\\.py"]

[[tool.mypy.overrides]]
module = "tornado.*"
ignore_missing_imports = true



================================================
FILE: releasenotes/notes/Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml
================================================
---
fixes:
  - "Fixes test failures with typeguard 3.x"


================================================
FILE: releasenotes/notes/Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml
================================================
---
other:
  - "Use `black` for code formatting and validate using `black --check`. Code compatibility: py26-py39."
  - "Enforce maximal line length to 120 symbols"


================================================
FILE: releasenotes/notes/add-async-actions-b249c527d99723bb.yaml
================================================
---
features:
  - |
    Added the ability to use async functions for retries. This way, you can now use
    asyncio coroutines for retry strategy predicates.


================================================
FILE: releasenotes/notes/add-re-pattern-to-match-types-6a4c1d9e64e2a5e1.yaml
================================================
---
fixes:
  - |
    Added `re.Pattern` to allowed match types.


================================================
FILE: releasenotes/notes/add-reno-d1ab5710f272650a.yaml
================================================
---
features:
  - Add reno (changelog system)


================================================
FILE: releasenotes/notes/add-retry_except_exception_type-31b31da1924d55f4.yaml
================================================
---
features:
  - Add ``retry_if_not_exception_type()`` that allows to retry if a raised exception doesn't match given exceptions.


================================================
FILE: releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml
================================================
---
features:
  - |
    Added a new stop function: stop_before_delay, which will stop execution
    if the next sleep time would cause overall delay to exceed the specified delay. 
    Useful for use cases where you have some upper bound on retry times that you must
    not exceed, so returning before that timeout is preferable than returning after that timeout.

================================================
FILE: releasenotes/notes/add-test-extra-55e869261b03e56d.yaml
================================================
---
other:
  - Add a \"test\" extra


================================================
FILE: releasenotes/notes/add_omitted_modules_to_import_all-2ab282f20a2c22f7.yaml
================================================
---
other:
  - Add `retry_if_exception_cause_type`and `wait_exponential_jitter` to __all__ of init.py

================================================
FILE: releasenotes/notes/add_retry_if_exception_cause_type-d16b918ace4ae0ad.yaml
================================================
---
features:
  - |
    Add a new `retry_base` class called `retry_if_exception_cause_type` that
    checks, recursively, if any of the causes of the raised exception is of a certain type.


================================================
FILE: releasenotes/notes/added_a_link_to_documentation-eefaf8f074b539f8.yaml
================================================
---
other:
  - |
    Added a link to the documentation, as code snippets are not being rendered properly
    Changed branch name to main in index.rst


================================================
FILE: releasenotes/notes/after_log-50f4d73b24ce9203.yaml
================================================
---
fixes:
  - "Fix after_log logger format: function name was used with delay formatting."


================================================
FILE: releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml
================================================
---
other:
  - Unit tests can now mock ``nap.sleep()`` for testing in all tenacity usage styles

================================================
FILE: releasenotes/notes/annotate_code-197b93130df14042.yaml
================================================
---
other:
  - Add type annotations to cover all public API.


================================================
FILE: releasenotes/notes/async-sleep-retrying-32de5866f5d041.yaml
================================================
---
fixes:
  - |
    Passing an async ``sleep`` callable (e.g. ``trio.sleep``) to ``@retry``
    now correctly uses ``AsyncRetrying``, even when the decorated function is
    synchronous. Previously, the async sleep would silently not be awaited,
    resulting in no delay between retries.


================================================
FILE: releasenotes/notes/before_sleep_log-improvements-d8149274dfb37d7c.yaml
================================================
---
features:
  - Add an ``exc_info`` option to the ``before_sleep_log()`` strategy.

================================================
FILE: releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml
================================================
---
prelude: >
    Clarify usage of `reraise` keyword argument


================================================
FILE: releasenotes/notes/dependabot-for-github-actions-4d2464f3c0928463.yaml
================================================
---
other:
  - |
    Add a Dependabot configuration submit PRs monthly (as needed)
    to keep GitHub action versions updated.


================================================
FILE: releasenotes/notes/deprecate-initial-for-multiplier-c7b4e2d9f1a83065.yaml
================================================
---
deprecations:
  - |
    The ``initial`` parameter of ``wait_exponential_jitter`` is deprecated in
    favor of ``multiplier``, for consistency with ``wait_exponential``. Passing
    ``initial`` still works but emits a ``DeprecationWarning``.
features:
  - |
    Add ``multiplier`` parameter to ``wait_exponential_jitter``, consistent
    with ``wait_exponential``.


================================================
FILE: releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml
================================================
---
other:
  - Do not package tests with tenacity.


================================================
FILE: releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml
================================================
---
other:
  - |
    Drop support for deprecated Python versions (2.7 and 3.5)


================================================
FILE: releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml
================================================
---
upgrade:
  - |
    Python 3.9 has reached end-of-life and is no longer supported.
    The minimum supported version is now Python 3.10.


================================================
FILE: releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml
================================================
---
upgrade:
  - "Removed `BaseRetrying.call`: was long time deprecated and produced `DeprecationWarning`"
  - "Removed `BaseRetrying.fn`: was noted as deprecated"
  - "API change: `BaseRetrying.begin()` do not require arguments anymore as it not setting `BaseRetrying.fn`"


================================================
FILE: releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml
================================================
---
features:
  - Explicitly export convenience symbols from tenacity root module


================================================
FILE: releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml
================================================
---
fixes:
  - |
    Fix async loop with retrying code block when result is available.


================================================
FILE: releasenotes/notes/fix-async-retry-type-overloads-27f3e0c239ed6b.yaml
================================================
---
fixes:
  - |
    The ``@retry`` decorator's type overloads for the ``sleep=`` parameter
    (e.g. ``sleep=trio.sleep``) have been improved. Previously, the
    async-sleep overload used ``R | Awaitable[R]`` as the return type
    bound, which was ambiguous: for ``async def f() -> T``, pyright could
    infer ``R = Coroutine[Any, Any, T]`` instead of ``R = T``, producing
    false-positive type errors in downstream code.


================================================
FILE: releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml
================================================
---
fixes:
  - |
    Avoid overwriting local contexts when applying the retry decorator.


================================================
FILE: releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml
================================================
---
fixes:
  - |
    Restore the value of the `retry` attribute for wrapped functions. Also,
    clarify that those attributes are write-only and statistics should be
    read from the function attribute directly.


================================================
FILE: releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml
================================================
---
fixes:
  - Fix setuptools config to include tenacity.asyncio package in release distributions.


================================================
FILE: releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml
================================================
---
fixes:
  - |
    Argument `wait` was improperly annotated, making mypy checks fail.
    Now it's annotated as `typing.Union[wait_base, typing.Callable[["RetryCallState"], typing.Union[float, int]]]`


================================================
FILE: releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml
================================================
---
fixes:
  - "Fix issue #288 : __name__ and other attributes for async functions"


================================================
FILE: releasenotes/notes/logging-protocol-a4cf0f786f21e4ee.yaml
================================================
---
other:
  - |
    Accept non-standard logger in helpers logging something (eg: structlog, loguru...)



================================================
FILE: releasenotes/notes/make-logger-more-compatible-5da1ddf1bab77047.yaml
================================================
---
fixes:
  - |
    Use str.format to format the logs internally to make logging compatible with other logger such as loguru.


================================================
FILE: releasenotes/notes/no-async-iter-6132a42e52348a75.yaml
================================================
---
fixes:
  - |
    `AsyncRetrying` was erroneously implementing `__iter__()`, making tenacity
    retrying mechanism working but in a synchronous fashion and not waiting as
    expected. This interface has been removed, `__aiter__()` should be used
    instead.


================================================
FILE: releasenotes/notes/pr320-py3-only-wheel-tag.yaml
================================================
---
other: >-
  Corrected the PyPI-published wheel tag to match the
  metadata saying that the release is Python 3 only.
...


================================================
FILE: releasenotes/notes/py36_plus-c425fb3aa17c6682.yaml
================================================
---
features:
  - Most part of the code is type annotated.
  - Python 3.10 support has been added.


================================================
FILE: releasenotes/notes/remove-py36-876c0416cf279d15.yaml
================================================
---
upgrade:
  - |
    Support for Python 3.6 has been removed.


================================================
FILE: releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml
================================================
---
features:
  - Add a ``__repr__`` method to ``RetryCallState`` objects for easier debugging.


================================================
FILE: releasenotes/notes/some-slug-for-preserve-defaults-86682846dfa18005.yaml
================================================
---
fixes:
  - |
    Preserve __defaults__ and __kwdefaults__ through retry decorator


================================================
FILE: releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml
================================================
---
fixes: Sphinx build error where Sphinx complains about an undefined class.


================================================
FILE: releasenotes/notes/support-py3.14-14928188cab53b99.yaml
================================================
---
features:
  - Python 3.14 support has been added.


================================================
FILE: releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml
================================================
---
features:
  - Add ``datetime.timedelta`` as accepted wait unit type.


================================================
FILE: releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml
================================================
---
features:
  - |
    - accept ``datetime.timedelta`` instances as argument to ``tenacity.stop.stop_after_delay``


================================================
FILE: releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml
================================================
---
features:
  - |
    If you're using `Trio <https://trio.readthedocs.io>`__, then
    ``@retry`` now works automatically. It's no longer necessary to
    pass ``sleep=trio.sleep``.


================================================
FILE: releasenotes/notes/wait-exponential-jitter-min-timedelta-a8e3c1f4b7d29e50.yaml
================================================
---
features:
  - |
    Add ``min`` parameter to ``wait_exponential_jitter`` to set a minimum wait
    time floor, consistent with ``wait_exponential``. Also accept ``timedelta``
    for ``max``, ``jitter``, and ``min`` parameters.


================================================
FILE: releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml
================================================
---
fixes:
  - |
    Respects `min` arg for `wait_random_exponential`


================================================
FILE: releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml
================================================
---
features:
  - |
    Implement a wait.wait_exponential_jitter per Google's storage retry guide.
    See https://cloud.google.com/storage/docs/retry-strategy


================================================
FILE: reno.yaml
================================================
---
unreleased_version_title: Unreleased


================================================
FILE: tenacity/__init__.py
================================================
# Copyright 2016-2018 Julien Danjou
# Copyright 2017 Elisey Zanko
# Copyright 2016 Étienne Bersac
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import dataclasses
import functools
import sys
import threading
import time
import typing as t
import warnings
from abc import ABC, abstractmethod
from concurrent import futures

from . import _utils

# Import all built-in after strategies for easier usage.
from .after import after_log, after_nothing

# Import all built-in before strategies for easier usage.
from .before import before_log, before_nothing

# Import all built-in before sleep strategies for easier usage.
from .before_sleep import before_sleep_log, before_sleep_nothing

# Import all nap strategies for easier usage.
from .nap import sleep, sleep_using_event

# Import all built-in retry strategies for easier usage.
from .retry import (
    retry_all,
    retry_always,
    retry_any,
    retry_base,
    retry_if_exception,
    retry_if_exception_cause_type,
    retry_if_exception_message,
    retry_if_exception_type,
    retry_if_not_exception_message,
    retry_if_not_exception_type,
    retry_if_not_result,
    retry_if_result,
    retry_never,
    retry_unless_exception_type,
)

# Import all built-in stop strategies for easier usage.
from .stop import (
    stop_after_attempt,
    stop_after_delay,
    stop_all,
    stop_any,
    stop_before_delay,
    stop_never,
    stop_when_event_set,
)

# Import all built-in wait strategies for easier usage.
from .wait import (
    wait_chain,
    wait_combine,
    wait_exception,
    wait_exponential,
    wait_exponential_jitter,
    wait_fixed,
    wait_incrementing,
    wait_none,
    wait_random,
    wait_random_exponential,
)
from .wait import wait_random_exponential as wait_full_jitter

try:
    import tornado
except ImportError:
    tornado = None  # type: ignore[assignment]

if t.TYPE_CHECKING:
    if sys.version_info >= (3, 11):
        from typing import Self
    else:
        from typing_extensions import Self

    import types

    from . import asyncio as tasyncio
    from .retry import RetryBaseT
    from .stop import StopBaseT
    from .wait import WaitBaseT


WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Any])
P = t.ParamSpec("P")
R = t.TypeVar("R")


@dataclasses.dataclass(slots=True)
class IterState:
    actions: list[t.Callable[["RetryCallState"], t.Any]] = dataclasses.field(
        default_factory=list
    )
    retry_run_result: bool = False
    stop_run_result: bool = False
    is_explicit_retry: bool = False

    def reset(self) -> None:
        self.actions = []
        self.retry_run_result = False
        self.stop_run_result = False
        self.is_explicit_retry = False


class TryAgain(Exception):
    """Always retry the executed function when raised."""


NO_RESULT = object()


class DoAttempt:
    pass


class DoSleep(float):
    pass


class BaseAction:
    """Base class for representing actions to take by retry object.

    Concrete implementations must define:
    - __init__: to initialize all necessary fields
    - REPR_FIELDS: class variable specifying attributes to include in repr(self)
    - NAME: for identification in retry object methods and callbacks
    """

    REPR_FIELDS: t.Sequence[str] = ()
    NAME: str | None = None

    def __repr__(self) -> str:
        state_str = ", ".join(
            f"{field}={getattr(self, field)!r}" for field in self.REPR_FIELDS
        )
        return f"{self.__class__.__name__}({state_str})"

    def __str__(self) -> str:
        return repr(self)


class RetryAction(BaseAction):
    REPR_FIELDS = ("sleep",)
    NAME = "retry"

    def __init__(self, sleep: t.SupportsFloat) -> None:
        self.sleep = float(sleep)


_unset = object()


def _first_set(first: t.Any | object, second: t.Any) -> t.Any:
    return second if first is _unset else first


class RetryError(Exception):
    """Encapsulates the last attempt instance right before giving up."""

    def __init__(self, last_attempt: "Future") -> None:
        self.last_attempt = last_attempt
        super().__init__(last_attempt)

    def reraise(self) -> t.NoReturn:
        if self.last_attempt.failed:
            raise self.last_attempt.result()
        raise self

    def __str__(self) -> str:
        return f"{self.__class__.__name__}[{self.last_attempt}]"


class AttemptManager:
    """Manage attempt context."""

    def __init__(self, retry_state: "RetryCallState"):
        self.retry_state = retry_state

    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: "types.TracebackType | None",
    ) -> bool | None:
        if exc_type is not None and exc_value is not None:
            self.retry_state.set_exception((exc_type, exc_value, traceback))
            return True  # Swallow exception.
        # We don't have the result, actually.
        self.retry_state.set_result(None)
        return None

    async def __aenter__(self) -> None:
        pass

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: "types.TracebackType | None",
    ) -> bool | None:
        return self.__exit__(exc_type, exc_value, traceback)


class BaseRetrying(ABC):
    def __init__(
        self,
        sleep: t.Callable[[int | float], None] = sleep,
        stop: "StopBaseT" = stop_never,
        wait: "WaitBaseT" = wait_none(),
        retry: "RetryBaseT" = retry_if_exception_type(),
        before: t.Callable[["RetryCallState"], None] = before_nothing,
        after: t.Callable[["RetryCallState"], None] = after_nothing,
        before_sleep: t.Callable[["RetryCallState"], None] | None = None,
        reraise: bool = False,
        retry_error_cls: type[RetryError] = RetryError,
        retry_error_callback: t.Callable[["RetryCallState"], t.Any] | None = None,
        name: str | None = None,
        enabled: bool = True,
    ):
        self.sleep = sleep
        self.stop = stop
        self.wait = wait
        self.retry = retry
        self.before = before
        self.after = after
        self.before_sleep = before_sleep
        self.reraise = reraise
        self._local = threading.local()
        self.retry_error_cls = retry_error_cls
        self.retry_error_callback = retry_error_callback
        self._name = name
        self.enabled = enabled

    def copy(
        self,
        sleep: t.Callable[[int | float], None] | object = _unset,
        stop: "StopBaseT | object" = _unset,
        wait: "WaitBaseT | object" = _unset,
        retry: retry_base | object = _unset,
        before: t.Callable[["RetryCallState"], None] | object = _unset,
        after: t.Callable[["RetryCallState"], None] | object = _unset,
        before_sleep: t.Callable[["RetryCallState"], None] | None | object = _unset,
        reraise: bool | object = _unset,
        retry_error_cls: type[RetryError] | object = _unset,
        retry_error_callback: t.Callable[["RetryCallState"], t.Any]
        | None
        | object = _unset,
        name: str | None | object = _unset,
        enabled: bool | object = _unset,
    ) -> "Self":
        """Copy this object with some parameters changed if needed."""
        return self.__class__(
            sleep=_first_set(sleep, self.sleep),
            stop=_first_set(stop, self.stop),
            wait=_first_set(wait, self.wait),
            retry=_first_set(retry, self.retry),
            before=_first_set(before, self.before),
            after=_first_set(after, self.after),
            before_sleep=_first_set(before_sleep, self.before_sleep),
            reraise=_first_set(reraise, self.reraise),
            retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls),
            retry_error_callback=_first_set(
                retry_error_callback, self.retry_error_callback
            ),
            name=_first_set(name, self._name),
            enabled=_first_set(enabled, self.enabled),
        )

    def __getstate__(self) -> dict[str, t.Any]:
        # Exclude threading.local which cannot be pickled
        return {k: v for k, v in self.__dict__.items() if k != "_local"}

    def __setstate__(self, state: dict[str, t.Any]) -> None:
        self.__dict__.update(state)
        self._local = threading.local()

    def __str__(self) -> str:
        return self._name if self._name is not None else "<unknown>"

    def __repr__(self) -> str:
        return (
            f"<{self.__class__.__name__} object at 0x{id(self):x} ("
            f"stop={self.stop}, "
            f"wait={self.wait}, "
            f"sleep={self.sleep}, "
            f"retry={self.retry}, "
            f"before={self.before}, "
            f"after={self.after}, "
            f"name={self._name!r})>"
        )

    @property
    def statistics(self) -> dict[str, t.Any]:
        """Return a dictionary of runtime statistics.

        This dictionary will be empty when the controller has never been
        ran. When it is running or has ran previously it should have (but
        may not) have useful and/or informational keys and values when
        running is underway and/or completed.

        .. warning:: The keys in this dictionary **should** be somewhat
                     stable (not changing), but their existence **may**
                     change between major releases as new statistics are
                     gathered or removed so before accessing keys ensure that
                     they actually exist and handle when they do not.

        .. note:: The values in this dictionary are local to the thread
                  running call (so if multiple threads share the same retrying
                  object - either directly or indirectly) they will each have
                  their own view of statistics they have collected (in the
                  future we may provide a way to aggregate the various
                  statistics from each thread).
        """
        if not hasattr(self._local, "statistics"):
            self._local.statistics = t.cast("dict[str, t.Any]", {})
        return self._local.statistics  # type: ignore[no-any-return]

    @property
    def iter_state(self) -> IterState:
        if not hasattr(self._local, "iter_state"):
            self._local.iter_state = IterState()
        return self._local.iter_state  # type: ignore[no-any-return]

    def wraps(self, f: t.Callable[P, R]) -> "_RetryDecorated[P, R]":
        """Wrap a function for retrying.

        :param f: A function to wrap for retrying.
        """

        @functools.wraps(
            f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
        )
        def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
            if not self.enabled:
                return f(*args, **kw)
            # Always create a copy to prevent overwriting the local contexts when
            # calling the same wrapped functions multiple times in the same stack
            copy = self.copy()
            wrapped_f.statistics = copy.statistics  # type: ignore[attr-defined]
            self._local.statistics = copy.statistics
            return copy(f, *args, **kw)

        def retry_with(*args: t.Any, **kwargs: t.Any) -> "_RetryDecorated[P, R]":
            return self.copy(*args, **kwargs).wraps(f)

        # Preserve attributes
        wrapped_f.retry = self  # type: ignore[attr-defined]
        wrapped_f.retry_with = retry_with  # type: ignore[attr-defined]
        wrapped_f.statistics = {}  # type: ignore[attr-defined]

        return t.cast("_RetryDecorated[P, R]", wrapped_f)

    def begin(self) -> None:
        self.statistics.clear()
        self.statistics["start_time"] = time.monotonic()
        self.statistics["attempt_number"] = 1
        self.statistics["idle_for"] = 0
        self.statistics["delay_since_first_attempt"] = 0

    def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
        self.iter_state.actions.append(fn)

    def _run_retry(self, retry_state: "RetryCallState") -> None:
        self.iter_state.retry_run_result = self.retry(retry_state)

    def _run_wait(self, retry_state: "RetryCallState") -> None:
        if self.wait:
            sleep = self.wait(retry_state)
        else:
            sleep = 0.0

        retry_state.upcoming_sleep = sleep

    def _run_stop(self, retry_state: "RetryCallState") -> None:
        self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
        self.iter_state.stop_run_result = self.stop(retry_state)

    def iter(self, retry_state: "RetryCallState") -> DoAttempt | DoSleep | t.Any:
        self._begin_iter(retry_state)
        result = None
        for action in self.iter_state.actions:
            result = action(retry_state)
        return result

    def _begin_iter(self, retry_state: "RetryCallState") -> None:
        self.iter_state.reset()

        fut = retry_state.outcome
        if fut is None:
            if self.before is not None:
                self._add_action_func(self.before)
            self._add_action_func(lambda rs: DoAttempt())
            return

        self.iter_state.is_explicit_retry = fut.failed and isinstance(
            fut.exception(), TryAgain
        )
        if not self.iter_state.is_explicit_retry:
            self._add_action_func(self._run_retry)
        self._add_action_func(self._post_retry_check_actions)

    def _post_retry_check_actions(self, retry_state: "RetryCallState") -> None:
        if not (self.iter_state.is_explicit_retry or self.iter_state.retry_run_result):
            self._add_action_func(lambda rs: rs.outcome.result())
            return

        if self.after is not None:
            self._add_action_func(self.after)

        self._add_action_func(self._run_wait)
        self._add_action_func(self._run_stop)
        self._add_action_func(self._post_stop_check_actions)

    def _post_stop_check_actions(self, retry_state: "RetryCallState") -> None:
        if self.iter_state.stop_run_result:
            if self.retry_error_callback:
                self._add_action_func(self.retry_error_callback)
                return

            def exc_check(rs: "RetryCallState") -> None:
                fut = t.cast("Future", rs.outcome)
                retry_exc = self.retry_error_cls(fut)
                if self.reraise:
                    retry_exc.reraise()
                raise retry_exc from fut.exception()

            self._add_action_func(exc_check)
            return

        def next_action(rs: "RetryCallState") -> None:
            sleep = rs.upcoming_sleep
            rs.next_action = RetryAction(sleep)
            rs.idle_for += sleep
            self.statistics["idle_for"] += sleep
            self.statistics["attempt_number"] += 1

        self._add_action_func(next_action)

        if self.before_sleep is not None:
            self._add_action_func(self.before_sleep)

        self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep))

    def __iter__(self) -> t.Generator[AttemptManager, None, None]:
        self.begin()

        retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
        while True:
            do = self.iter(retry_state=retry_state)
            if isinstance(do, DoAttempt):
                yield AttemptManager(retry_state=retry_state)
            elif isinstance(do, DoSleep):
                retry_state.prepare_for_next_attempt()
                self.sleep(do)
            else:
                break

    @abstractmethod
    def __call__(
        self,
        fn: t.Callable[..., WrappedFnReturnT],
        *args: t.Any,
        **kwargs: t.Any,
    ) -> WrappedFnReturnT:
        pass


class Retrying(BaseRetrying):
    """Retrying controller."""

    def __call__(
        self,
        fn: t.Callable[..., WrappedFnReturnT],
        *args: t.Any,
        **kwargs: t.Any,
    ) -> WrappedFnReturnT:
        self.begin()

        retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
        while True:
            do = self.iter(retry_state=retry_state)
            if isinstance(do, DoAttempt):
                try:
                    result = fn(*args, **kwargs)
                except BaseException:
                    retry_state.set_exception(sys.exc_info())  # type: ignore[arg-type]
                else:
                    retry_state.set_result(result)
            elif isinstance(do, DoSleep):
                retry_state.prepare_for_next_attempt()
                self.sleep(do)
            else:
                return do  # type: ignore[no-any-return]


class Future(futures.Future[t.Any]):
    """Encapsulates a (future or past) attempted call to a target function."""

    def __init__(self, attempt_number: int) -> None:
        super().__init__()
        self.attempt_number = attempt_number

    @property
    def failed(self) -> bool:
        """Return whether a exception is being held in this future."""
        return self.exception() is not None

    @classmethod
    def construct(
        cls, attempt_number: int, value: t.Any, has_exception: bool
    ) -> "Future":
        """Construct a new Future object."""
        fut = cls(attempt_number)
        if has_exception:
            fut.set_exception(value)
        else:
            fut.set_result(value)
        return fut


class RetryCallState:
    """State related to a single call wrapped with Retrying."""

    def __init__(
        self,
        retry_object: BaseRetrying,
        fn: WrappedFn | None,
        args: t.Any,
        kwargs: t.Any,
    ) -> None:
        #: Retry call start timestamp
        self.start_time = time.monotonic()
        #: Retry manager object
        self.retry_object = retry_object
        #: Function wrapped by this retry call
        self.fn = fn
        #: Arguments of the function wrapped by this retry call
        self.args = args
        #: Keyword arguments of the function wrapped by this retry call
        self.kwargs = kwargs

        #: The number of the current attempt
        self.attempt_number: int = 1
        #: Last outcome (result or exception) produced by the function
        self.outcome: Future | None = None
        #: Timestamp of the last outcome
        self.outcome_timestamp: float | None = None
        #: Time spent sleeping in retries
        self.idle_for: float = 0.0
        #: Next action as decided by the retry manager
        self.next_action: RetryAction | None = None
        #: Next sleep time as decided by the retry manager.
        self.upcoming_sleep: float = 0.0

    def get_fn_name(self) -> str:
        """Get the name of the function being retried.

        Returns the fully-qualified name of the wrapped function when used as a
        decorator, the ``name`` passed to the retrying object when used as a
        context manager, or ``"<unknown>"`` if neither is available.
        """
        if self.fn is not None:
            return _utils.get_callback_name(self.fn)
        return str(self.retry_object)

    @property
    def seconds_since_start(self) -> float | None:
        if self.outcome_timestamp is None:
            return None
        return self.outcome_timestamp - self.start_time

    def prepare_for_next_attempt(self) -> None:
        self.outcome = None
        self.outcome_timestamp = None
        self.attempt_number += 1
        self.next_action = None

    def set_result(self, val: t.Any) -> None:
        ts = time.monotonic()
        fut = Future(self.attempt_number)
        fut.set_result(val)
        self.outcome, self.outcome_timestamp = fut, ts

    def set_exception(
        self,
        exc_info: tuple[
            type[BaseException], BaseException, "types.TracebackType | None"
        ],
    ) -> None:
        ts = time.monotonic()
        fut = Future(self.attempt_number)
        fut.set_exception(exc_info[1])
        self.outcome, self.outcome_timestamp = fut, ts

    def __repr__(self) -> str:
        if self.outcome is None:
            result = "none yet"
        elif self.outcome.failed:
            exception = self.outcome.exception()
            result = f"failed ({exception.__class__.__name__} {exception})"
        else:
            result = f"returned {self.outcome.result()}"

        slept = float(round(self.idle_for, 2))
        clsname = self.__class__.__name__
        return f"<{clsname} {id(self)}: attempt #{self.attempt_number}; slept for {slept}; last result: {result}>"


class _RetryDecorated(t.Protocol[P, R]):
    """Protocol for functions decorated with @retry.

    Provides the original callable signature plus retry control attributes.
    """

    retry: "BaseRetrying"
    statistics: dict[str, t.Any]

    def retry_with(self, *args: t.Any, **kwargs: t.Any) -> "_RetryDecorated[P, R]": ...

    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ...


class _AsyncRetryDecorator(t.Protocol):
    @t.overload
    def __call__(
        self, fn: "t.Callable[P, types.CoroutineType[t.Any, t.Any, R]]"
    ) -> "_RetryDecorated[P, types.CoroutineType[t.Any, t.Any, R]]": ...
    @t.overload
    def __call__(
        self, fn: t.Callable[P, t.Coroutine[t.Any, t.Any, R]]
    ) -> "_RetryDecorated[P, t.Coroutine[t.Any, t.Any, R]]": ...
    @t.overload
    def __call__(
        self, fn: t.Callable[P, t.Awaitable[R]]
    ) -> "_RetryDecorated[P, t.Awaitable[R]]": ...
    @t.overload
    def __call__(
        self, fn: t.Callable[P, R]
    ) -> "_RetryDecorated[P, t.Awaitable[R]]": ...


@t.overload
def retry(func: t.Callable[P, R]) -> _RetryDecorated[P, R]: ...


@t.overload
def retry(
    *,
    sleep: t.Callable[[int | float], t.Awaitable[None]],
    stop: "StopBaseT" = ...,
    wait: "WaitBaseT" = ...,
    retry: "RetryBaseT | tasyncio.retry.RetryBaseT" = ...,
    before: t.Callable[["RetryCallState"], None | t.Awaitable[None]] = ...,
    after: t.Callable[["RetryCallState"], None | t.Awaitable[None]] = ...,
    before_sleep: t.Callable[["RetryCallState"], None | t.Awaitable[None]] | None = ...,
    reraise: bool = ...,
    retry_error_cls: type["RetryError"] = ...,
    retry_error_callback: t.Callable[["RetryCallState"], t.Any | t.Awaitable[t.Any]]
    | None = ...,
    enabled: bool = ...,
) -> _AsyncRetryDecorator: ...


@t.overload
def retry(
    sleep: t.Callable[[int | float], None] = sleep,
    stop: "StopBaseT" = stop_never,
    wait: "WaitBaseT" = wait_none(),
    retry: "RetryBaseT | tasyncio.retry.RetryBaseT" = retry_if_exception_type(),
    before: t.Callable[["RetryCallState"], None | t.Awaitable[None]] = before_nothing,
    after: t.Callable[["RetryCallState"], None | t.Awaitable[None]] = after_nothing,
    before_sleep: t.Callable[["RetryCallState"], None | t.Awaitable[None]]
    | None = None,
    reraise: bool = False,
    retry_error_cls: type["RetryError"] = RetryError,
    retry_error_callback: t.Callable[["RetryCallState"], t.Any | t.Awaitable[t.Any]]
    | None = None,
    enabled: bool = True,
) -> t.Callable[[t.Callable[P, R]], _RetryDecorated[P, R]]: ...


def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:
    """Wrap a function with a new `Retrying` object.

    :param dargs: positional arguments passed to Retrying object
    :param dkw: keyword arguments passed to the Retrying object
    """
    # support both @retry and @retry() as valid syntax
    if len(dargs) == 1 and callable(dargs[0]):
        return retry()(dargs[0])

    def wrap(f: t.Callable[P, R]) -> _RetryDecorated[P, R]:
        if isinstance(f, retry_base):
            warnings.warn(
                f"Got retry_base instance ({f.__class__.__name__}) as callable argument, "
                f"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)",
                stacklevel=2,
            )
        r: BaseRetrying
        sleep = dkw.get("sleep")
        if _utils.is_coroutine_callable(f) or (
            sleep is not None and _utils.is_coroutine_callable(sleep)
        ):
            r = AsyncRetrying(*dargs, **dkw)
        elif (
            tornado
            and hasattr(tornado.gen, "is_coroutine_function")
            and tornado.gen.is_coroutine_function(f)
        ):
            r = TornadoRetrying(*dargs, **dkw)
        else:
            r = Retrying(*dargs, **dkw)

        return r.wraps(f)

    return wrap


from tenacity.asyncio import AsyncRetrying  # noqa: E402

if tornado:
    from tenacity.tornadoweb import TornadoRetrying


__all__ = [
    "NO_RESULT",
    "AsyncRetrying",
    "AttemptManager",
    "BaseAction",
    "BaseRetrying",
    "DoAttempt",
    "DoSleep",
    "Future",
    "RetryAction",
    "RetryCallState",
    "RetryError",
    "Retrying",
    "TryAgain",
    "WrappedFn",
    "after_log",
    "after_nothing",
    "before_log",
    "before_nothing",
    "before_sleep_log",
    "before_sleep_nothing",
    "retry",
    "retry_all",
    "retry_always",
    "retry_any",
    "retry_base",
    "retry_if_exception",
    "retry_if_exception_cause_type",
    "retry_if_exception_message",
    "retry_if_exception_type",
    "retry_if_not_exception_message",
    "retry_if_not_exception_type",
    "retry_if_not_result",
    "retry_if_result",
    "retry_never",
    "retry_unless_exception_type",
    "sleep",
    "sleep_using_event",
    "stop_after_attempt",
    "stop_after_delay",
    "stop_all",
    "stop_any",
    "stop_before_delay",
    "stop_never",
    "stop_when_event_set",
    "wait_chain",
    "wait_combine",
    "wait_exception",
    "wait_exponential",
    "wait_exponential_jitter",
    "wait_fixed",
    "wait_full_jitter",
    "wait_incrementing",
    "wait_none",
    "wait_random",
    "wait_random_exponential",
]


================================================
FILE: tenacity/_utils.py
================================================
# Copyright 2016 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import contextlib
import functools
import inspect
import sys
import typing
from datetime import timedelta

# sys.maxsize:
# An integer giving the maximum value a variable of type Py_ssize_t can take.
MAX_WAIT = sys.maxsize / 2


class LoggerProtocol(typing.Protocol):
    """
    Protocol used by utils expecting a logger (eg: before_log).

    Compatible with logging, structlog, loguru, etc...
    """

    def log(self, level: int, msg: str, *args: typing.Any) -> typing.Any: ...


def find_ordinal(pos_num: int) -> str:
    # See: https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers

    if 11 <= (pos_num % 100) <= 13:
        return "th"

    if pos_num == 0:
        return "th"
    if pos_num == 1:
        return "st"
    if pos_num == 2:
        return "nd"
    if pos_num == 3:
        return "rd"
    if 4 <= pos_num <= 20:
        return "th"
    return find_ordinal(pos_num % 10)


def to_ordinal(pos_num: int) -> str:
    return f"{pos_num}{find_ordinal(pos_num)}"


def get_callback_name(cb: typing.Callable[..., typing.Any]) -> str:
    """Get a callback fully-qualified name.

    If no name can be produced ``repr(cb)`` is called and returned.
    """
    segments = []
    try:
        segments.append(cb.__qualname__)
    except AttributeError:
        with contextlib.suppress(AttributeError):
            segments.append(cb.__name__)
    if not segments:
        return repr(cb)
    with contextlib.suppress(AttributeError):
        # When running under sphinx it appears this can be none?
        if cb.__module__:
            segments.insert(0, cb.__module__)
    return ".".join(segments)


time_unit_type = int | float | timedelta


def to_seconds(time_unit: time_unit_type) -> float:
    return float(
        time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit
    )


def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool:
    if inspect.isclass(call):
        return False
    if inspect.iscoroutinefunction(call):
        return True
    partial_call = isinstance(call, functools.partial) and call.func
    dunder_call = partial_call or getattr(call, "__call__", None)  # noqa: B004
    return inspect.iscoroutinefunction(dunder_call)


def wrap_to_async_func(
    call: typing.Callable[..., typing.Any],
) -> typing.Callable[..., typing.Awaitable[typing.Any]]:
    if is_coroutine_callable(call):
        return call

    async def inner(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
        return call(*args, **kwargs)

    return inner


================================================
FILE: tenacity/after.py
================================================
# Copyright 2016 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import typing

from tenacity import _utils

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


def after_nothing(retry_state: "RetryCallState") -> None:
    """After call strategy that does nothing."""


def after_log(
    logger: _utils.LoggerProtocol,
    log_level: int,
    sec_format: str = "%.3g",
) -> typing.Callable[["RetryCallState"], None]:
    """After call strategy that logs to some logger the finished attempt."""

    def log_it(retry_state: "RetryCallState") -> None:
        fn_name = retry_state.get_fn_name()
        secs = retry_state.seconds_since_start
        logger.log(
            log_level,
            f"Finished call to '{fn_name}' "
            f"after {sec_format % secs if secs is not None else '?'}(s), "
            f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
        )

    return log_it


================================================
FILE: tenacity/asyncio/__init__.py
================================================
# Copyright 2016 Étienne Bersac
# Copyright 2016 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import functools
import sys
import typing as t

import tenacity
from tenacity import (
    AttemptManager,
    BaseRetrying,
    DoAttempt,
    DoSleep,
    RetryCallState,
    RetryError,
    _RetryDecorated,
    _utils,
    after_nothing,
    before_nothing,
)

# Import all built-in retry strategies for easier usage.
from .retry import (
    RetryBaseT,
    retry_all,
    retry_any,
    retry_if_exception,
    retry_if_result,
)

if t.TYPE_CHECKING:
    from tenacity.retry import RetryBaseT as SyncRetryBaseT
    from tenacity.stop import StopBaseT
    from tenacity.wait import WaitBaseT

WrappedFnReturnT = t.TypeVar("WrappedFnReturnT")
WrappedFn = t.TypeVar("WrappedFn", bound=t.Callable[..., t.Awaitable[t.Any]])
P = t.ParamSpec("P")
R = t.TypeVar("R")


def _portable_async_sleep(seconds: float) -> t.Awaitable[None]:
    # If trio is already imported, then importing it is cheap.
    # If trio isn't already imported, then it's definitely not running, so we
    # can skip further checks.
    if "trio" in sys.modules:
        # If trio is available, then sniffio is too
        import sniffio
        import trio

        if sniffio.current_async_library() == "trio":
            return trio.sleep(seconds)  # noqa: ASYNC105
    # Otherwise, assume asyncio
    # Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead).
    import asyncio

    return asyncio.sleep(seconds)


class AsyncRetrying(BaseRetrying):
    def __init__(
        self,
        sleep: t.Callable[
            [int | float], None | t.Awaitable[None]
        ] = _portable_async_sleep,
        stop: "StopBaseT" = tenacity.stop.stop_never,
        wait: "WaitBaseT" = tenacity.wait.wait_none(),
        retry: "SyncRetryBaseT | RetryBaseT" = tenacity.retry_if_exception_type(),
        before: t.Callable[
            ["RetryCallState"], None | t.Awaitable[None]
        ] = before_nothing,
        after: t.Callable[["RetryCallState"], None | t.Awaitable[None]] = after_nothing,
        before_sleep: t.Callable[["RetryCallState"], None | t.Awaitable[None]]
        | None = None,
        reraise: bool = False,
        retry_error_cls: type["RetryError"] = RetryError,
        retry_error_callback: t.Callable[["RetryCallState"], t.Any | t.Awaitable[t.Any]]
        | None = None,
        name: str | None = None,
        enabled: bool = True,
    ) -> None:
        super().__init__(
            sleep=sleep,  # type: ignore[arg-type]
            stop=stop,
            wait=wait,
            retry=retry,  # type: ignore[arg-type]
            before=before,  # type: ignore[arg-type]
            after=after,  # type: ignore[arg-type]
            before_sleep=before_sleep,  # type: ignore[arg-type]
            reraise=reraise,
            retry_error_cls=retry_error_cls,
            retry_error_callback=retry_error_callback,
            name=name,
            enabled=enabled,
        )

    async def __call__(  # type: ignore[override]
        self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any
    ) -> WrappedFnReturnT:
        self.begin()

        retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
        is_async = _utils.is_coroutine_callable(fn)
        while True:
            do = await self.iter(retry_state=retry_state)
            if isinstance(do, DoAttempt):
                try:
                    if is_async:
                        result = await fn(*args, **kwargs)
                    else:
                        result = fn(*args, **kwargs)
                except BaseException:
                    retry_state.set_exception(sys.exc_info())  # type: ignore[arg-type]
                else:
                    retry_state.set_result(result)
            elif isinstance(do, DoSleep):
                retry_state.prepare_for_next_attempt()
                await self.sleep(do)  # type: ignore[misc]
            else:
                return do  # type: ignore[no-any-return]

    def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
        self.iter_state.actions.append(_utils.wrap_to_async_func(fn))

    async def _run_retry(self, retry_state: "RetryCallState") -> None:  # type: ignore[override]
        self.iter_state.retry_run_result = await _utils.wrap_to_async_func(self.retry)(
            retry_state
        )

    async def _run_wait(self, retry_state: "RetryCallState") -> None:  # type: ignore[override]
        if self.wait:
            sleep = await _utils.wrap_to_async_func(self.wait)(retry_state)
        else:
            sleep = 0.0

        retry_state.upcoming_sleep = sleep

    async def _run_stop(self, retry_state: "RetryCallState") -> None:  # type: ignore[override]
        self.statistics["delay_since_first_attempt"] = retry_state.seconds_since_start
        self.iter_state.stop_run_result = await _utils.wrap_to_async_func(self.stop)(
            retry_state
        )

    async def iter(self, retry_state: "RetryCallState") -> DoAttempt | DoSleep | t.Any:
        self._begin_iter(retry_state)
        result = None
        for action in self.iter_state.actions:
            result = await action(retry_state)
        return result

    def __iter__(self) -> t.Generator[AttemptManager, None, None]:
        raise TypeError("AsyncRetrying object is not iterable")

    def __aiter__(self) -> "AsyncRetrying":
        self.begin()
        self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={})
        return self

    async def __anext__(self) -> AttemptManager:
        while True:
            do = await self.iter(retry_state=self._retry_state)
            if do is None:
                raise StopAsyncIteration
            if isinstance(do, DoAttempt):
                return AttemptManager(retry_state=self._retry_state)
            if isinstance(do, DoSleep):
                self._retry_state.prepare_for_next_attempt()
                await self.sleep(do)  # type: ignore[misc]
            else:
                raise StopAsyncIteration

    def wraps(self, fn: t.Callable[P, R]) -> _RetryDecorated[P, R]:
        wrapped = super().wraps(fn)
        # Ensure wrapper is recognized as a coroutine function.

        @functools.wraps(
            fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__")
        )
        async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
            if not self.enabled:
                return await fn(*args, **kwargs)  # type: ignore[misc]
            # Always create a copy to prevent overwriting the local contexts when
            # calling the same wrapped functions multiple times in the same stack
            copy = self.copy()
            async_wrapped.statistics = copy.statistics  # type: ignore[attr-defined]
            self._local.statistics = copy.statistics
            return await copy(fn, *args, **kwargs)  # type: ignore[type-var]

        # Preserve attributes
        async_wrapped.retry = self  # type: ignore[attr-defined]
        async_wrapped.retry_with = wrapped.retry_with  # type: ignore[attr-defined]
        async_wrapped.statistics = {}  # type: ignore[attr-defined]

        return t.cast("_RetryDecorated[P, R]", async_wrapped)


__all__ = [
    "AsyncRetrying",
    "WrappedFn",
    "retry_all",
    "retry_any",
    "retry_if_exception",
    "retry_if_result",
]


================================================
FILE: tenacity/asyncio/retry.py
================================================
# Copyright 2016–2021 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import typing

from tenacity import _utils, retry_base

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


class async_retry_base(retry_base):
    """Abstract base class for async retry strategies."""

    @abc.abstractmethod
    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        pass

    def __and__(  # type: ignore[override]
        self, other: "retry_base | async_retry_base"
    ) -> "retry_all":
        return retry_all(self, other)

    def __rand__(  # type: ignore[misc,override]
        self, other: "retry_base | async_retry_base"
    ) -> "retry_all":
        return retry_all(other, self)

    def __or__(  # type: ignore[override]
        self, other: "retry_base | async_retry_base"
    ) -> "retry_any":
        return retry_any(self, other)

    def __ror__(  # type: ignore[misc,override]
        self, other: "retry_base | async_retry_base"
    ) -> "retry_any":
        return retry_any(other, self)


RetryBaseT = (
    async_retry_base | typing.Callable[["RetryCallState"], typing.Awaitable[bool]]
)


class retry_if_exception(async_retry_base):
    """Retry strategy that retries if an exception verifies a predicate."""

    def __init__(
        self, predicate: typing.Callable[[BaseException], typing.Awaitable[bool]]
    ) -> None:
        self.predicate = predicate

    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if retry_state.outcome.failed:
            exception = retry_state.outcome.exception()
            if exception is None:
                raise RuntimeError("outcome failed but the exception is None")
            return await self.predicate(exception)
        return False


class retry_if_result(async_retry_base):
    """Retries if the result verifies a predicate."""

    def __init__(
        self, predicate: typing.Callable[[typing.Any], typing.Awaitable[bool]]
    ) -> None:
        self.predicate = predicate

    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if not retry_state.outcome.failed:
            return await self.predicate(retry_state.outcome.result())
        return False


class retry_any(async_retry_base):
    """Retries if any of the retries condition is valid."""

    def __init__(self, *retries: retry_base | async_retry_base) -> None:
        self.retries = retries

    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        result = False
        for r in self.retries:
            result = result or await _utils.wrap_to_async_func(r)(retry_state)
            if result:
                break
        return result

    def __ror__(  # type: ignore[misc,override]
        self, other: "retry_base | async_retry_base"
    ) -> "retry_any":
        if isinstance(other, retry_any):
            return retry_any(*other.retries, *self.retries)
        return retry_any(other, *self.retries)


class retry_all(async_retry_base):
    """Retries if all the retries condition are valid."""

    def __init__(self, *retries: retry_base | async_retry_base) -> None:
        self.retries = retries

    async def __call__(self, retry_state: "RetryCallState") -> bool:  # type: ignore[override]
        result = True
        for r in self.retries:
            result = result and await _utils.wrap_to_async_func(r)(retry_state)
            if not result:
                break
        return result

    def __rand__(  # type: ignore[misc,override]
        self, other: "retry_base | async_retry_base"
    ) -> "retry_all":
        if isinstance(other, retry_all):
            return retry_all(*other.retries, *self.retries)
        return retry_all(other, *self.retries)


================================================
FILE: tenacity/before.py
================================================
# Copyright 2016 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import typing

from tenacity import _utils

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


def before_nothing(retry_state: "RetryCallState") -> None:
    """Before call strategy that does nothing."""


def before_log(
    logger: _utils.LoggerProtocol, log_level: int
) -> typing.Callable[["RetryCallState"], None]:
    """Before call strategy that logs to some logger the attempt."""

    def log_it(retry_state: "RetryCallState") -> None:
        fn_name = retry_state.get_fn_name()
        logger.log(
            log_level,
            f"Starting call to '{fn_name}', "
            f"this is the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
        )

    return log_it


================================================
FILE: tenacity/before_sleep.py
================================================
# Copyright 2016 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import traceback
import typing

from tenacity import _utils

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


def before_sleep_nothing(retry_state: "RetryCallState") -> None:
    """Before sleep strategy that does nothing."""


def before_sleep_log(
    logger: _utils.LoggerProtocol,
    log_level: int,
    exc_info: bool = False,
    sec_format: str = "%.3g",
) -> typing.Callable[["RetryCallState"], None]:
    """Before sleep strategy that logs to some logger the attempt."""

    def log_it(retry_state: "RetryCallState") -> None:
        if retry_state.outcome is None:
            raise RuntimeError("log_it() called before outcome was set")

        if retry_state.next_action is None:
            raise RuntimeError("log_it() called before next_action was set")

        if retry_state.outcome.failed:
            ex = retry_state.outcome.exception()
            verb, value = "raised", f"{ex.__class__.__name__}: {ex}"
        else:
            verb, value = "returned", retry_state.outcome.result()

        fn_name = retry_state.get_fn_name()

        msg = (
            f"Retrying {fn_name} "
            f"in {sec_format % retry_state.next_action.sleep} seconds as it {verb} {value}."
        )

        if exc_info and retry_state.outcome.failed:
            ex = retry_state.outcome.exception()
            if ex is not None:
                tb = "".join(traceback.format_exception(type(ex), ex, ex.__traceback__))
                msg = f"{msg}\n{tb.rstrip()}"

        logger.log(log_level, msg)

    return log_it


================================================
FILE: tenacity/nap.py
================================================
# Copyright 2016 Étienne Bersac
# Copyright 2016 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import time
import typing

if typing.TYPE_CHECKING:
    import threading


def sleep(seconds: float) -> None:
    """
    Sleep strategy that delays execution for a given number of seconds.

    This is the default strategy, and may be mocked out for unit testing.
    """
    time.sleep(seconds)


class sleep_using_event:
    """Sleep strategy that waits on an event to be set."""

    def __init__(self, event: "threading.Event") -> None:
        self.event = event

    def __call__(self, timeout: float | None) -> None:
        # NOTE(harlowja): this may *not* actually wait for timeout
        # seconds if the event is set (ie this may eject out early).
        self.event.wait(timeout=timeout)


================================================
FILE: tenacity/py.typed
================================================


================================================
FILE: tenacity/retry.py
================================================
# Copyright 2016–2021 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import re
import typing

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


class retry_base(abc.ABC):
    """Abstract base class for retry strategies."""

    @abc.abstractmethod
    def __call__(self, retry_state: "RetryCallState") -> bool:
        pass

    def __and__(self, other: "RetryBaseT") -> "retry_all":
        if isinstance(other, retry_base):
            return other.__rand__(self)
        # Plain callable: flatten if self is already a retry_all
        if isinstance(self, retry_all):
            return retry_all(*self.retries, other)
        return retry_all(self, other)

    def __rand__(self, other: "RetryBaseT") -> "retry_all":
        # Flatten if other is already a retry_all
        if isinstance(other, retry_all):
            return retry_all(*other.retries, self)
        return retry_all(other, self)

    def __or__(self, other: "RetryBaseT") -> "retry_any":
        if isinstance(other, retry_base):
            return other.__ror__(self)
        # Plain callable: flatten if self is already a retry_any
        if isinstance(self, retry_any):
            return retry_any(*self.retries, other)
        return retry_any(self, other)

    def __ror__(self, other: "RetryBaseT") -> "retry_any":
        # Flatten if other is already a retry_any
        if isinstance(other, retry_any):
            return retry_any(*other.retries, self)
        return retry_any(other, self)


RetryBaseT = retry_base | typing.Callable[["RetryCallState"], bool]


class _retry_never(retry_base):
    """Retry strategy that never rejects any result."""

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return False


retry_never = _retry_never()


class _retry_always(retry_base):
    """Retry strategy that always rejects any result."""

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return True


retry_always = _retry_always()


class retry_if_exception(retry_base):
    """Retry strategy that retries if an exception verifies a predicate."""

    def __init__(self, predicate: typing.Callable[[BaseException], bool]) -> None:
        self.predicate = predicate

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if retry_state.outcome.failed:
            exception = retry_state.outcome.exception()
            if exception is None:
                raise RuntimeError("outcome failed but the exception is None")
            return self.predicate(exception)
        return False


class retry_if_exception_type(retry_if_exception):
    """Retries if an exception has been raised of one or more types."""

    def __init__(
        self,
        exception_types: type[BaseException]
        | tuple[type[BaseException], ...] = Exception,
    ) -> None:
        self.exception_types = exception_types
        super().__init__(self._check)

    def _check(self, e: BaseException) -> bool:
        return isinstance(e, self.exception_types)


class retry_if_not_exception_type(retry_if_exception):
    """Retries except an exception has been raised of one or more types."""

    def __init__(
        self,
        exception_types: type[BaseException]
        | tuple[type[BaseException], ...] = Exception,
    ) -> None:
        self.exception_types = exception_types
        super().__init__(self._check)

    def _check(self, e: BaseException) -> bool:
        return not isinstance(e, self.exception_types)


class retry_unless_exception_type(retry_if_exception):
    """Retries until an exception is raised of one or more types."""

    def __init__(
        self,
        exception_types: type[BaseException]
        | tuple[type[BaseException], ...] = Exception,
    ) -> None:
        self.exception_types = exception_types
        super().__init__(self._check)

    def _check(self, e: BaseException) -> bool:
        return not isinstance(e, self.exception_types)

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        # always retry if no exception was raised
        if not retry_state.outcome.failed:
            return True

        exception = retry_state.outcome.exception()
        if exception is None:
            raise RuntimeError("outcome failed but the exception is None")
        return self.predicate(exception)


class retry_if_exception_cause_type(retry_base):
    """Retries if any of the causes of the raised exception is of one or more types.

    The check on the type of the cause of the exception is done recursively (until finding
    an exception in the chain that has no `__cause__`)
    """

    def __init__(
        self,
        exception_types: type[BaseException]
        | tuple[type[BaseException], ...] = Exception,
    ) -> None:
        self.exception_cause_types = exception_types

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__ called before outcome was set")

        if retry_state.outcome.failed:
            exc = retry_state.outcome.exception()
            while exc is not None:
                if isinstance(exc.__cause__, self.exception_cause_types):
                    return True
                exc = exc.__cause__

        return False


class retry_if_result(retry_base):
    """Retries if the result verifies a predicate."""

    def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None:
        self.predicate = predicate

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if not retry_state.outcome.failed:
            return self.predicate(retry_state.outcome.result())
        return False


class retry_if_not_result(retry_base):
    """Retries if the result refutes a predicate."""

    def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None:
        self.predicate = predicate

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if not retry_state.outcome.failed:
            return not self.predicate(retry_state.outcome.result())
        return False


class retry_if_exception_message(retry_if_exception):
    """Retries if an exception message equals or matches."""

    def __init__(
        self,
        message: str | None = None,
        match: None | str | re.Pattern[str] = None,
    ) -> None:
        if message and match:
            raise TypeError(
                f"{self.__class__.__name__}() takes either 'message' or 'match', not both"
            )

        if not message and not match:
            raise TypeError(
                f"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'"
            )

        self.message = message
        self.match = re.compile(match) if match else None
        super().__init__(self._check)

    def _check(self, exception: BaseException) -> bool:
        if self.message:
            return self.message == str(exception)
        assert self.match is not None
        return bool(self.match.match(str(exception)))


class retry_if_not_exception_message(retry_if_exception_message):
    """Retries until an exception message equals or matches."""

    def _check(self, exception: BaseException) -> bool:
        return not super()._check(exception)

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        if not retry_state.outcome.failed:
            return True

        exception = retry_state.outcome.exception()
        if exception is None:
            raise RuntimeError("outcome failed but the exception is None")
        return self.predicate(exception)


class retry_any(retry_base):
    """Retries if any of the retries condition is valid."""

    def __init__(self, *retries: "RetryBaseT") -> None:
        self.retries = retries

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return any(r(retry_state) for r in self.retries)

    def __ror__(self, other: "RetryBaseT") -> "retry_any":
        if isinstance(other, retry_any):
            return retry_any(*other.retries, *self.retries)
        return retry_any(other, *self.retries)


class retry_all(retry_base):
    """Retries if all the retries condition are valid."""

    def __init__(self, *retries: "RetryBaseT") -> None:
        self.retries = retries

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return all(r(retry_state) for r in self.retries)

    def __rand__(self, other: "RetryBaseT") -> "retry_all":
        if isinstance(other, retry_all):
            return retry_all(*other.retries, *self.retries)
        return retry_all(other, *self.retries)


================================================
FILE: tenacity/stop.py
================================================
# Copyright 2016–2021 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import typing

from tenacity import _utils

if typing.TYPE_CHECKING:
    import threading

    from tenacity import RetryCallState


class stop_base(abc.ABC):
    """Abstract base class for stop strategies."""

    @abc.abstractmethod
    def __call__(self, retry_state: "RetryCallState") -> bool:
        pass

    def __and__(self, other: "stop_base") -> "stop_all":
        return stop_all(self, other)

    def __or__(self, other: "stop_base") -> "stop_any":
        return stop_any(self, other)


StopBaseT = stop_base | typing.Callable[["RetryCallState"], bool]


class stop_any(stop_base):
    """Stop if any of the stop condition is valid."""

    def __init__(self, *stops: stop_base) -> None:
        self.stops = stops

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return any(x(retry_state) for x in self.stops)


class stop_all(stop_base):
    """Stop if all the stop conditions are valid."""

    def __init__(self, *stops: stop_base) -> None:
        self.stops = stops

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return all(x(retry_state) for x in self.stops)


class _stop_never(stop_base):
    """Never stop."""

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return False


stop_never = _stop_never()


class stop_when_event_set(stop_base):
    """Stop when the given event is set."""

    def __init__(self, event: "threading.Event") -> None:
        self.event = event

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return self.event.is_set()


class stop_after_attempt(stop_base):
    """Stop when the previous attempt >= max_attempt."""

    def __init__(self, max_attempt_number: int) -> None:
        self.max_attempt_number = max_attempt_number

    def __call__(self, retry_state: "RetryCallState") -> bool:
        return retry_state.attempt_number >= self.max_attempt_number


class stop_after_delay(stop_base):
    """
    Stop when the time from the first attempt >= limit.

    Note: `max_delay` will be exceeded, so when used with a `wait`, the actual total delay will be greater
    than `max_delay` by some of the final sleep period before `max_delay` is exceeded.

    If you need stricter timing with waits, consider `stop_before_delay` instead.
    """

    def __init__(self, max_delay: _utils.time_unit_type) -> None:
        self.max_delay = _utils.to_seconds(max_delay)

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.seconds_since_start is None:
            raise RuntimeError("__call__() called but seconds_since_start is not set")
        return retry_state.seconds_since_start >= self.max_delay


class stop_before_delay(stop_base):
    """
    Stop right before the next attempt would take place after the time from the first attempt >= limit.

    Most useful when you are using with a `wait` function like wait_random_exponential, but need to make
    sure that the max_delay is not exceeded.
    """

    def __init__(self, max_delay: _utils.time_unit_type) -> None:
        self.max_delay = _utils.to_seconds(max_delay)

    def __call__(self, retry_state: "RetryCallState") -> bool:
        if retry_state.seconds_since_start is None:
            raise RuntimeError("__call__() called but seconds_since_start is not set")
        return (
            retry_state.seconds_since_start + retry_state.upcoming_sleep
            >= self.max_delay
        )


================================================
FILE: tenacity/tornadoweb.py
================================================
# Copyright 2017 Elisey Zanko
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import sys
import typing

from tornado import gen

from tenacity import BaseRetrying, DoAttempt, DoSleep, RetryCallState

if typing.TYPE_CHECKING:
    from tornado.concurrent import Future

_RetValT = typing.TypeVar("_RetValT")


class TornadoRetrying(BaseRetrying):
    sleep: typing.Callable[..., "Future[None]"]

    def __init__(
        self,
        sleep: "typing.Callable[[float], Future[None]]" = gen.sleep,
        **kwargs: typing.Any,
    ) -> None:
        super().__init__(**kwargs)
        self.sleep = sleep

    @gen.coroutine
    def __call__(  # type: ignore[override]
        self,
        fn: "typing.Callable[..., typing.Generator[typing.Any, typing.Any, _RetValT] | Future[_RetValT]]",
        *args: typing.Any,
        **kwargs: typing.Any,
    ) -> "typing.Generator[typing.Any, typing.Any, _RetValT]":
        self.begin()

        retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)
        while True:
            do = self.iter(retry_state=retry_state)
            if isinstance(do, DoAttempt):
                try:
                    result = yield fn(*args, **kwargs)
                except BaseException:
                    retry_state.set_exception(sys.exc_info())  # type: ignore[arg-type]
                else:
                    retry_state.set_result(result)
            elif isinstance(do, DoSleep):
                retry_state.prepare_for_next_attempt()
                yield self.sleep(do)
            else:
                raise gen.Return(do)


================================================
FILE: tenacity/wait.py
================================================
# Copyright 2016–2021 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013-2014 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import abc
import random
import typing
import warnings

from tenacity import _utils

if typing.TYPE_CHECKING:
    from tenacity import RetryCallState


class wait_base(abc.ABC):
    """Abstract base class for wait strategies."""

    @abc.abstractmethod
    def __call__(self, retry_state: "RetryCallState") -> float:
        pass

    def __add__(self, other: "wait_base") -> "wait_combine":
        return wait_combine(self, other)

    def __radd__(self, other: "wait_base") -> "wait_combine | wait_base":
        # make it possible to use multiple waits with the built-in sum function
        if other == 0:  # type: ignore[comparison-overlap]
            return self
        return self.__add__(other)


WaitBaseT = wait_base | typing.Callable[["RetryCallState"], float | int]


class wait_fixed(wait_base):
    """Wait strategy that waits a fixed amount of time between each retry."""

    def __init__(self, wait: _utils.time_unit_type) -> None:
        self.wait_fixed = _utils.to_seconds(wait)

    def __call__(self, retry_state: "RetryCallState") -> float:
        return self.wait_fixed


class wait_none(wait_fixed):
    """Wait strategy that doesn't wait at all before retrying."""

    def __init__(self) -> None:
        super().__init__(0)


class wait_random(wait_base):
    """Wait strategy that waits a random amount of time between min/max."""

    def __init__(
        self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1
    ) -> None:
        self.wait_random_min = _utils.to_seconds(min)
        self.wait_random_max = _utils.to_seconds(max)

    def __call__(self, retry_state: "RetryCallState") -> float:
        return self.wait_random_min + (
            random.random() * (self.wait_random_max - self.wait_random_min)
        )


class wait_combine(wait_base):
    """Combine several waiting strategies."""

    def __init__(self, *strategies: wait_base) -> None:
        self.wait_funcs = strategies

    def __call__(self, retry_state: "RetryCallState") -> float:
        return sum(x(retry_state=retry_state) for x in self.wait_funcs)


class wait_chain(wait_base):
    """Chain two or more waiting strategies.

    If all strategies are exhausted, the very last strategy is used
    thereafter.

    For example::

        @retry(wait=wait_chain(*[wait_fixed(1) for i in range(3)] +
                               [wait_fixed(2) for j in range(5)] +
                               [wait_fixed(5) for k in range(4)]))
        def wait_chained():
            print("Wait 1s for 3 attempts, 2s for 5 attempts and 5s "
                  "thereafter.")
    """

    def __init__(self, *strategies: wait_base) -> None:
        self.strategies = strategies

    def __call__(self, retry_state: "RetryCallState") -> float:
        wait_func_no = min(max(retry_state.attempt_number, 1), len(self.strategies))
        wait_func = self.strategies[wait_func_no - 1]
        return wait_func(retry_state=retry_state)


class wait_exception(wait_base):
    """Wait strategy that waits the amount of time returned by the predicate.

    The predicate is passed the exception object. Based on the exception, the
    user can decide how much time to wait before retrying.

    For example::

        def http_error(exception: BaseException) -> float:
            if (
                isinstance(exception, requests.HTTPError)
                and exception.response.status_code == requests.codes.too_many_requests
            ):
                return float(exception.response.headers.get("Retry-After", "1"))
            return 60.0


        @retry(
            stop=stop_after_attempt(3),
            wait=wait_exception(http_error),
        )
        def http_get_request(url: str) -> None:
            response = requests.get(url)
            response.raise_for_status()
    """

    def __init__(self, predicate: typing.Callable[[BaseException], float]) -> None:
        self.predicate = predicate

    def __call__(self, retry_state: "RetryCallState") -> float:
        if retry_state.outcome is None:
            raise RuntimeError("__call__() called before outcome was set")

        exception = retry_state.outcome.exception()
        if exception is None:
            raise RuntimeError("outcome failed but the exception is None")
        return self.predicate(exception)


class wait_incrementing(wait_base):
    """Wait an incremental amount of time after each attempt.

    Starting at a starting value and incrementing by a value for each attempt
    (and restricting the upper limit to some maximum value).
    """

    def __init__(
        self,
        start: _utils.time_unit_type = 0,
        increment: _utils.time_unit_type = 100,
        max: _utils.time_unit_type = _utils.MAX_WAIT,
    ) -> None:
        self.start = _utils.to_seconds(start)
        self.increment = _utils.to_seconds(increment)
        self.max = _utils.to_seconds(max)

    def __call__(self, retry_state: "RetryCallState") -> float:
        result = self.start + (self.increment * (retry_state.attempt_number - 1))
        return max(0, min(result, self.max))


class wait_exponential(wait_base):
    """Wait strategy that applies exponential backoff.

    It allows for a customized multiplier and an ability to restrict the
    upper and lower limits to some maximum and minimum value.

    The intervals are fixed (i.e. there is no jitter), so this strategy is
    suitable for balancing retries against latency when a required resource is
    unavailable for an unknown duration, but *not* suitable for resolving
    contention between multiple processes for a shared resource. Use
    wait_random_exponential for the latter case.
    """

    def __init__(
        self,
        multiplier: float = 1,
        max: _utils.time_unit_type = _utils.MAX_WAIT,
        exp_base: float = 2,
        min: _utils.time_unit_type = 0,
    ) -> None:
        self.multiplier = multiplier
        self.min = _utils.to_seconds(min)
        self.max = _utils.to_seconds(max)
        self.exp_base = exp_base

    def __call__(self, retry_state: "RetryCallState") -> float:
        try:
            exp = self.exp_base ** (retry_state.attempt_number - 1)
            result = self.multiplier * exp
        except OverflowError:
            return self.max
        return max(max(0, self.min), min(result, self.max))


class wait_random_exponential(wait_exponential):
    """Random wait with exponentially widening window.

    An exponential backoff strategy used to mediate contention between multiple
    uncoordinated processes for a shared resource in distributed systems. This
    is the sense in which "exponential backoff" is meant in e.g. Ethernet
    networking, and corresponds to the "Full Jitter" algorithm described in
    this blog post:

    https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/

    Each retry occurs at a random time in a geometrically expanding interval.
    It allows for a custom multiplier and an ability to restrict the upper
    limit of the random interval to some maximum value.

    Example::

        wait_random_exponential(multiplier=0.5,  # initial window 0.5s
                                max=60)          # max 60s timeout

    When waiting for an unavailable resource to become available again, as
    opposed to trying to resolve contention for a shared resource, the
    wait_exponential strategy (which uses a fixed interval) may be preferable.

    """

    def __call__(self, retry_state: "RetryCallState") -> float:
        high = super().__call__(retry_state=retry_state)
        return random.uniform(self.min, high)


class wait_exponential_jitter(wait_base):
    """Wait strategy that applies exponential backoff and jitter.

    It allows for a customized multiplier, maximum wait, jitter and minimum.

    This implements the strategy described here:
    https://cloud.google.com/storage/docs/retry-strategy

    The wait time is max(min, min(multiplier * 2**n + random.uniform(0, jitter), maximum))
    where n is the retry count.
    """

    def __init__(
        self,
        initial: float = 1,
        max: _utils.time_unit_type = _utils.MAX_WAIT,
        exp_base: float = 2,
        jitter: _utils.time_unit_type = 1,
        min: _utils.time_unit_type = 0,
        multiplier: float = 1,
    ) -> None:
        if initial != 1 and multiplier != 1:
            raise ValueError(
                "Cannot specify both 'initial' and 'multiplier' — use 'multiplier' only"
            )

        if initial != 1:
            warnings.warn(
                "The 'initial' parameter is deprecated, use 'multiplier' instead",
                DeprecationWarning,
                stacklevel=2,
            )
            multiplier = initial

        self.multiplier = multiplier
        self.max = _utils.to_seconds(max)
        self.exp_base = exp_base
        self.jitter = _utils.to_seconds(jitter)
        self.min = _utils.to_seconds(min)

    def __call__(self, retry_state: "RetryCallState") -> float:
        jitter = random.uniform(0, self.jitter)
        try:
            exp = self.exp_base ** (retry_state.attempt_number - 1)
            result = self.multiplier * exp + jitter
        except OverflowError:
            result = self.max
        return max(max(0, self.min), min(result, self.max))


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/test_after.py
================================================
import logging
import random
import unittest.mock

from tenacity import (
    _utils,
    after_log,
)

from . import test_tenacity


class TestAfterLogFormat(unittest.TestCase):
    def setUp(self) -> None:
        self.log_level = random.choice(
            (
                logging.DEBUG,
                logging.INFO,
                logging.WARNING,
                logging.ERROR,
                logging.CRITICAL,
            )
        )
        self.previous_attempt_number = random.randint(1, 512)

    def test_01_default(self) -> None:
        """Test log formatting."""
        log = unittest.mock.MagicMock(spec="logging.Logger.log")
        logger = unittest.mock.MagicMock(spec="logging.Logger", log=log)

        sec_format = "%.3g"
        delay_since_first_attempt = 0.1

        retry_state = test_tenacity.make_retry_state(
            self.previous_attempt_number, delay_since_first_attempt
        )
        fun = after_log(
            logger=logger, log_level=self.log_level
        )  # use default sec_format
        fun(retry_state)
        log.assert_called_once_with(
            self.log_level,
            f"Finished call to '{retry_state.get_fn_name()}' "
            f"after {sec_format % retry_state.seconds_since_start}(s), "
            f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
        )

    def test_02_none_seconds_since_start(self) -> None:
        """Test log formatting when seconds_since_start is None."""
        log = unittest.mock.MagicMock(spec="logging.Logger.log")
        logger = unittest.mock.MagicMock(spec="logging.Logger", log=log)

        retry_state = test_tenacity.make_retry_state(self.previous_attempt_number, 0.1)
        retry_state.outcome_timestamp = None
        assert retry_state.seconds_since_start is None

        fun = after_log(logger=logger, log_level=self.log_level)
        fun(retry_state)
        log.assert_called_once_with(
            self.log_level,
            f"Finished call to '{retry_state.get_fn_name()}' "
            f"after ?(s), "
            f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
        )

    def test_02_custom_sec_format(self) -> None:
        """Test log formatting with custom int format.."""
        log = unittest.mock.MagicMock(spec="logging.Logger.log")
        logger = unittest.mock.MagicMock(spec="logging.Logger", log=log)

        sec_format = "%.1f"
        delay_since_first_attempt = 0.1

        retry_state = test_tenacity.make_retry_state(
            self.previous_attempt_number, delay_since_first_attempt
        )
        fun = after_log(logger=logger, log_level=self.log_level, sec_format=sec_format)
        fun(retry_state)
        log.assert_called_once_with(
            self.log_level,
            f"Finished call to '{retry_state.get_fn_name()}' "
            f"after {sec_format % retry_state.seconds_since_start}(s), "
            f"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.",
        )


================================================
FILE: tests/test_asyncio.py
================================================
# Copyright 2016 Étienne Bersac
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import asyncio
import inspect
import unittest
from collections.abc import Callable, Coroutine
from functools import wraps
from typing import Any, TypeVar
from unittest import mock

try:
    import trio
except ImportError:
    have_trio = False
else:
    have_trio = True

import pytest

import tenacity
from tenacity import (
    AsyncRetrying,
    RetryCallState,
    RetryError,
    retry,
    retry_if_exception,
    retry_if_result,
    stop_after_attempt,
)
from tenacity import asyncio as tasyncio
from tenacity.wait import wait_fixed

from .test_tenacity import (
    NoIOErrorAfterCount,
    NoneReturnUntilAfterCount,
    current_time_ms,
)

_F = TypeVar("_F", bound=Callable[..., Coroutine[Any, Any, Any]])


def asynctest(callable_: _F) -> Callable[..., Any]:
    @wraps(callable_)
    def wrapper(*a: Any, **kw: Any) -> Any:
        return asyncio.run(callable_(*a, **kw))

    return wrapper


async def _async_function(thing: NoIOErrorAfterCount) -> Any:
    await asyncio.sleep(0.00001)
    return thing.go()


@retry
async def _retryable_coroutine(thing: NoIOErrorAfterCount) -> Any:
    await asyncio.sleep(0.00001)
    return thing.go()


@retry(stop=stop_after_attempt(2))
async def _retryable_coroutine_with_2_attempts(thing: NoIOErrorAfterCount) -> Any:
    await asyncio.sleep(0.00001)
    return thing.go()


class TestAsyncio(unittest.TestCase):
    @asynctest
    async def test_retry(self) -> None:
        thing = NoIOErrorAfterCount(5)
        await _retryable_coroutine(thing)
        assert thing.counter == thing.count

    @asynctest
    async def test_iscoroutinefunction(self) -> None:
        assert asyncio.iscoroutinefunction(_retryable_coroutine)
        assert inspect.iscoroutinefunction(_retryable_coroutine)

    @asynctest
    async def test_retry_using_async_retying(self) -> None:
        thing = NoIOErrorAfterCount(5)
        retrying = AsyncRetrying()
        await retrying(_async_function, thing)
        assert thing.counter == thing.count

    @asynctest
    async def test_stop_after_attempt(self) -> None:
        thing = NoIOErrorAfterCount(2)
        try:
            await _retryable_coroutine_with_2_attempts(thing)
        except RetryError:
            assert thing.counter == 2

    def test_repr(self) -> None:
        repr(tasyncio.AsyncRetrying())

    def test_retry_attributes(self) -> None:
        assert hasattr(_retryable_coroutine, "retry")
        assert hasattr(_retryable_coroutine, "retry_with")

    def test_retry_preserves_argument_defaults(self) -> None:
        async def function_with_defaults(a: int = 1) -> int:
            return a

        async def function_with_kwdefaults(*, a: int = 1) -> int:
            return a

        retrying = AsyncRetrying(
            wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3)
        )
        wrapped_defaults_function = retrying.wraps(function_with_defaults)
        wrapped_kwdefaults_function = retrying.wraps(function_with_kwdefaults)

        self.assertEqual(
            function_with_defaults.__defaults__,
            wrapped_defaults_function.__defaults__,  # type: ignore[attr-defined]
        )
        self.assertEqual(
            function_with_kwdefaults.__kwdefaults__,
            wrapped_kwdefaults_function.__kwdefaults__,  # type: ignore[attr-defined]
        )

    @asynctest
    async def test_attempt_number_is_correct_for_interleaved_coroutines(self) -> None:
        attempts: list[Any] = []

        def after(retry_state: RetryCallState) -> None:
            attempts.append((retry_state.args[0], retry_state.attempt_number))

        thing1 = NoIOErrorAfterCount(3)
        thing2 = NoIOErrorAfterCount(3)

        await asyncio.gather(
            _retryable_coroutine.retry_with(after=after)(thing1),
            _retryable_coroutine.retry_with(after=after)(thing2),
        )

        # There's no waiting on retry, only a wait in the coroutine, so the
        # executions should be interleaved.
        even_thing_attempts = attempts[::2]
        things, attempt_nos1 = zip(*even_thing_attempts)
        assert len(set(things)) == 1
        assert list(attempt_nos1) == [1, 2, 3]

        odd_thing_attempts = attempts[1::2]
        things, attempt_nos2 = zip(*odd_thing_attempts)
        assert len(set(things)) == 1
        assert list(attempt_nos2) == [1, 2, 3]


class TestAsyncEnabled(unittest.TestCase):
    @asynctest
    async def test_enabled_false_skips_retry(self) -> None:
        """When enabled=False, async function is called directly without retrying."""
        call_count = 0

        @retry(enabled=False, stop=stop_after_attempt(3))
        async def always_fails() -> None:
            nonlocal call_count
            call_count += 1
            raise ValueError("fail")

        with pytest.raises(ValueError, match="fail"):
            await always_fails()
        assert call_count == 1


@unittest.skipIf(not have_trio, "trio not installed")
class TestTrio(unittest.TestCase):
    def test_trio_basic(self) -> None:
        thing = NoIOErrorAfterCount(5)

        @retry
        async def trio_function() -> Any:
            await trio.sleep(0.00001)
            return thing.go()

        trio.run(trio_function)

        assert thing.counter == thing.count


class TestContextManager(unittest.TestCase):
    @asynctest
    async def test_do_max_attempts(self) -> None:
        attempts = 0
        retrying = tasyncio.AsyncRetrying(stop=stop_after_attempt(3))
        try:
            async for attempt in retrying:
                with attempt:
                    attempts += 1
                    raise Exception
        except RetryError:
            pass

        assert attempts == 3

    @asynctest
    async def test_async_with_attempt_manager(self) -> None:
        """AttemptManager supports async with for use inside async for."""
        attempts = 0
        retrying = tasyncio.AsyncRetrying(stop=stop_after_attempt(3))
        try:
            async for attempt in retrying:
                async with attempt:
                    attempts += 1
                    raise Exception
        except RetryError:
            pass

        assert attempts == 3

    @asynctest
    async def test_reraise(self) -> None:
        class CustomError(Exception):
            pass

        try:
            async for attempt in tasyncio.AsyncRetrying(
                stop=stop_after_attempt(1), reraise=True
            ):
                with attempt:
                    raise CustomError
        except CustomError:
            pass
        else:
            raise Exception

    @asynctest
    async def test_sleeps(self) -> None:
        start = current_time_ms()
        try:
            async for attempt in tasyncio.AsyncRetrying(
                stop=stop_after_attempt(1), wait=wait_fixed(1)
            ):
                with attempt:
                    raise Exception
        except RetryError:
            pass
        t = current_time_ms() - start
        self.assertLess(t, 1.1)

    @asynctest
    async def test_retry_with_result(self) -> None:
        async def test() -> int:
            attempts = 0

            # mypy doesn't have great lambda support
            def lt_3(x: float) -> bool:
                return x < 3

            async for attempt in tasyncio.AsyncRetrying(retry=retry_if_result(lt_3)):
                with attempt:
                    attempts += 1
                attempt.retry_state.set_result(attempts)
            return attempts

        result = await test()

        self.assertEqual(3, result)

    @asynctest
    async def test_retry_with_async_result(self) -> None:
        async def test() -> int:
            attempts = 0

            async def lt_3(x: float) -> bool:
                return x < 3

            async for attempt in tasyncio.AsyncRetrying(
                retry=tasyncio.retry_if_result(lt_3)
            ):
                with attempt:
                    attempts += 1

                assert attempt.retry_state.outcome  # help mypy
                if not attempt.retry_state.outcome.failed:
                    attempt.retry_state.set_result(attempts)

            return attempts

        result = await test()

        self.assertEqual(3, result)

    @asynctest
    async def test_retry_with_async_exc(self) -> None:
        async def test() -> int:
            attempts = 0

            class CustomException(Exception):
                pass

            async def is_exc(e: BaseException) -> bool:
                return isinstance(e, CustomException)

            async for attempt in tasyncio.AsyncRetrying(
                retry=tasyncio.retry_if_exception(is_exc)
            ):
                with attempt:
                    attempts += 1
                    if attempts < 3:
                        raise CustomException

                assert attempt.retry_state.outcome  # help mypy
                if not attempt.retry_state.outcome.failed:
                    attempt.retry_state.set_result(attempts)

            return attempts

        result = await test()

        self.assertEqual(3, result)

    @asynctest
    async def test_retry_with_async_result_or(self) -> None:
        async def test() -> int:
            attempts = 0

            async def lt_3(x: float) -> bool:
                return x < 3

            class CustomException(Exception):
                pass

            def is_exc(e: BaseException) -> bool:
                return isinstance(e, CustomException)

            retry_strategy = tasyncio.retry_if_result(lt_3) | retry_if_exception(is_exc)
            async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):
                with attempt:
                    attempts += 1
                    if 2 < attempts < 4:
                        raise CustomException

                assert attempt.retry_state.outcome  # help mypy
                if not attempt.retry_state.outcome.failed:
                    attempt.retry_state.set_result(attempts)

            return attempts

        result = await test()

        self.assertEqual(4, result)

    @asynctest
    async def test_retry_with_async_result_ror(self) -> None:
        async def test() -> int:
            attempts = 0

            def lt_3(x: float) -> bool:
                return x < 3

            class CustomException(Exception):
                pass

            async def is_exc(e: BaseException) -> bool:
                return isinstance(e, CustomException)

            retry_strategy = retry_if_result(lt_3) | tasyncio.retry_if_exception(is_exc)
            async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):
                with attempt:
                    attempts += 1
                    if 2 < attempts < 4:
                        raise CustomException

                assert attempt.retry_state.outcome  # help mypy
                if not attempt.retry_state.outcome.failed:
                    attempt.retry_state.set_result(attempts)

            return attempts

        result = await test()

        self.assertEqual(4, result)

    @asynctest
    async def test_retry_with_async_result_and(self) -> None:
        async def test() -> int:
            attempts = 0

            async def lt_3(x: float) -> bool:
                return x < 3

            def gt_0(x: float) -> bool:
                return x > 0

            retry_strategy = tasyncio.retry_if_result(lt_3) & retry_if_result(gt_0)
            async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):
                with attempt:
                    attempts += 1
                attempt.retry_state.set_result(attempts)

            return attempts

        result = await test()

        self.assertEqual(3, result)

    @asynctest
    async def test_retry_with_async_result_rand(self) -> None:
        async def test() -> int:
            attempts = 0

            async def lt_3(x: float) -> bool:
                return x < 3

            def gt_0(x: float) -> bool:
                return x > 0

            retry_strategy = retry_if_result(gt_0) & tasyncio.retry_if_result(lt_3)
            async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):
                with attempt:
                    attempts += 1
                attempt.retry_state.set_result(attempts)

            return attempts

        result = await test()

        self.assertEqual(3, result)

    @asynctest
    async def test_async_retying_iterator(self) -> None:
        thing = NoIOErrorAfterCount(5)
        with pytest.raises(TypeError):
            for attempts in AsyncRetrying():
                with attempts:
                    await _async_function(thing)


class TestDecoratorWrapper(unittest.TestCase):
    @asynctest
    async def test_retry_function_attributes(self) -> None:
        """Test that the wrapped function attributes are exposed as intended.

        - statistics contains the value for the latest function run
        - retry object can be modified to change its behaviour (useful to patch in tests)
        - retry object statistics are synced with function statistics
        """

        self.assertTrue(
            await _retryable_coroutine_with_2_attempts(NoIOErrorAfterCount(1))
        )

        expected_stats = {
            "attempt_number": 2,
            "delay_since_first_attempt": mock.ANY,
            "idle_for": mock.ANY,
            "start_time": mock.ANY,
        }
        self.assertEqual(
            _retryable_coroutine_with_2_attempts.statistics,
            expected_stats,
        )
        self.assertEqual(
            _retryable_coroutine_with_2_attempts.retry.statistics,
            expected_stats,
        )

        with mock.patch.object(
            _retryable_coroutine_with_2_attempts.retry,
            "stop",
            tenacity.stop_after_attempt(1),
        ):
            try:
                self.assertTrue(
                    await _retryable_coroutine_with_2_attempts(NoIOErrorAfterCount(2))
                )
            except RetryError as exc:
                expected_stats = {
                    "attempt_number": 1,
                    "delay_since_first_attempt": mock.ANY,
                    "idle_for": mock.ANY,
                    "start_time": mock.ANY,
                }
                self.assertEqual(
                    _retryable_coroutine_with_2_attempts.statistics,
                    expected_stats,
                )
                self.assertEqual(exc.last_attempt.attempt_number, 1)
                self.assertEqual(
                    _retryable_coroutine_with_2_attempts.retry.statistics,
                    expected_stats,
                )
            else:
                self.fail("RetryError should have been raised after 1 attempt")


# make sure mypy accepts passing an async sleep function
# https://github.com/jd/tenacity/issues/399
async def my_async_sleep(x: float) -> None:
    await asyncio.sleep(x)


@retry(sleep=my_async_sleep)
async def foo() -> None:
    pass


class TestSyncFunctionWithAsyncSleep(unittest.TestCase):
    @asynctest
    async def test_sync_function_with_async_sleep(self) -> None:
        """A sync function with an async sleep callable uses AsyncRetrying."""
        mock_sleep = mock.AsyncMock()

        thing = NoneReturnUntilAfterCount(2)

        @retry(
            sleep=mock_sleep,
            wait=wait_fixed(1),
            retry=retry_if_result(lambda x: x is None),
        )
        def sync_function() -> Any:
            return thing.go()

        result = await sync_function()
        assert result is True
        assert mock_sleep.await_count == 2


if __name__ == "__main__":
    unittest.main()


================================================
FILE: tests/test_issue_478.py
================================================
import asyncio
import typing
import unittest
from functools import wraps

from tenacity import RetryCallState, retry


def asynctest(
    callable_: typing.Callable[..., typing.Any],
) -> typing.Callable[..., typing.Any]:
    @wraps(callable_)
    def wrapper(*a: typing.Any, **kw: typing.Any) -> typing.Any:
        return asyncio.run(callable_(*a, **kw))

    return wrapper


MAX_RETRY_FIX_ATTEMPTS = 2


class TestIssue478(unittest.TestCase):
    def test_issue(self) -> None:
        results = []

        def do_retry(retry_state: RetryCallState) -> bool:
            outcome = retry_state.outcome
            assert outcome
            ex = outcome.exception()
            _subject_: str = retry_state.args[0]

            if _subject_ == "Fix":  # no retry on fix failure
                return False

            if retry_state.attempt_number >= MAX_RETRY_FIX_ATTEMPTS:
                return False

            if ex:
                do_fix_work()
                return True

            return False

        @retry(reraise=True, retry=do_retry)
        def _do_work(subject: str) -> None:
            if subject == "Error":
                results.append(f"{subject} is not working")
                raise Exception(f"{subject} is not working")
            results.append(f"{subject} is working")

        def do_any_work(subject: str) -> None:
            _do_work(subject)

        def do_fix_work() -> None:
            _do_work("Fix")

        try:
            do_any_work("Error")
        except Exception as exc:
            assert str(exc) == "Error is not working"
        else:
            raise AssertionError("No exception caught")

        assert results == [
            "Error is not working",
            "Fix is working",
            "Error is not working",
        ]

    @asynctest
    async def test_async(self) -> None:
        results = []

        async def do_retry(retry_state: RetryCallState) -> bool:
            outcome = retry_state.outcome
            assert outcome
            ex = outcome.exception()
            _subject_: str = retry_state.args[0]

            if _subject_ == "Fix":  # no retry on fix failure
                return False

            if retry_state.attempt_number >= MAX_RETRY_FIX_ATTEMPTS:
                return False

            if ex:
                await do_fix_work()
                return True

            return False

        @retry(reraise=True, retry=do_retry)
        async def _do_work(subject: str) -> None:
            if subject == "Error":
                results.append(f"{subject} is not working")
                raise Exception(f"{subject} is not working")
            results.append(f"{subject} is working")

        async def do_any_work(subject: str) -> None:
            await _do_work(subject)

        async def do_fix_work() -> None:
            await _do_work("Fix")

        try:
            await do_any_work("Error")
        except Exception as exc:
            assert str(exc) == "Error is not working"
        else:
            raise AssertionError("No exception caught")

        assert results == [
            "Error is not working",
            "Fix is working",
            "Error is not working",
        ]


================================================
FILE: tests/test_tenacity.py
================================================
# Copyright 2016–2021 Julien Danjou
# Copyright 2016 Joshua Harlow
# Copyright 2013 Ray Holder
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import contextlib
import datetime
import logging
import pickle
import re
import time
import typing
import unittest
from fractions import Fraction
from unittest import mock

import pytest
from typeguard import check_type

import tenacity
from tenacity import RetryCallState, RetryError, Retrying, retry
from tenacity.retry import retry_all, retry_any

_unset = object()


def _make_unset_exception(func_name: str, **kwargs: typing.Any) -> TypeError:
    missing = []
    for k, v in kwargs.items():
        if v is _unset:
            missing.append(k)
    missing_str = ", ".join(repr(s) for s in missing)
    return TypeError(func_name + " func missing parameters: " + missing_str)


def _set_delay_since_start(retry_state: RetryCallState, delay: typing.Any) -> None:
    # Ensure outcome_timestamp - start_time is *exactly* equal to the delay to
    # avoid complexity in test code.
    retry_state.start_time = Fraction(retry_state.start_time)  # type: ignore[assignment]
    retry_state.outcome_timestamp = retry_state.start_time + Fraction(delay)
    assert retry_state.seconds_since_start == delay


def make_retry_state(
    previous_attempt_number: typing.Any,
    delay_since_first_attempt: typing.Any,
    last_result: typing.Any = None,
    upcoming_sleep: typing.Any = 0,
) -> RetryCallState:
    """Construct RetryCallState for given attempt number & delay.

    Only used in testing and thus is extra careful about timestamp arithmetics.
    """
    required_parameter_unset = (
        previous_attempt_number is _unset or delay_since_first_attempt is _unset
    )
    if required_parameter_unset:
        raise _make_unset_exception(
            "wait/stop",
            previous_attempt_number=previous_attempt_number,
            delay_since_first_attempt=delay_since_first_attempt,
        )

    retry_state = RetryCallState(None, None, (), {})  # type: ignore[arg-type]
    retry_state.attempt_number = previous_attempt_number
    if last_result is not None:
        retry_state.outcome = last_result
    else:
        retry_state.set_result(None)

    retry_state.upcoming_sleep = upcoming_sleep

    _set_delay_since_start(retry_state, delay_since_first_attempt)
    return retry_state


class TestBase(unittest.TestCase):
    def test_retrying_repr(self) -> None:
        class ConcreteRetrying(tenacity.BaseRetrying):
            def __call__(
                self, fn: typing.Any, *args: typing.Any, **kwargs: typing.Any
            ) -> typing.Any:
                pass

        repr(ConcreteRetrying())

    def test_callstate_repr(self) -> None:
        rs = RetryCallState(None, None, (), {})  # type: ignore[arg-type]
        rs.idle_for = 1.1111111
        assert repr(rs).endswith("attempt #1; slept for 1.11; last result: none yet>")
        rs = make_retry_state(2, 5)
        assert repr(rs).endswith(
            "attempt #2; slept for 0.0; last result: returned None>"
        )
        rs = make_retry_state(
            0, 0, last_result=tenacity.Future.construct(1, ValueError("aaa"), True)
        )
        assert repr(rs).endswith(
            "attempt #0; slept for 0.0; last result: failed (ValueError aaa)>"
        )


class TestRetryingName(unittest.TestCase):
    def test_str_default(self) -> None:
        """Without a name, str() returns '<unknown>'."""
        assert str(Retrying()) == "<unknown>"

    def test_str_with_name(self) -> None:
        """With a name, str() returns the given name."""
        assert str(Retrying(name="my_block")) == "my_block"

    def test_str_preserved_by_copy(self) -> None:
        """copy() preserves the name."""
        r = Retrying(name="my_block")
        assert str(r.copy()) == "my_block"

    def test_str_overridden_by_copy(self) -> None:
        """copy() allows overriding the name."""
        r = Retrying(name="original")
        assert str(r.copy(name="overridden")) == "overridden"

    def test_get_fn_name_decorator(self) -> None:
        """get_fn_name() returns the function's qualified name when used as decorator."""
        captured: list[RetryCallState] = []

        @tenacity.retry(
            stop=tenacity.stop_after_attempt(1),
            after=lambda rs: captured.append(rs),
        )
        def my_func() -> None:
            raise ValueError

        with contextlib.suppress(Exception):
            my_func()
        assert captured
        assert "my_func" in captured[0].get_fn_name()

    def test_get_fn_name_context_manager_no_name(self) -> None:
        """get_fn_name() returns '<unknown>' in context manager mode without a name."""
        r = Retrying(stop=tenacity.stop_after_attempt(1))
        rs = RetryCallState(r, None, (), {})
        assert rs.get_fn_name() == "<unknown>"

    def test_get_fn_name_context_manager_with_name(self) -> None:
        """get_fn_name() returns the given name in context manager mode."""
        r = Retrying(name="ws_listener", stop=tenacity.stop_after_attempt(1))
        rs = RetryCallState(r, None, (), {})
        assert rs.get_fn_name() == "ws_listener"

    def test_logging_uses_name(self) -> None:
        """before_log uses the name parameter in context manager mode."""
        import unittest.mock

        log = unittest.mock.MagicMock()
        logger = unittest.mock.MagicMock(log=log)

        with contextlib.suppress(Exception):
            for attempt in Retrying(
                name="my_block",
                before=tenacity.before_log(logger, logging.INFO),
                stop=tenacity.stop_after_attempt(1),
            ):
                with attempt:
                    raise ValueError

        args = log.call_args[0]
        assert "my_block" in args[1]


class TestStopConditions(unittest.TestCase):
    def test_never_stop(self) -> None:
        r = Retrying()
        self.assertFalse(r.stop(make_retry_state(3, 6546)))

    def test_stop_any(self) -> None:
        stop = tenacity.stop_any(
            tenacity.stop_after_delay(1), tenacity.stop_after_attempt(4)
        )

        def s(*args: typing.Any) -> bool:
            return stop(make_retry_state(*args))

        self.assertFalse(s(1, 0.1))
        self.assertFalse(s(2, 0.2))
        self.assertFalse(s(2, 0.8))
        self.assertTrue(s(4, 0.8))
        self.assertTrue(s(3, 1.8))
        self.assertTrue(s(4, 1.8))

    def test_stop_all(self) -> None:
        stop = tenacity.stop_all(
            tenacity.stop_after_delay(1), tenacity.stop_after_attempt(4)
        )

        def s(*args: typing.Any) -> bool:
            return stop(make_retry_state(*args))

        self.assertFalse(s(1, 0.1))
        self.assertFalse(s(2, 0.2))
        self.assertFalse(s(2, 0.8))
        self.assertFalse(s(4, 0.8))
        self.assertFalse(s(3, 1.8))
        self.assertTrue(s(4, 1.8))

    def test_stop_or(self) -> None:
        stop = tenacity.stop_after_delay(1) | tenacity.stop_after_attempt(4)

        def s(*args: typing.Any) -> bool:
            return stop(make_retry_state(*args))

        self.assertFalse(s(1, 0.1))
        self.assertFalse(s(2, 0.2))
        self.assertFalse(s(2, 0.8))
        self.assertTrue(s(4, 0.8))
        self.assertTrue(s(3, 1.8))
        self.assertTrue(s(4, 1.8))

    def test_stop_and(self) -> None:
        stop = tenacity.stop_after_delay(1) & tenacity.stop_after_attempt(4)

        def s(*args: typing.Any) -> bool:
            return stop(make_retry_state(*args))

        self.assertFalse(s(1, 0.1))
        self.assertFalse(s(2, 0.2))
        self.assertFalse(s(2, 0.8))
        self.assertFalse(s(4, 0.8))
        self.assertFalse(s(3, 1.8))
        self.assertTrue(s(4, 1.8))

    def test_stop_after_attempt(self) -> None:
        r = Retrying(stop=tenacity.stop_after_attempt(3))
        self.assertFalse(r.stop(make_retry_state(2, 6546)))
        self.assertTrue(r.stop(make_retry_state(3, 6546)))
        self.assertTrue(r.stop(make_retry_state(4, 6546)))

    def test_stop_after_delay(self) -> None:
        for delay in (1, datetime.timedelta(seconds=1)):
            with self.subTest():
                r = Retrying(stop=tenacity.stop_after_delay(delay))
                self.assertFalse(r.stop(make_retry_state(2, 0.999)))
                self.assertTrue(r.stop(make_retry_state(2, 1)))
                self.assertTrue(r.stop(make_retry_state(2, 1.001)))

    def test_stop_before_delay(self) -> None:
        for delay in (1, datetime.timedelta(seconds=1)):
            with self.subTest():
                r = Retrying(stop=tenacity.stop_before_delay(delay))
                self.assertFalse(
                    r.stop(make_retry_state(2, 0.999, upcoming_sleep=0.0001))
                )
                self.assertTrue(r.stop(make_retry_state(2, 1, upcoming_sleep=0.001)))
                self.assertTrue(r.stop(make_retry_state(2, 1, upcoming_sleep=1)))

                # It should act the same as stop_after_delay if upcoming sleep is 0
                self.assertFalse(r.stop(make_retry_state(2, 0.999, upcoming_sleep=0)))
                self.assertTrue(r.stop(make_retry_state(2, 1, upcoming_sleep=0)))
                self.assertTrue(r.stop(make_retry_state(2, 1.001, upcoming_sleep=0)))

    def test_legacy_explicit_stop_type(self) -> None:
        Retrying(stop="stop_after_attempt")  # type: ignore[arg-type]

    def test_stop_func_with_retry_state(self) -> None:
        def stop_func(retry_state: RetryCallState) -> bool:
            rs = retry_state
            return rs.attempt_number == rs.seconds_since_start

        r = Retrying(stop=stop_func)
        self.assertFalse(r.stop(make_retry_state(1, 3)))
        self.assertFalse(r.stop(make_retry_state(100, 99)))
        self.assertTrue(r.stop(make_retry_state(101, 101)))


class TestWaitConditions(unittest.TestCase):
    def test_no_sleep(self) -> None:
        r = Retrying()
        self.assertEqual(0, r.wait(make_retry_state(18, 9879)))

    def test_fixed_sleep(self) -> None:
        for wait in (1, datetime.timedelta(seconds=1)):
            with self.subTest():
                r = Retrying(wait=tenacity.wait_fixed(wait))
                self.assertEqual(1, r.wait(make_retry_state(12, 6546)))

    def test_incrementing_sleep(self) -> None:
        for start, increment in (
            (500, 100),
            (datetime.timedelta(seconds=500), datetime.timedelta(seconds=100)),
        ):
            with self.subTest():
                r = Retrying(
                    wait=tenacity.wait_incrementing(start=start, increment=increment)
                )
                self.assertEqual(500, r.wait(make_retry_state(1, 6546)))
                self.assertEqual(600, r.wait(make_retry_state(2, 6546)))
                self.assertEqual(700, r.wait(make_retry_state(3, 6546)))

    def test_random_sleep(self) -> None:
        for min_, max_ in (
            (1, 20),
            (datetime.timedelta(seconds=1), datetime.timedelta(seconds=20)),
        ):
            with self.subTest():
                r = Retrying(wait=tenacity.wait_random(min=min_, max=max_))
                times = set()
                for _ in range(1000):
                    times.add(r.wait(make_retry_state(1, 6546)))

                # this is kind of non-deterministic...
                self.assertTrue(len(times) > 1)
                for t in times:
                    self.assertTrue(t >= 1)
                    self.assertTrue(t < 20)

    def test_random_sleep_withoutmin_(self) -> None:
        r = Retrying(wait=tenacity.wait_random(max=2))
        times = set()
        times.add(r.wait(make_retry_state(1, 6546)))
        times.add(r.wait(make_retry_state(1, 6546)))
        times.add(r.wait(make_retry_state(1, 6546)))
        times.add(r.wait(make_retry_state(1, 6546)))

        # this is kind of non-deterministic...
        self.assertTrue(len(times) > 1)
        for t in times:
            self.assertTrue(t >= 0)
            self.assertTrue(t <= 2)

    def test_exponential(self) -> None:
        r = Retrying(wait=tenacity.wait_exponential())
        self.assertEqual(r.wait(make_retry_state(1, 0)), 1)
        self.assertEqual(r.wait(make_retry_state(2, 0)), 2)
        self.assertEqual(r.wait(make_retry_state(3, 0)), 4)
        self.assertEqual(r.wait(make_retry_state(4, 0)), 8)
        self.assertEqual(r.wait(make_retry_state(5, 0)), 16)
        self.assertEqual(r.wait(make_retry_state(6, 0)), 32)
        self.assertEqual(r.wait(make_retry_state(7, 0)), 64)
        self.assertEqual(r.wait(make_retry_state(8, 0)), 128)

    def test_exponential_with_max_wait(self) -> None:
        r = Retrying(wait=tenacity.wait_exponential(max=40))
        self.assertEqual(r.wait(make_retry_state(1, 0)), 1)
        self.assertEqual(r.wait(make_retry_state(2, 0)), 2)
        self.assertEqual(r.wait(make_retry_state(3, 0)), 4)
        self.assertEqual(r.wait(make_retry_state(4, 0)), 8)
        self.assertEqual(r.wait(make_retry_state(5, 0)), 16)
        self.assertEqual(r.wait(make_retry_state(6, 0)), 32)
        self.assertEqual(r.wait(make_retry_state(7, 0)), 40)
        self.assertEqual(r.wait(make_retry_state(8, 0)), 40)
        self.assertEqual(r.wait(make_retry_state(50, 0)), 40)

    def test_exponential_with_min_wait(self) -> None:
        r = Retrying(wait=tenacity.wait_exponential(min=20))
        self.assertEqual(r.wait(make_retry_state(1, 0)), 20)
        self.assertEqual(r.wait(make_retry_state(2, 0)), 20)
        self.assertEqual(r.wait(make_retry_state(3, 0)), 20)
        self.assertEqual(r.wait(make_retry_state(4, 0)), 20)
        self.assertEqual(r.wait(make_retry_state(5, 0)), 20)
        self.assertEqual(r.wait(make_retry_state(6, 0)), 32)
        self.assertEqual(r.wait(make_retry_state(7, 0)), 64)
        self.assertEqual(r.wait(make_retry_state(8, 0)), 128)
        self.assertEqual(r.wait(make_retry_state(20, 0)), 524288)

    def test_exponential_with_max_wait_and_multiplier(self) -> None:
        r = Retrying(wait=tenacity.wait_exponential(max=50, multiplier=1))
        self.assertEqual(r.wait(make_retry_state(1, 0)), 1)
        self.assertEqual(r.wait(make_retry_state(2, 0)), 2)
        self.assertEqual(r.wait(make_retry_state(3, 0)), 4)
        self.assertEqual(r.wait(make_retry_state(4, 0)), 8)
        self.assertEqual(r.wait(make_retry_state(5, 0)), 16)
        self.assertEqual(r.wait(make_retry_state(6, 0)), 32)
        self.assertEqual(r.wait(make_retry_state(7, 0)), 50)
        self.assertEqual(r.wait(make_retry_state(8, 0)), 50)
        self.assertEqual(r.wait(make_retry_state(50, 0)), 50)

    def test_exponential_with_min_wait_and_multiplier(self) -> None:
        r = Retrying(wait=tenacity.wait_exponential(min=20, multiplier=2))
        self.assertEqual(r.wait(make_retry_state(1, 0)), 20)
        self.assertEqual(r.wait(make_retry_state(2, 0)), 20)
        self.assertEqual(r.wait(make_retry_state(3, 0)), 20)
        self.assertEqual(r.wait(make_retry_state(4, 0)), 20)
        self.assertEqual(r.wait(make_retry_state(5, 0)), 32)
        self.assertEqual(r.wait(make_retry_state(6, 0)), 64)
        self.assertEqual(r.wait(make_retry_state(7, 0)), 128)
        self.assertEqual(r.wait(make_retry_state(8, 0)), 256)
        self.assertEqual(r.wait(make_retry_state(20, 0)), 1048576)

    def test_exponential_with_min_wait_andmax__wait(self) -> None:
        for min_, max_ in (
            (10, 100),
            (datetime.timedelta(seconds=10), datetime.timedelta(seconds=100)),
        ):
            with self.subTest():
                r = Retrying(wait=tenacity.wait_exponential(min=min_, max=max_))
                self.assertEqual(r.wait(make_retry_state(1, 0)), 10)
                self.assertEqual(r.wait(make_retry_state(2, 0)), 10)
                self.assertEqual(r.wait(make_retry_state(3, 0)), 10)
                self.assertEqual(r.wait(make_retry_state(4, 0)), 10)
                self.assertEqual(r.wait(make_retry_state(5, 0)), 16)
                self.assertEqual(r.wait(make_retry_state(6, 0)), 32)
                self.assertEqual(r.wait(make_retry_state(7, 0)), 64)
                self.assertEqual(r.wait(make_retry_state(8, 0)), 100)
                self.assertEqual(r.wait(make_retry_state(9, 0)), 100)
                self.assertEqual(r.wait(make_retry_state(20, 0)), 100)

    def test_legacy_explicit_wait_type(self) -> None:
        Retrying(wait="exponential_sleep")  # type: ignore[arg-type]

    def test_wait_func(self) -> None:
        def wait_func(retry_state: RetryCallState) -> typing.Any:
            return retry_state.attempt_number * retry_state.seconds_since_start  # type: ignore[operator]

        r = Retrying(wait=wait_func)
        self.assertEqual(r.wait(make_retry_state(1, 5)), 5)
        self.assertEqual(r.wait(make_retry_state(2, 11)), 22)
        self.assertEqual(r.wait(make_retry_state(10, 100)), 1000)

    def test_wait_combine(self) -> None:
        r = Retrying(
            wait=tenacity.wait_combine(
                tenacity.wait_random(0, 3), tenacity.wait_fixed(5)
            )
        )
        # Test it a few time since it's random
        for _i in range(1000):
            w = r.wait(make_retry_state(1, 5))
            self.assertLess(w, 8)
            self.assertGreaterEqual(w, 5)

    def test_wait_exception(self) -> None:
        def predicate(exc: BaseException) -> float:
            if isinstance(exc, ValueError):
                return 3.5
            return 10.0

        r = Retrying(wait=tenacity.wait_exception(predicate))

        fut1 = tenacity.Future.construct(1, ValueError(), True)
        self.assertEqual(r.wait(make_retry_state(1, 0, last_result=fut1)), 3.5)

        fut2 = tenacity.Future.construct(1, KeyError(), True)
        self.assertEqual(r.wait(make_retry_state(1, 0, last_result=fut2)), 10.0)

        fut3 = tenacity.Future.construct(1, None, False)
        with self.assertRaises(RuntimeError):
            r.wait(make_retry_state(1, 0, last_result=fut3))

    def test_wait_double_sum(self) -> None:
        r = Retrying(wait=tenacity.wait_random(0, 3) + tenacity.wait_fixed(5))
        # Test it a few time since it's random
        for _i in range(1000):
            w = r.wait(make_retry_state(1, 5))
            self.assertLess(w, 8)
            self.assertGreaterEqual(w, 5)

    def test_wait_triple_sum(self) -> None:
        r = Retrying(
            wait=tenacity.wait_fixed(1)
            + tenacity.wait_random(0, 3)
            + tenacity.wait_fixed(5)
        )
        # Test it a few time since it's random
        for _i in range(1000):
            w = r.wait(make_retry_state(1, 5))
            self.assertLess(w, 9)
            self.assertGreaterEqual(w, 6)

    def test_wait_arbitrary_sum(self) -> None:
        r = Retrying(
            wait=sum(  # type: ignore[arg-type]
                [
                    tenacity.wait_fixed(1),  # type: ignore[list-item]
                    tenacity.wait_random(0, 3),  # type: ignore[list-item]
                    tenacity.wait_fixed(5),  # type: ignore[list-item]
                    tenacity.wait_none(),  # type: ignore[list-item]
                ]
            )
        )
        # Test it a few time since it's random
        for _ in range(1000):
            w = r.wait(make_retry_state(1, 5))
            self.assertLess(w, 9)
            self.assertGreaterEqual(w, 6)

    def _assert_range(self, wait: float, min_: float, max_: float) -> None:
        self.assertLess(wait, max_)
        self.assertGreaterEqual(wait, min_)

    def _assert_inclusive_range(self, wait: float, low: float, high: float) -> None:
        self.assertLessEqual(wait, high)
        self.assertGreaterEqual(wait, low)

    def _assert_inclusive_epsilon(
        self, wait: float, target: float, epsilon: float
    ) -> None:
        self.assertLessEqual(wait, target + epsilon)
        self.assertGreaterEqual(wait, target - epsilon)

    def test_wait_chain(self) -> None:
        r = Retrying(
            wait=tenacity.wait_chain(
                *[tenacity.wait_fixed(1) for i in range(2)]
                + [tenacity.wait_fixed(4) for i in range(2)]
                + [tenacity.wait_fixed(8) for i in range(1)]
            )
        )

        for i in range(10):
            w = r.wait(make_retry_state(i + 1, 1))
            if i < 2:
                self._assert_range(w, 1, 2)
            elif i < 4:
                self._assert_range(w, 4, 5)
            else:
                self._assert_range(w, 8, 9)

    def test_wait_chain_multiple_invocations(self) -> None:
        sleep_intervals: list[float] = []
        r = Retrying(
            sleep=sleep_intervals.append,
            wait=tenacity.wait_chain(*[tenacity.wait_fixed(i + 1) for i in range(3)]),
            stop=tenacity.stop_after_attempt(5),
            retry=tenacity.retry_if_result(lambda x: x == 1),
        )

        @r.wraps
        def always_return_1() -> int:
            return 1

        self.assertRaises(tenacity.RetryError, always_return_1)
        self.assertEqual(sleep_intervals, [1.0, 2.0, 3.0, 3.0])
        sleep_intervals[:] = []

        # Clear and restart retrying.
        self.assertRaises(tenacity.RetryError, always_return_1)
        self.assertEqual(sleep_intervals, [1.0, 2.0, 3.0, 3.0])
        sleep_intervals[:] = []

    def test_wait_random_exponential(self) -> None:
        fn = tenacity.wait_random_exponential(0.5, 60.0)

        for _ in range(1000):
            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 0, 0.5)
            self._assert_inclusive_range(fn(make_retry_state(2, 0)), 0, 1.0)
            self._assert_inclusive_range(fn(make_retry_state(3, 0)), 0, 2.0)
            self._assert_inclusive_range(fn(make_retry_state(4, 0)), 0, 4.0)
            self._assert_inclusive_range(fn(make_retry_state(5, 0)), 0, 8.0)
            self._assert_inclusive_range(fn(make_retry_state(6, 0)), 0, 16.0)
            self._assert_inclusive_range(fn(make_retry_state(7, 0)), 0, 32.0)
            self._assert_inclusive_range(fn(make_retry_state(8, 0)), 0, 60.0)
            self._assert_inclusive_range(fn(make_retry_state(9, 0)), 0, 60.0)

        # max wait
        max_wait = 5
        fn = tenacity.wait_random_exponential(10, max_wait)
        for _ in range(1000):
            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 0.00, max_wait)

        # min wait
        min_wait = 5
        fn = tenacity.wait_random_exponential(min=min_wait)
        for _ in range(1000):
            self._assert_inclusive_range(fn(make_retry_state(1, 0)), min_wait, 5)

        # Default arguments exist
        fn = tenacity.wait_random_exponential()
        fn(make_retry_state(0, 0))

    def test_wait_random_exponential_statistically(self) -> None:
        fn = tenacity.wait_random_exponential(0.5, 60.0)

        attempt = [[fn(make_retry_state(i, 0)) for _ in range(4000)] for i in range(10)]

        def mean(lst: list[float]) -> float:
            return float(sum(lst)) / float(len(lst))

        # skipping attempt 0
        self._assert_inclusive_epsilon(mean(attempt[1]), 0.25, 0.02)
        self._assert_inclusive_epsilon(mean(attempt[2]), 0.50, 0.04)
        self._assert_inclusive_epsilon(mean(attempt[3]), 1, 0.08)
        self._assert_inclusive_epsilon(mean(attempt[4]), 2, 0.16)
        self._assert_inclusive_epsilon(mean(attempt[5]), 4, 0.32)
        self._assert_inclusive_epsilon(mean(attempt[6]), 8, 0.64)
        self._assert_inclusive_epsilon(mean(attempt[7]), 16, 1.28)
        self._assert_inclusive_epsilon(mean(attempt[8]), 30, 2.56)
        self._assert_inclusive_epsilon(mean(attempt[9]), 30, 2.56)

    def test_wait_exponential_jitter(self) -> None:
        fn = tenacity.wait_exponential_jitter(max=60)

        for _ in range(1000):
            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 1, 2)
            self._assert_inclusive_range(fn(make_retry_state(2, 0)), 2, 3)
            self._assert_inclusive_range(fn(make_retry_state(3, 0)), 4, 5)
            self._assert_inclusive_range(fn(make_retry_state(4, 0)), 8, 9)
            self._assert_inclusive_range(fn(make_retry_state(5, 0)), 16, 17)
            self._assert_inclusive_range(fn(make_retry_state(6, 0)), 32, 33)
            self.assertEqual(fn(make_retry_state(7, 0)), 60)
            self.assertEqual(fn(make_retry_state(8, 0)), 60)
            self.assertEqual(fn(make_retry_state(9, 0)), 60)

        with self.assertWarns(DeprecationWarning):
            fn = tenacity.wait_exponential_jitter(10, 5)
        for _ in range(1000):
            self.assertEqual(fn(make_retry_state(1, 0)), 5)

        # Default arguments exist
        fn = tenacity.wait_exponential_jitter()
        fn(make_retry_state(0, 0))

    def test_wait_exponential_jitter_min(self) -> None:
        fn = tenacity.wait_exponential_jitter(initial=1, max=60, jitter=1, min=5)
        for _ in range(1000):
            # Even for attempt 1 (base wait=1 + jitter 0..1 = 1..2), min=5 applies
            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 5, 5)
            self._assert_inclusive_range(fn(make_retry_state(2, 0)), 5, 5)
            self._assert_inclusive_range(fn(make_retry_state(3, 0)), 5, 5)
            # For attempt 4, base wait=8 + jitter 0..1 = 8..9, above min
            self._assert_inclusive_range(fn(make_retry_state(4, 0)), 8, 9)

    def test_wait_exponential_jitter_timedelta(self) -> None:
        from datetime import timedelta

        fn = tenacity.wait_exponential_jitter(
            max=timedelta(seconds=60),
            jitter=timedelta(seconds=1),
            min=timedelta(seconds=5),
        )
        for _ in range(1000):
            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 5, 5)
            self._assert_inclusive_range(fn(make_retry_state(5, 0)), 16, 17)
            self.assertEqual(fn(make_retry_state(7, 0)), 60)

    def test_wait_exponential_jitter_multiplier(self) -> None:
        fn = tenacity.wait_exponential_jitter(multiplier=10, max=60, jitter=0)
        self.assertEqual(fn(make_retry_state(1, 0)), 10)
        self.assertEqual(fn(make_retry_state(2, 0)), 20)
        self.assertEqual(fn(make_retry_state(3, 0)), 40)
        self.assertEqual(fn(make_retry_state(4, 0)), 60)

    def test_wait_exponential_jitter_initial_deprecated(self) -> None:
        with self.assertWarns(DeprecationWarning):
            fn = tenacity.wait_exponential_jitter(initial=10, max=60, jitter=0)
        self.assertEqual(fn(make_retry_state(1, 0)), 10)
        self.assertEqual(fn(make_retry_state(2, 0)), 20)

    def test_wait_exponential_jitter_initial_and_multiplier_raises(self) -> None:
        with self.assertRaises(ValueError):
            tenacity.wait_exponential_jitter(initial=5, multiplier=10)

    def test_wait_retry_state_attributes(self) -> None:
        class ExtractCallState(Exception):
            pass

        # retry_state is mutable, so return it as an exception to extract the
        # exact values it has when wait is called and bypass any other logic.
        def waitfunc(retry_state: RetryCallState) -> float:
            raise ExtractCallState(retry_state)

        retrying = Retrying(
            wait=waitfunc,
            retry=(
                tenacity.retry_if_exception_type()
                | tenacity.retry_if_result(lambda result: result == 123)
            ),
        )

        def returnval() -> int:
            return 123

        try:
            retrying(returnval)
        except ExtractCallState as err:
            retry_state = err.args[0]
        self.assertIs(retry_state.fn, returnval)
        self.assertEqual(retry_state.args, ())
        self.assertEqual(retry_state.kwargs, {})
        self.assertEqual(retry_state.outcome.result(), 123)
        self.assertEqual(retry_state.attempt_number, 1)
        self.assertGreaterEqual(retry_state.outcome_timestamp, retry_state.start_time)

        def dying() -> None:
            raise Exception("Broken")

        try:
            retrying(dying)
        except ExtractCallState as err:
            retry_state = err.args[0]
        self.assertIs(retry_state.fn, dying)
        self.assertEqual(retry_state.args, ())
        self.assertEqual(retry_state.kwargs, {})
        self.assertEqual(str(retry_state.outcome.exception()), "Broken")
        self.assertEqual(retry_state.attempt_number, 1)
        self.assertGreaterEqual(retry_state.outcome_timestamp, retry_state.start_time)


class TestRetryConditions(unittest.TestCase):
    def test_retry_if_result(self) -> None:
        retry = tenacity.retry_if_result(lambda x: x == 1)

        def r(fut: tenacity.Future) -> bool:
            retry_state = make_retry_state(1, 1.0, last_result=fut)
            return retry(retry_state)

        self.assertTrue(r(tenacity.Future.construct(1, 1, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 2, False)))

    def test_retry_if_not_result(self) -> None:
        retry = tenacity.retry_if_not_result(lambda x: x == 1)

        def r(fut: tenacity.Future) -> bool:
            retry_state = make_retry_state(1, 1.0, last_result=fut)
            return retry(retry_state)

        self.assertTrue(r(tenacity.Future.construct(1, 2, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 1, False)))

    def test_retry_any(self) -> None:
        retry = tenacity.retry_any(
            tenacity.retry_if_result(lambda x: x == 1),
            tenacity.retry_if_result(lambda x: x == 2),
        )

        def r(fut: tenacity.Future) -> bool:
            retry_state = make_retry_state(1, 1.0, last_result=fut)
            return retry(retry_state)

        self.assertTrue(r(tenacity.Future.construct(1, 1, False)))
        self.assertTrue(r(tenacity.Future.construct(1, 2, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 3, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 1, True)))

    def test_retry_all(self) -> None:
        retry = tenacity.retry_all(
            tenacity.retry_if_result(lambda x: x == 1),
            tenacity.retry_if_result(lambda x: isinstance(x, int)),
        )

        def r(fut: tenacity.Future) -> bool:
            retry_state = make_retry_state(1, 1.0, last_result=fut)
            return retry(retry_state)

        self.assertTrue(r(tenacity.Future.construct(1, 1, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 2, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 3, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 1, True)))

    def test_retry_and(self) -> None:
        retry = tenacity.retry_if_result(lambda x: x == 1) & tenacity.retry_if_result(
            lambda x: isinstance(x, int)
        )

        def r(fut: tenacity.Future) -> bool:
            retry_state = make_retry_state(1, 1.0, last_result=fut)
            return retry(retry_state)

        self.assertTrue(r(tenacity.Future.construct(1, 1, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 2, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 3, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 1, True)))

    def test_retry_or(self) -> None:
        retry = tenacity.retry_if_result(
            lambda x: x == "foo"
        ) | tenacity.retry_if_result(lambda x: isinstance(x, int))

        def r(fut: tenacity.Future) -> bool:
            retry_state = make_retry_state(1, 1.0, last_result=fut)
            return retry(retry_state)

        self.assertTrue(r(tenacity.Future.construct(1, "foo", False)))
        self.assertFalse(r(tenacity.Future.construct(1, "foobar", False)))
        self.assertFalse(r(tenacity.Future.construct(1, 2.2, False)))
        self.assertFalse(r(tenacity.Future.construct(1, 42, True)))

    def test_retry_or_with_plain_function(self) -> None:
        """Plain callables can be composed with retry_base via |."""

        def my_retry(retry_state: tenacity.RetryCallState) -> bool:
            return retry_state.outcome is not None and not retry_state.outcome.failed

        # retry_base | plain_callable (exercises __or__ fallback)
        retry = tenacity.retry_if_exception_type(Exception) | my_retry
        retry_state = make_retry_state(
            1, 1.0, last_result=tenacity.Future.construct(1, "ok", False)
        )
        self.assertTrue(retry(retry_state))

        # plain_callable | retry_base (exercises __ror__ via reflection)
        retry2 = my_retry | tenacity.retry_if_exception_type(Exception)
        self.assertTrue(retry2(retry_state))

    def test_retry_and_with_plain_function(self) -> None:
        """Plain callables can be composed with retry_base via &."""

        def my_retry(retry_state: tenacity.RetryCallState) -> bool:
            return True

        # retry_base & plain_callable (exercises __and__ fallback)
        retry = tenacity.retry_if_result(lambda x: x == 1) & my_retry
        retry_state = make_retry_state(
            1, 1.0, last_result=tenacity.Future.construct(1, 1, False)
        )
        self.assertTrue(retry(retry_state))

        # plain_callable & retry_base (exercises __rand__ via reflection)
        retry2 = my_retry & tenacity.retry_if_result(lambda x: x == 1)
        self.assertTrue(retry2(retry_state))

    def test_retry_or_coalesces(self) -> None:
        """Multiple | operations flatten into a single retry_any."""
        a = tenacity.retry_if_exception_type(IOError)
        b = tenacity.retry_if_exception_type(OSError)
        c = tenacity.retry_if_exception_type(ValueError)

        combined = a | b | c
        self.assertIsInstance(combined, retry_any)
        self.assertEqual(len(combined.retries), 3)

    def test_retry_and_coalesces(self) -> None:
        """Multiple & operations flatten into a single retry_all."""
        a = tenacity.retry_if_result(lambda x: x == 1)
        b = tenacity.retry_if_result(lambda x: x > 0)
        c = tenacity.retry_if_result(lambda x: x < 10)

        combined = a & b & c
        self.assertIsInstance(combined, retry_all)
        self.assertEqual(len(combined.retries), 3)

    def _raise_try_again(self) -> None:
        self._attempts += 1
        if self._attempts < 3:
            raise tenacity.TryAgain

    def test_retry_try_again(self) -> None:
        self._attempts = 0
        Retrying(stop=tenacity.stop_after_attempt(5), retry=tenacity.retry_never)(
            self._raise_try_again
        )
        self.assertEqual(3, self._attempts)

    def test_retry_try_again_forever(self) -> None:
        def _r() -> None:
            raise tenacity.TryAgain

        r = Retrying(stop=tenacity.stop_after_attempt(5), retry=tenacity.retry_never)
        self.assertRaises(tenacity.RetryError, r, _r)
        self.assertEqual(5, r.statistics["attempt_number"])

    def test_retry_try_again_forever_reraise(self) -> None:
        def _r() -> None:
            raise tenacity.TryAgain

        r = Retrying(
            stop=tenacity.stop_after_attempt(5),
            retry=tenacity.retry_never,
            reraise=True,
        )
        self.assertRaises(tenacity.TryAgain, r, _r)
        self.assertEqual(5, r.statistics["attempt_number"])

    def test_retry_if_exception_message_negative_no_inputs(self) -> None:
        with self.assertRaises(TypeError):
            tenacity.retry_if_exception_message()

    def test_retry_if_exception_message_negative_too_many_inputs(self) -> None:
        with self.assertRaises(TypeError):
            tenacity.retry_if_exception_message(message="negative", match="negative")


class NoneReturnUntilAfterCount:
    """Holds counter state for invoking a method several times in a row."""

    def __init__(self, count: int) -> None:
        self.counter = 0
        self.count = count

    def go(self) -> typing.Any:
        """Return None until after count threshold has been crossed.

        Then return True.
        """
        if self.counter < self.count:
            self.counter += 1
            return None
        return True


class NoIOErrorAfterCount:
    """Holds counter state for invoking a method several times in a row."""

    def __init__(self, count: int) -> None:
        self.counter = 0
        self.count = count

    def go(self) -> typing.Any:
        """Raise an IOError until after count threshold has been crossed.

        Then return True.
        """
        if self.counter < self.count:
            self.counter += 1
            raise OSError("Hi there, I'm an IOError")
        return True


class NoNameErrorAfterCount:
    """Holds counter state for invoking a method several times in a row."""

    def __init__(self, count: int) -> None:
        self.counter = 0
        self.count = count

    def go(self) -> typing.Any:
        """Raise a NameError until after count threshold has been crossed.

        Then return True.
        """
        if self.counter < self.count:
            self.counter += 1
            raise NameError("Hi there, I'm a NameError")
        return True


class NoNameErrorCauseAfterCount:
    """Holds counter state for invoking a method several times in a row."""

    def __init__(self, count: int) -> None:
        self.counter = 0
        self.count = count

    def go2(self) -> typing.Any:
        raise NameError("Hi there, I'm a NameError")

    def go(self) -> typing.Any:
        """Raise an IOError with a NameError as cause until after count threshold has been crossed.

        Then return True.
        """
        if self.counter < self.count:
            self.counter += 1
            try:
                self.go2()
            except NameError as e:
                raise OSError from e

        return True


class NoIOErrorCauseAfterCount:
    """Holds counter state for invoking a method several times in a row."""

    def __init__(self, count: int) -> None:
        self.counter = 0
        self.count = count

    def go2(self) -> typing.Any:
        raise OSError("Hi there, I'm an IOError")

    def go(self) -> typing.Any:
        """Raise a NameError with an IOError as cause until after count threshold has been crossed.

        Then return True.
        """
        if self.counter < self.count:
            self.counter += 1
            try:
                self.go2()
            except OSError as e:
                raise NameError from e

        return True


class NameErrorUntilCount:
    """Holds counter state for invoking a method several times in a row."""

    derived_message = "Hi there, I'm a NameError"

    def __init__(self, count: int) -> None:
        self.counter = 0
        self.count = count

    def go(self) -> typing.Any:
        """Return True until after count threshold has been crossed.

        Then raise a NameError.
        """
        if self.counter < self.count:
            self.counter += 1
            return True
        raise NameError(self.derived_message)


class IOErrorUntilCount:
    """Holds counter state for invoking a method several times in a row."""

    def __init__(self, count: int) -> None:
        self.counter = 0
        self.count = count

    def go(self) -> typing.Any:
        """Return True until after count threshold has been crossed.

        Then raise an IOError.
        """
        if self.counter < self.count:
            self.counter += 1
            return True
        raise OSError("Hi there, I'm an IOError")


class CustomError(Exception):
    """This is a custom exception class.

    Note that For Python 2.x, we don't strictly need to extend BaseException,
    however, Python 3.x will complain. While this test suite won't run
    correctly under Python 3.x without extending from the Python exception
    hierarchy, the actual module code is backwards compatible Python 2.x and
    will allow for cases where exception classes don't extend from the
    hierarchy.
    """

    def __init__(self, value: str) -> None:
        self.value = value

    def __str__(self) -> str:
        return self.value


class NoCustomErrorAfterCount:
    """Holds counter state for invoking a method several times in a row."""

    derived_message = "This is a Custom exception class"

    def __init__(self, count: int) -> None:
        self.counter = 0
        self.count = count

    def go(self) -> typing.Any:
        """Raise a CustomError until after count threshold has been crossed.

        Then return True.
        """
        if self.counter < self.count:
            self.counter += 1
            raise CustomError(self.derived_message)
        return True


class CapturingHandler(logging.Handler):
    """Captures log records for inspection."""

    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
        super().__init__(*args, **kwargs)
        self.records: list[logging.LogRecord] = []

    def emit(self, record: logging.LogRecord) -> None:
        self.records.append(record)


def current_time_ms() -> int:
    return round(time.time() * 1000)


@retry(
    wait=tenacity.wait_fixed(0.05),
    retry=tenacity.retry_if_result(lambda result: result is None),
)
def _retryable_test_with_wait(thing: typing.Any) -> typing.Any:
    return thing.go()


@retry(
    stop=tenacity.stop_after_attempt(3),
    retry=tenacity.retry_if_result(lambda result: result is None),
)
def _retryable_test_with_stop(thing: typing.Any) -> typing.Any:
    return thing.go()


@retry(retry=tenacity.retry_if_exception_cause_type(NameError))
def _retryable_test_with_exception_cause_type(thing: typing.Any) -> typing.Any:
    return thing.go()


@retry(retry=tenacity.retry_if_exception_type(IOError))
def _retryable_test_with_exception_type_io(thing: typing.Any) -> typing.Any:
    return thing.go()


@retry(retry=tenacity.retry_if_not_exception_type(IOError))
def _retryable_test_if_not_exception_type_io(thing: typing.Any) -> typing.Any:
    return thing.go()


@retry(
    stop=tenacity.stop_after_attempt(3), retry=tenacity.retry_if_exception_type(IOError)
)
def _retryable_test_with_exception_type_io_attempt_limit(
    thing: typing.Any,
) -> typing.Any:
    return thing.go()


@retry(retry=tenacity.retry_unless_exception_type(NameError))
def _retryable_test_with_unless_exception_type_name(thing: typing.Any) -> typing.Any:
    return thing.go()


@retry(
    stop=tenacity.stop_after_attempt(3),
    retry=tenacity.retry_unless_exception_type(NameError),
)
def _retryable_test_with_unless_exception_type_name_attempt_limit(
    thing: typing.Any,
) -> typing.Any:
    return thing.go()


@retry(retry=tenacity.retry_unless_exception_type())
def _retrya
Download .txt
gitextract_hx8yszl8/

├── .editorconfig
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yaml
│       └── release.yml
├── .gitignore
├── .mergify.yml
├── .readthedocs.yml
├── LICENSE
├── doc/
│   └── source/
│       ├── api.rst
│       ├── changelog.rst
│       ├── conf.py
│       └── index.rst
├── pyproject.toml
├── releasenotes/
│   └── notes/
│       ├── Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml
│       ├── Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml
│       ├── add-async-actions-b249c527d99723bb.yaml
│       ├── add-re-pattern-to-match-types-6a4c1d9e64e2a5e1.yaml
│       ├── add-reno-d1ab5710f272650a.yaml
│       ├── add-retry_except_exception_type-31b31da1924d55f4.yaml
│       ├── add-stop-before-delay-a775f88ac872c923.yaml
│       ├── add-test-extra-55e869261b03e56d.yaml
│       ├── add_omitted_modules_to_import_all-2ab282f20a2c22f7.yaml
│       ├── add_retry_if_exception_cause_type-d16b918ace4ae0ad.yaml
│       ├── added_a_link_to_documentation-eefaf8f074b539f8.yaml
│       ├── after_log-50f4d73b24ce9203.yaml
│       ├── allow-mocking-of-nap-sleep-6679c50e702446f1.yaml
│       ├── annotate_code-197b93130df14042.yaml
│       ├── async-sleep-retrying-32de5866f5d041.yaml
│       ├── before_sleep_log-improvements-d8149274dfb37d7c.yaml
│       ├── clarify-reraise-option-6829667eacf4f599.yaml
│       ├── dependabot-for-github-actions-4d2464f3c0928463.yaml
│       ├── deprecate-initial-for-multiplier-c7b4e2d9f1a83065.yaml
│       ├── do_not_package_tests-fe5ac61940b0a5ed.yaml
│       ├── drop-deprecated-python-versions-69a05cb2e0f1034c.yaml
│       ├── drop-python-3.9-ecfa2d7db9773e96.yaml
│       ├── drop_deprecated-7ea90b212509b082.yaml
│       ├── export-convenience-symbols-981d9611c8b754f3.yaml
│       ├── fix-async-loop-with-result-f68e913ccb425aca.yaml
│       ├── fix-async-retry-type-overloads-27f3e0c239ed6b.yaml
│       ├── fix-local-context-overwrite-94190ba06a481631.yaml
│       ├── fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml
│       ├── fix-setuptools-config-3af71aa3592b6948.yaml
│       ├── fix-wait-typing-b26eecdb6cc0a1de.yaml
│       ├── fix_async-52b6594c8e75c4bc.yaml
│       ├── logging-protocol-a4cf0f786f21e4ee.yaml
│       ├── make-logger-more-compatible-5da1ddf1bab77047.yaml
│       ├── no-async-iter-6132a42e52348a75.yaml
│       ├── pr320-py3-only-wheel-tag.yaml
│       ├── py36_plus-c425fb3aa17c6682.yaml
│       ├── remove-py36-876c0416cf279d15.yaml
│       ├── retrycallstate-repr-94947f7b00ee15e1.yaml
│       ├── some-slug-for-preserve-defaults-86682846dfa18005.yaml
│       ├── sphinx_define_error-642c9cd5c165d39a.yaml
│       ├── support-py3.14-14928188cab53b99.yaml
│       ├── support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml
│       ├── timedelta-for-stop-ef6bf71b88ce9988.yaml
│       ├── trio-support-retry-22bd544800cd1f36.yaml
│       ├── wait-exponential-jitter-min-timedelta-a8e3c1f4b7d29e50.yaml
│       ├── wait-random-exponential-min-2a4b7eed9f002436.yaml
│       └── wait_exponential_jitter-6ffc81dddcbaa6d3.yaml
├── reno.yaml
├── tenacity/
│   ├── __init__.py
│   ├── _utils.py
│   ├── after.py
│   ├── asyncio/
│   │   ├── __init__.py
│   │   └── retry.py
│   ├── before.py
│   ├── before_sleep.py
│   ├── nap.py
│   ├── py.typed
│   ├── retry.py
│   ├── stop.py
│   ├── tornadoweb.py
│   └── wait.py
└── tests/
    ├── __init__.py
    ├── test_after.py
    ├── test_asyncio.py
    ├── test_issue_478.py
    ├── test_tenacity.py
    ├── test_tornado.py
    └── test_utils.py
Download .txt
SYMBOL INDEX (485 symbols across 18 files)

FILE: tenacity/__init__.py
  class IterState (line 112) | class IterState:
    method reset (line 120) | def reset(self) -> None:
  class TryAgain (line 127) | class TryAgain(Exception):
  class DoAttempt (line 134) | class DoAttempt:
  class DoSleep (line 138) | class DoSleep(float):
  class BaseAction (line 142) | class BaseAction:
    method __repr__ (line 154) | def __repr__(self) -> str:
    method __str__ (line 160) | def __str__(self) -> str:
  class RetryAction (line 164) | class RetryAction(BaseAction):
    method __init__ (line 168) | def __init__(self, sleep: t.SupportsFloat) -> None:
  function _first_set (line 175) | def _first_set(first: t.Any | object, second: t.Any) -> t.Any:
  class RetryError (line 179) | class RetryError(Exception):
    method __init__ (line 182) | def __init__(self, last_attempt: "Future") -> None:
    method reraise (line 186) | def reraise(self) -> t.NoReturn:
    method __str__ (line 191) | def __str__(self) -> str:
  class AttemptManager (line 195) | class AttemptManager:
    method __init__ (line 198) | def __init__(self, retry_state: "RetryCallState"):
    method __enter__ (line 201) | def __enter__(self) -> None:
    method __exit__ (line 204) | def __exit__(
    method __aenter__ (line 217) | async def __aenter__(self) -> None:
    method __aexit__ (line 220) | async def __aexit__(
  class BaseRetrying (line 229) | class BaseRetrying(ABC):
    method __init__ (line 230) | def __init__(
    method copy (line 259) | def copy(
    method __getstate__ (line 294) | def __getstate__(self) -> dict[str, t.Any]:
    method __setstate__ (line 298) | def __setstate__(self, state: dict[str, t.Any]) -> None:
    method __str__ (line 302) | def __str__(self) -> str:
    method __repr__ (line 305) | def __repr__(self) -> str:
    method statistics (line 318) | def statistics(self) -> dict[str, t.Any]:
    method iter_state (line 344) | def iter_state(self) -> IterState:
    method wraps (line 349) | def wraps(self, f: t.Callable[P, R]) -> "_RetryDecorated[P, R]":
    method begin (line 378) | def begin(self) -> None:
    method _add_action_func (line 385) | def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
    method _run_retry (line 388) | def _run_retry(self, retry_state: "RetryCallState") -> None:
    method _run_wait (line 391) | def _run_wait(self, retry_state: "RetryCallState") -> None:
    method _run_stop (line 399) | def _run_stop(self, retry_state: "RetryCallState") -> None:
    method iter (line 403) | def iter(self, retry_state: "RetryCallState") -> DoAttempt | DoSleep |...
    method _begin_iter (line 410) | def _begin_iter(self, retry_state: "RetryCallState") -> None:
    method _post_retry_check_actions (line 427) | def _post_retry_check_actions(self, retry_state: "RetryCallState") -> ...
    method _post_stop_check_actions (line 439) | def _post_stop_check_actions(self, retry_state: "RetryCallState") -> N...
    method __iter__ (line 469) | def __iter__(self) -> t.Generator[AttemptManager, None, None]:
    method __call__ (line 484) | def __call__(
  class Retrying (line 493) | class Retrying(BaseRetrying):
    method __call__ (line 496) | def __call__(
  class Future (line 521) | class Future(futures.Future[t.Any]):
    method __init__ (line 524) | def __init__(self, attempt_number: int) -> None:
    method failed (line 529) | def failed(self) -> bool:
    method construct (line 534) | def construct(
  class RetryCallState (line 546) | class RetryCallState:
    method __init__ (line 549) | def __init__(
    method get_fn_name (line 580) | def get_fn_name(self) -> str:
    method seconds_since_start (line 592) | def seconds_since_start(self) -> float | None:
    method prepare_for_next_attempt (line 597) | def prepare_for_next_attempt(self) -> None:
    method set_result (line 603) | def set_result(self, val: t.Any) -> None:
    method set_exception (line 609) | def set_exception(
    method __repr__ (line 620) | def __repr__(self) -> str:
  class _RetryDecorated (line 634) | class _RetryDecorated(t.Protocol[P, R]):
    method retry_with (line 643) | def retry_with(self, *args: t.Any, **kwargs: t.Any) -> "_RetryDecorate...
    method __call__ (line 645) | def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ...
  class _AsyncRetryDecorator (line 648) | class _AsyncRetryDecorator(t.Protocol):
    method __call__ (line 650) | def __call__(
    method __call__ (line 654) | def __call__(
    method __call__ (line 658) | def __call__(
    method __call__ (line 662) | def __call__(
  function retry (line 668) | def retry(func: t.Callable[P, R]) -> _RetryDecorated[P, R]: ...
  function retry (line 672) | def retry(
  function retry (line 690) | def retry(
  function retry (line 707) | def retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:

FILE: tenacity/_utils.py
  class LoggerProtocol (line 28) | class LoggerProtocol(typing.Protocol):
    method log (line 35) | def log(self, level: int, msg: str, *args: typing.Any) -> typing.Any: ...
  function find_ordinal (line 38) | def find_ordinal(pos_num: int) -> str:
  function to_ordinal (line 57) | def to_ordinal(pos_num: int) -> str:
  function get_callback_name (line 61) | def get_callback_name(cb: typing.Callable[..., typing.Any]) -> str:
  function to_seconds (line 84) | def to_seconds(time_unit: time_unit_type) -> float:
  function is_coroutine_callable (line 90) | def is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool:
  function wrap_to_async_func (line 100) | def wrap_to_async_func(

FILE: tenacity/after.py
  function after_nothing (line 25) | def after_nothing(retry_state: "RetryCallState") -> None:
  function after_log (line 29) | def after_log(

FILE: tenacity/asyncio/__init__.py
  function _portable_async_sleep (line 56) | def _portable_async_sleep(seconds: float) -> t.Awaitable[None]:
  class AsyncRetrying (line 74) | class AsyncRetrying(BaseRetrying):
    method __init__ (line 75) | def __init__(
    method __call__ (line 111) | async def __call__(  # type: ignore[override]
    method _add_action_func (line 136) | def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:
    method _run_retry (line 139) | async def _run_retry(self, retry_state: "RetryCallState") -> None:  # ...
    method _run_wait (line 144) | async def _run_wait(self, retry_state: "RetryCallState") -> None:  # t...
    method _run_stop (line 152) | async def _run_stop(self, retry_state: "RetryCallState") -> None:  # t...
    method iter (line 158) | async def iter(self, retry_state: "RetryCallState") -> DoAttempt | DoS...
    method __iter__ (line 165) | def __iter__(self) -> t.Generator[AttemptManager, None, None]:
    method __aiter__ (line 168) | def __aiter__(self) -> "AsyncRetrying":
    method __anext__ (line 173) | async def __anext__(self) -> AttemptManager:
    method wraps (line 186) | def wraps(self, fn: t.Callable[P, R]) -> _RetryDecorated[P, R]:

FILE: tenacity/asyncio/retry.py
  class async_retry_base (line 25) | class async_retry_base(retry_base):
    method __call__ (line 29) | async def __call__(self, retry_state: "RetryCallState") -> bool:  # ty...
    method __and__ (line 32) | def __and__(  # type: ignore[override]
    method __rand__ (line 37) | def __rand__(  # type: ignore[misc,override]
    method __or__ (line 42) | def __or__(  # type: ignore[override]
    method __ror__ (line 47) | def __ror__(  # type: ignore[misc,override]
  class retry_if_exception (line 58) | class retry_if_exception(async_retry_base):
    method __init__ (line 61) | def __init__(
    method __call__ (line 66) | async def __call__(self, retry_state: "RetryCallState") -> bool:  # ty...
  class retry_if_result (line 78) | class retry_if_result(async_retry_base):
    method __init__ (line 81) | def __init__(
    method __call__ (line 86) | async def __call__(self, retry_state: "RetryCallState") -> bool:  # ty...
  class retry_any (line 95) | class retry_any(async_retry_base):
    method __init__ (line 98) | def __init__(self, *retries: retry_base | async_retry_base) -> None:
    method __call__ (line 101) | async def __call__(self, retry_state: "RetryCallState") -> bool:  # ty...
    method __ror__ (line 109) | def __ror__(  # type: ignore[misc,override]
  class retry_all (line 117) | class retry_all(async_retry_base):
    method __init__ (line 120) | def __init__(self, *retries: retry_base | async_retry_base) -> None:
    method __call__ (line 123) | async def __call__(self, retry_state: "RetryCallState") -> bool:  # ty...
    method __rand__ (line 131) | def __rand__(  # type: ignore[misc,override]

FILE: tenacity/before.py
  function before_nothing (line 25) | def before_nothing(retry_state: "RetryCallState") -> None:
  function before_log (line 29) | def before_log(

FILE: tenacity/before_sleep.py
  function before_sleep_nothing (line 26) | def before_sleep_nothing(retry_state: "RetryCallState") -> None:
  function before_sleep_log (line 30) | def before_sleep_log(

FILE: tenacity/nap.py
  function sleep (line 25) | def sleep(seconds: float) -> None:
  class sleep_using_event (line 34) | class sleep_using_event:
    method __init__ (line 37) | def __init__(self, event: "threading.Event") -> None:
    method __call__ (line 40) | def __call__(self, timeout: float | None) -> None:

FILE: tenacity/retry.py
  class retry_base (line 25) | class retry_base(abc.ABC):
    method __call__ (line 29) | def __call__(self, retry_state: "RetryCallState") -> bool:
    method __and__ (line 32) | def __and__(self, other: "RetryBaseT") -> "retry_all":
    method __rand__ (line 40) | def __rand__(self, other: "RetryBaseT") -> "retry_all":
    method __or__ (line 46) | def __or__(self, other: "RetryBaseT") -> "retry_any":
    method __ror__ (line 54) | def __ror__(self, other: "RetryBaseT") -> "retry_any":
  class _retry_never (line 64) | class _retry_never(retry_base):
    method __call__ (line 67) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class _retry_always (line 74) | class _retry_always(retry_base):
    method __call__ (line 77) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class retry_if_exception (line 84) | class retry_if_exception(retry_base):
    method __init__ (line 87) | def __init__(self, predicate: typing.Callable[[BaseException], bool]) ...
    method __call__ (line 90) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class retry_if_exception_type (line 102) | class retry_if_exception_type(retry_if_exception):
    method __init__ (line 105) | def __init__(
    method _check (line 113) | def _check(self, e: BaseException) -> bool:
  class retry_if_not_exception_type (line 117) | class retry_if_not_exception_type(retry_if_exception):
    method __init__ (line 120) | def __init__(
    method _check (line 128) | def _check(self, e: BaseException) -> bool:
  class retry_unless_exception_type (line 132) | class retry_unless_exception_type(retry_if_exception):
    method __init__ (line 135) | def __init__(
    method _check (line 143) | def _check(self, e: BaseException) -> bool:
    method __call__ (line 146) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class retry_if_exception_cause_type (line 160) | class retry_if_exception_cause_type(retry_base):
    method __init__ (line 167) | def __init__(
    method __call__ (line 174) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class retry_if_result (line 188) | class retry_if_result(retry_base):
    method __init__ (line 191) | def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> ...
    method __call__ (line 194) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class retry_if_not_result (line 203) | class retry_if_not_result(retry_base):
    method __init__ (line 206) | def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> ...
    method __call__ (line 209) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class retry_if_exception_message (line 218) | class retry_if_exception_message(retry_if_exception):
    method __init__ (line 221) | def __init__(
    method _check (line 240) | def _check(self, exception: BaseException) -> bool:
  class retry_if_not_exception_message (line 247) | class retry_if_not_exception_message(retry_if_exception_message):
    method _check (line 250) | def _check(self, exception: BaseException) -> bool:
    method __call__ (line 253) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class retry_any (line 266) | class retry_any(retry_base):
    method __init__ (line 269) | def __init__(self, *retries: "RetryBaseT") -> None:
    method __call__ (line 272) | def __call__(self, retry_state: "RetryCallState") -> bool:
    method __ror__ (line 275) | def __ror__(self, other: "RetryBaseT") -> "retry_any":
  class retry_all (line 281) | class retry_all(retry_base):
    method __init__ (line 284) | def __init__(self, *retries: "RetryBaseT") -> None:
    method __call__ (line 287) | def __call__(self, retry_state: "RetryCallState") -> bool:
    method __rand__ (line 290) | def __rand__(self, other: "RetryBaseT") -> "retry_all":

FILE: tenacity/stop.py
  class stop_base (line 27) | class stop_base(abc.ABC):
    method __call__ (line 31) | def __call__(self, retry_state: "RetryCallState") -> bool:
    method __and__ (line 34) | def __and__(self, other: "stop_base") -> "stop_all":
    method __or__ (line 37) | def __or__(self, other: "stop_base") -> "stop_any":
  class stop_any (line 44) | class stop_any(stop_base):
    method __init__ (line 47) | def __init__(self, *stops: stop_base) -> None:
    method __call__ (line 50) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class stop_all (line 54) | class stop_all(stop_base):
    method __init__ (line 57) | def __init__(self, *stops: stop_base) -> None:
    method __call__ (line 60) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class _stop_never (line 64) | class _stop_never(stop_base):
    method __call__ (line 67) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class stop_when_event_set (line 74) | class stop_when_event_set(stop_base):
    method __init__ (line 77) | def __init__(self, event: "threading.Event") -> None:
    method __call__ (line 80) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class stop_after_attempt (line 84) | class stop_after_attempt(stop_base):
    method __init__ (line 87) | def __init__(self, max_attempt_number: int) -> None:
    method __call__ (line 90) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class stop_after_delay (line 94) | class stop_after_delay(stop_base):
    method __init__ (line 104) | def __init__(self, max_delay: _utils.time_unit_type) -> None:
    method __call__ (line 107) | def __call__(self, retry_state: "RetryCallState") -> bool:
  class stop_before_delay (line 113) | class stop_before_delay(stop_base):
    method __init__ (line 121) | def __init__(self, max_delay: _utils.time_unit_type) -> None:
    method __call__ (line 124) | def __call__(self, retry_state: "RetryCallState") -> bool:

FILE: tenacity/tornadoweb.py
  class TornadoRetrying (line 28) | class TornadoRetrying(BaseRetrying):
    method __init__ (line 31) | def __init__(
    method __call__ (line 40) | def __call__(  # type: ignore[override]

FILE: tenacity/wait.py
  class wait_base (line 28) | class wait_base(abc.ABC):
    method __call__ (line 32) | def __call__(self, retry_state: "RetryCallState") -> float:
    method __add__ (line 35) | def __add__(self, other: "wait_base") -> "wait_combine":
    method __radd__ (line 38) | def __radd__(self, other: "wait_base") -> "wait_combine | wait_base":
  class wait_fixed (line 48) | class wait_fixed(wait_base):
    method __init__ (line 51) | def __init__(self, wait: _utils.time_unit_type) -> None:
    method __call__ (line 54) | def __call__(self, retry_state: "RetryCallState") -> float:
  class wait_none (line 58) | class wait_none(wait_fixed):
    method __init__ (line 61) | def __init__(self) -> None:
  class wait_random (line 65) | class wait_random(wait_base):
    method __init__ (line 68) | def __init__(
    method __call__ (line 74) | def __call__(self, retry_state: "RetryCallState") -> float:
  class wait_combine (line 80) | class wait_combine(wait_base):
    method __init__ (line 83) | def __init__(self, *strategies: wait_base) -> None:
    method __call__ (line 86) | def __call__(self, retry_state: "RetryCallState") -> float:
  class wait_chain (line 90) | class wait_chain(wait_base):
    method __init__ (line 106) | def __init__(self, *strategies: wait_base) -> None:
    method __call__ (line 109) | def __call__(self, retry_state: "RetryCallState") -> float:
  class wait_exception (line 115) | class wait_exception(wait_base):
    method __init__ (line 141) | def __init__(self, predicate: typing.Callable[[BaseException], float])...
    method __call__ (line 144) | def __call__(self, retry_state: "RetryCallState") -> float:
  class wait_incrementing (line 154) | class wait_incrementing(wait_base):
    method __init__ (line 161) | def __init__(
    method __call__ (line 171) | def __call__(self, retry_state: "RetryCallState") -> float:
  class wait_exponential (line 176) | class wait_exponential(wait_base):
    method __init__ (line 189) | def __init__(
    method __call__ (line 201) | def __call__(self, retry_state: "RetryCallState") -> float:
  class wait_random_exponential (line 210) | class wait_random_exponential(wait_exponential):
    method __call__ (line 236) | def __call__(self, retry_state: "RetryCallState") -> float:
  class wait_exponential_jitter (line 241) | class wait_exponential_jitter(wait_base):
    method __init__ (line 253) | def __init__(
    method __call__ (line 281) | def __call__(self, retry_state: "RetryCallState") -> float:

FILE: tests/test_after.py
  class TestAfterLogFormat (line 13) | class TestAfterLogFormat(unittest.TestCase):
    method setUp (line 14) | def setUp(self) -> None:
    method test_01_default (line 26) | def test_01_default(self) -> None:
    method test_02_none_seconds_since_start (line 48) | def test_02_none_seconds_since_start(self) -> None:
    method test_02_custom_sec_format (line 66) | def test_02_custom_sec_format(self) -> None:

FILE: tests/test_asyncio.py
  function asynctest (line 54) | def asynctest(callable_: _F) -> Callable[..., Any]:
  function _async_function (line 62) | async def _async_function(thing: NoIOErrorAfterCount) -> Any:
  function _retryable_coroutine (line 68) | async def _retryable_coroutine(thing: NoIOErrorAfterCount) -> Any:
  function _retryable_coroutine_with_2_attempts (line 74) | async def _retryable_coroutine_with_2_attempts(thing: NoIOErrorAfterCoun...
  class TestAsyncio (line 79) | class TestAsyncio(unittest.TestCase):
    method test_retry (line 81) | async def test_retry(self) -> None:
    method test_iscoroutinefunction (line 87) | async def test_iscoroutinefunction(self) -> None:
    method test_retry_using_async_retying (line 92) | async def test_retry_using_async_retying(self) -> None:
    method test_stop_after_attempt (line 99) | async def test_stop_after_attempt(self) -> None:
    method test_repr (line 106) | def test_repr(self) -> None:
    method test_retry_attributes (line 109) | def test_retry_attributes(self) -> None:
    method test_retry_preserves_argument_defaults (line 113) | def test_retry_preserves_argument_defaults(self) -> None:
    method test_attempt_number_is_correct_for_interleaved_coroutines (line 136) | async def test_attempt_number_is_correct_for_interleaved_coroutines(se...
  class TestAsyncEnabled (line 163) | class TestAsyncEnabled(unittest.TestCase):
    method test_enabled_false_skips_retry (line 165) | async def test_enabled_false_skips_retry(self) -> None:
  class TestTrio (line 181) | class TestTrio(unittest.TestCase):
    method test_trio_basic (line 182) | def test_trio_basic(self) -> None:
  class TestContextManager (line 195) | class TestContextManager(unittest.TestCase):
    method test_do_max_attempts (line 197) | async def test_do_max_attempts(self) -> None:
    method test_async_with_attempt_manager (line 211) | async def test_async_with_attempt_manager(self) -> None:
    method test_reraise (line 226) | async def test_reraise(self) -> None:
    method test_sleeps (line 242) | async def test_sleeps(self) -> None:
    method test_retry_with_result (line 256) | async def test_retry_with_result(self) -> None:
    method test_retry_with_async_result (line 275) | async def test_retry_with_async_result(self) -> None:
    method test_retry_with_async_exc (line 299) | async def test_retry_with_async_exc(self) -> None:
    method test_retry_with_async_result_or (line 328) | async def test_retry_with_async_result_or(self) -> None:
    method test_retry_with_async_result_ror (line 359) | async def test_retry_with_async_result_ror(self) -> None:
    method test_retry_with_async_result_and (line 390) | async def test_retry_with_async_result_and(self) -> None:
    method test_retry_with_async_result_rand (line 413) | async def test_retry_with_async_result_rand(self) -> None:
    method test_async_retying_iterator (line 436) | async def test_async_retying_iterator(self) -> None:
  class TestDecoratorWrapper (line 444) | class TestDecoratorWrapper(unittest.TestCase):
    method test_retry_function_attributes (line 446) | async def test_retry_function_attributes(self) -> None:
  function my_async_sleep (line 504) | async def my_async_sleep(x: float) -> None:
  function foo (line 509) | async def foo() -> None:
  class TestSyncFunctionWithAsyncSleep (line 513) | class TestSyncFunctionWithAsyncSleep(unittest.TestCase):
    method test_sync_function_with_async_sleep (line 515) | async def test_sync_function_with_async_sleep(self) -> None:

FILE: tests/test_issue_478.py
  function asynctest (line 9) | def asynctest(
  class TestIssue478 (line 22) | class TestIssue478(unittest.TestCase):
    method test_issue (line 23) | def test_issue(self) -> None:
    method test_async (line 71) | async def test_async(self) -> None:

FILE: tests/test_tenacity.py
  function _make_unset_exception (line 37) | def _make_unset_exception(func_name: str, **kwargs: typing.Any) -> TypeE...
  function _set_delay_since_start (line 46) | def _set_delay_since_start(retry_state: RetryCallState, delay: typing.An...
  function make_retry_state (line 54) | def make_retry_state(
  class TestBase (line 87) | class TestBase(unittest.TestCase):
    method test_retrying_repr (line 88) | def test_retrying_repr(self) -> None:
    method test_callstate_repr (line 97) | def test_callstate_repr(self) -> None:
  class TestRetryingName (line 113) | class TestRetryingName(unittest.TestCase):
    method test_str_default (line 114) | def test_str_default(self) -> None:
    method test_str_with_name (line 118) | def test_str_with_name(self) -> None:
    method test_str_preserved_by_copy (line 122) | def test_str_preserved_by_copy(self) -> None:
    method test_str_overridden_by_copy (line 127) | def test_str_overridden_by_copy(self) -> None:
    method test_get_fn_name_decorator (line 132) | def test_get_fn_name_decorator(self) -> None:
    method test_get_fn_name_context_manager_no_name (line 148) | def test_get_fn_name_context_manager_no_name(self) -> None:
    method test_get_fn_name_context_manager_with_name (line 154) | def test_get_fn_name_context_manager_with_name(self) -> None:
    method test_logging_uses_name (line 160) | def test_logging_uses_name(self) -> None:
  class TestStopConditions (line 180) | class TestStopConditions(unittest.TestCase):
    method test_never_stop (line 181) | def test_never_stop(self) -> None:
    method test_stop_any (line 185) | def test_stop_any(self) -> None:
    method test_stop_all (line 200) | def test_stop_all(self) -> None:
    method test_stop_or (line 215) | def test_stop_or(self) -> None:
    method test_stop_and (line 228) | def test_stop_and(self) -> None:
    method test_stop_after_attempt (line 241) | def test_stop_after_attempt(self) -> None:
    method test_stop_after_delay (line 247) | def test_stop_after_delay(self) -> None:
    method test_stop_before_delay (line 255) | def test_stop_before_delay(self) -> None:
    method test_legacy_explicit_stop_type (line 270) | def test_legacy_explicit_stop_type(self) -> None:
    method test_stop_func_with_retry_state (line 273) | def test_stop_func_with_retry_state(self) -> None:
  class TestWaitConditions (line 284) | class TestWaitConditions(unittest.TestCase):
    method test_no_sleep (line 285) | def test_no_sleep(self) -> None:
    method test_fixed_sleep (line 289) | def test_fixed_sleep(self) -> None:
    method test_incrementing_sleep (line 295) | def test_incrementing_sleep(self) -> None:
    method test_random_sleep (line 308) | def test_random_sleep(self) -> None:
    method test_random_sleep_withoutmin_ (line 325) | def test_random_sleep_withoutmin_(self) -> None:
    method test_exponential (line 339) | def test_exponential(self) -> None:
    method test_exponential_with_max_wait (line 350) | def test_exponential_with_max_wait(self) -> None:
    method test_exponential_with_min_wait (line 362) | def test_exponential_with_min_wait(self) -> None:
    method test_exponential_with_max_wait_and_multiplier (line 374) | def test_exponential_with_max_wait_and_multiplier(self) -> None:
    method test_exponential_with_min_wait_and_multiplier (line 386) | def test_exponential_with_min_wait_and_multiplier(self) -> None:
    method test_exponential_with_min_wait_andmax__wait (line 398) | def test_exponential_with_min_wait_andmax__wait(self) -> None:
    method test_legacy_explicit_wait_type (line 416) | def test_legacy_explicit_wait_type(self) -> None:
    method test_wait_func (line 419) | def test_wait_func(self) -> None:
    method test_wait_combine (line 428) | def test_wait_combine(self) -> None:
    method test_wait_exception (line 440) | def test_wait_exception(self) -> None:
    method test_wait_double_sum (line 458) | def test_wait_double_sum(self) -> None:
    method test_wait_triple_sum (line 466) | def test_wait_triple_sum(self) -> None:
    method test_wait_arbitrary_sum (line 478) | def test_wait_arbitrary_sum(self) -> None:
    method _assert_range (line 495) | def _assert_range(self, wait: float, min_: float, max_: float) -> None:
    method _assert_inclusive_range (line 499) | def _assert_inclusive_range(self, wait: float, low: float, high: float...
    method _assert_inclusive_epsilon (line 503) | def _assert_inclusive_epsilon(
    method test_wait_chain (line 509) | def test_wait_chain(self) -> None:
    method test_wait_chain_multiple_invocations (line 527) | def test_wait_chain_multiple_invocations(self) -> None:
    method test_wait_random_exponential (line 549) | def test_wait_random_exponential(self) -> None:
    method test_wait_random_exponential_statistically (line 579) | def test_wait_random_exponential_statistically(self) -> None:
    method test_wait_exponential_jitter (line 598) | def test_wait_exponential_jitter(self) -> None:
    method test_wait_exponential_jitter_min (line 621) | def test_wait_exponential_jitter_min(self) -> None:
    method test_wait_exponential_jitter_timedelta (line 631) | def test_wait_exponential_jitter_timedelta(self) -> None:
    method test_wait_exponential_jitter_multiplier (line 644) | def test_wait_exponential_jitter_multiplier(self) -> None:
    method test_wait_exponential_jitter_initial_deprecated (line 651) | def test_wait_exponential_jitter_initial_deprecated(self) -> None:
    method test_wait_exponential_jitter_initial_and_multiplier_raises (line 657) | def test_wait_exponential_jitter_initial_and_multiplier_raises(self) -...
    method test_wait_retry_state_attributes (line 661) | def test_wait_retry_state_attributes(self) -> None:
  class TestRetryConditions (line 707) | class TestRetryConditions(unittest.TestCase):
    method test_retry_if_result (line 708) | def test_retry_if_result(self) -> None:
    method test_retry_if_not_result (line 718) | def test_retry_if_not_result(self) -> None:
    method test_retry_any (line 728) | def test_retry_any(self) -> None:
    method test_retry_all (line 743) | def test_retry_all(self) -> None:
    method test_retry_and (line 758) | def test_retry_and(self) -> None:
    method test_retry_or (line 772) | def test_retry_or(self) -> None:
    method test_retry_or_with_plain_function (line 786) | def test_retry_or_with_plain_function(self) -> None:
    method test_retry_and_with_plain_function (line 803) | def test_retry_and_with_plain_function(self) -> None:
    method test_retry_or_coalesces (line 820) | def test_retry_or_coalesces(self) -> None:
    method test_retry_and_coalesces (line 830) | def test_retry_and_coalesces(self) -> None:
    method _raise_try_again (line 840) | def _raise_try_again(self) -> None:
    method test_retry_try_again (line 845) | def test_retry_try_again(self) -> None:
    method test_retry_try_again_forever (line 852) | def test_retry_try_again_forever(self) -> None:
    method test_retry_try_again_forever_reraise (line 860) | def test_retry_try_again_forever_reraise(self) -> None:
    method test_retry_if_exception_message_negative_no_inputs (line 872) | def test_retry_if_exception_message_negative_no_inputs(self) -> None:
    method test_retry_if_exception_message_negative_too_many_inputs (line 876) | def test_retry_if_exception_message_negative_too_many_inputs(self) -> ...
  class NoneReturnUntilAfterCount (line 881) | class NoneReturnUntilAfterCount:
    method __init__ (line 884) | def __init__(self, count: int) -> None:
    method go (line 888) | def go(self) -> typing.Any:
  class NoIOErrorAfterCount (line 899) | class NoIOErrorAfterCount:
    method __init__ (line 902) | def __init__(self, count: int) -> None:
    method go (line 906) | def go(self) -> typing.Any:
  class NoNameErrorAfterCount (line 917) | class NoNameErrorAfterCount:
    method __init__ (line 920) | def __init__(self, count: int) -> None:
    method go (line 924) | def go(self) -> typing.Any:
  class NoNameErrorCauseAfterCount (line 935) | class NoNameErrorCauseAfterCount:
    method __init__ (line 938) | def __init__(self, count: int) -> None:
    method go2 (line 942) | def go2(self) -> typing.Any:
    method go (line 945) | def go(self) -> typing.Any:
  class NoIOErrorCauseAfterCount (line 960) | class NoIOErrorCauseAfterCount:
    method __init__ (line 963) | def __init__(self, count: int) -> None:
    method go2 (line 967) | def go2(self) -> typing.Any:
    method go (line 970) | def go(self) -> typing.Any:
  class NameErrorUntilCount (line 985) | class NameErrorUntilCount:
    method __init__ (line 990) | def __init__(self, count: int) -> None:
    method go (line 994) | def go(self) -> typing.Any:
  class IOErrorUntilCount (line 1005) | class IOErrorUntilCount:
    method __init__ (line 1008) | def __init__(self, count: int) -> None:
    method go (line 1012) | def go(self) -> typing.Any:
  class CustomError (line 1023) | class CustomError(Exception):
    method __init__ (line 1034) | def __init__(self, value: str) -> None:
    method __str__ (line 1037) | def __str__(self) -> str:
  class NoCustomErrorAfterCount (line 1041) | class NoCustomErrorAfterCount:
    method __init__ (line 1046) | def __init__(self, count: int) -> None:
    method go (line 1050) | def go(self) -> typing.Any:
  class CapturingHandler (line 1061) | class CapturingHandler(logging.Handler):
    method __init__ (line 1064) | def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
    method emit (line 1068) | def emit(self, record: logging.LogRecord) -> None:
  function current_time_ms (line 1072) | def current_time_ms() -> int:
  function _retryable_test_with_wait (line 1080) | def _retryable_test_with_wait(thing: typing.Any) -> typing.Any:
  function _retryable_test_with_stop (line 1088) | def _retryable_test_with_stop(thing: typing.Any) -> typing.Any:
  function _retryable_test_with_exception_cause_type (line 1093) | def _retryable_test_with_exception_cause_type(thing: typing.Any) -> typi...
  function _retryable_test_with_exception_type_io (line 1098) | def _retryable_test_with_exception_type_io(thing: typing.Any) -> typing....
  function _retryable_test_if_not_exception_type_io (line 1103) | def _retryable_test_if_not_exception_type_io(thing: typing.Any) -> typin...
  function _retryable_test_with_exception_type_io_attempt_limit (line 1110) | def _retryable_test_with_exception_type_io_attempt_limit(
  function _retryable_test_with_unless_exception_type_name (line 1117) | def _retryable_test_with_unless_exception_type_name(thing: typing.Any) -...
  function _retryable_test_with_unless_exception_type_name_attempt_limit (line 1125) | def _retryable_test_with_unless_exception_type_name_attempt_limit(
  function _retryable_test_with_unless_exception_type_no_input (line 1132) | def _retryable_test_with_unless_exception_type_no_input(
  function _retryable_test_if_exception_message_message (line 1144) | def _retryable_test_if_exception_message_message(thing: typing.Any) -> t...
  function _retryable_test_if_not_exception_message_message (line 1153) | def _retryable_test_if_not_exception_message_message(thing: typing.Any) ...
  function _retryable_test_if_exception_message_match (line 1162) | def _retryable_test_if_exception_message_match(thing: typing.Any) -> typ...
  function _retryable_test_if_not_exception_message_match (line 1171) | def _retryable_test_if_not_exception_message_match(thing: typing.Any) ->...
  function _retryable_test_not_exception_message_delay (line 1180) | def _retryable_test_not_exception_message_delay(thing: typing.Any) -> ty...
  function _retryable_default (line 1185) | def _retryable_default(thing: typing.Any) -> typing.Any:
  function _retryable_default_f (line 1190) | def _retryable_default_f(thing: typing.Any) -> typing.Any:
  function _retryable_test_with_exception_type_custom (line 1195) | def _retryable_test_with_exception_type_custom(thing: typing.Any) -> typ...
  function _retryable_test_with_exception_type_custom_attempt_limit (line 1203) | def _retryable_test_with_exception_type_custom_attempt_limit(
  class TestDecoratorWrapper (line 1209) | class TestDecoratorWrapper(unittest.TestCase):
    method test_with_wait (line 1210) | def test_with_wait(self) -> None:
    method test_with_stop_on_return_value (line 1217) | def test_with_stop_on_return_value(self) -> None:
    method test_with_stop_on_exception (line 1227) | def test_with_stop_on_exception(self) -> None:
    method test_retry_if_exception_of_type (line 1235) | def test_retry_if_exception_of_type(self) -> None:
    method test_retry_except_exception_of_type (line 1256) | def test_retry_except_exception_of_type(self) -> None:
    method test_retry_until_exception_of_type_attempt_number (line 1268) | def test_retry_until_exception_of_type_attempt_number(self) -> None:
    method test_retry_until_exception_of_type_no_type (line 1280) | def test_retry_until_exception_of_type_no_type(self) -> None:
    method test_retry_until_exception_of_type_wrong_exception (line 1295) | def test_retry_until_exception_of_type_wrong_exception(self) -> None:
    method test_retry_if_exception_message (line 1306) | def test_retry_if_exception_message(self) -> None:
    method test_retry_if_not_exception_message (line 1315) | def test_retry_if_not_exception_message(self) -> None:
    method test_retry_if_not_exception_message_delay (line 1326) | def test_retry_if_not_exception_message_delay(self) -> None:
    method test_retry_if_exception_message_match (line 1336) | def test_retry_if_exception_message_match(self) -> None:
    method test_retry_if_not_exception_message_match (line 1344) | def test_retry_if_not_exception_message_match(self) -> None:
    method test_retry_if_exception_cause_type (line 1355) | def test_retry_if_exception_cause_type(self) -> None:
    method test_retry_preserves_argument_defaults (line 1366) | def test_retry_preserves_argument_defaults(self) -> None:
    method test_defaults (line 1388) | def test_defaults(self) -> None:
    method test_retry_function_object (line 1394) | def test_retry_function_object(self) -> None:
    method test_retry_function_attributes (line 1411) | def test_retry_function_attributes(self) -> None:
  class TestStatisticsKeys (line 1453) | class TestStatisticsKeys:
    method test_delay_since_first_attempt_available_on_first_attempt (line 1454) | def test_delay_since_first_attempt_available_on_first_attempt(self) ->...
  class TestEnabled (line 1470) | class TestEnabled:
    method test_enabled_false_skips_retry (line 1471) | def test_enabled_false_skips_retry(self) -> None:
    method test_enabled_false_preserves_attributes (line 1485) | def test_enabled_false_preserves_attributes(self) -> None:
    method test_enabled_false_via_retry_with (line 1497) | def test_enabled_false_via_retry_with(self) -> None:
    method test_enabled_true_retries_normally (line 1512) | def test_enabled_true_retries_normally(self) -> None:
  class TestRetryWith (line 1528) | class TestRetryWith:
    method test_redefine_wait (line 1529) | def test_redefine_wait(self) -> None:
    method test_redefine_stop (line 1538) | def test_redefine_stop(self) -> None:
    method test_retry_error_cls_should_be_preserved (line 1544) | def test_retry_error_cls_should_be_preserved(self) -> None:
    method test_retry_error_callback_should_be_preserved (line 1554) | def test_retry_error_callback_should_be_preserved(self) -> None:
  class TestBeforeAfterAttempts (line 1566) | class TestBeforeAfterAttempts(unittest.TestCase):
    method test_before_attempts (line 1569) | def test_before_attempts(self) -> None:
    method test_after_attempts (line 1587) | def test_after_attempts(self) -> None:
    method test_before_sleep (line 1606) | def test_before_sleep(self) -> None:
    method _before_sleep_log_raises (line 1623) | def _before_sleep_log_raises(
    method test_before_sleep_log_raises (line 1652) | def test_before_sleep_log_raises(self) -> None:
    method test_before_sleep_log_raises_with_exc_info (line 1655) | def test_before_sleep_log_raises_with_exc_info(self) -> None:
    method test_before_sleep_log_returns (line 1687) | def test_before_sleep_log_returns(self, exc_info: bool = False) -> None:
    method test_before_sleep_log_returns_with_exc_info (line 1715) | def test_before_sleep_log_returns_with_exc_info(self) -> None:
  class TestReraiseExceptions (line 1719) | class TestReraiseExceptions(unittest.TestCase):
    method test_reraise_by_default (line 1720) | def test_reraise_by_default(self) -> None:
    method test_reraise_from_retry_error (line 1735) | def test_reraise_from_retry_error(self) -> None:
    method test_reraise_timeout_from_retry_error (line 1752) | def test_reraise_timeout_from_retry_error(self) -> None:
    method test_reraise_no_exception (line 1772) | def test_reraise_no_exception(self) -> None:
  class TestStatistics (line 1788) | class TestStatistics(unittest.TestCase):
    method test_stats (line 1789) | def test_stats(self) -> None:
    method test_stats_failing (line 1798) | def test_stats_failing(self) -> None:
    method test_retry_object_statistics_synced (line 1808) | def test_retry_object_statistics_synced(self) -> None:
    method test_retry_object_statistics_during_execution (line 1821) | def test_retry_object_statistics_during_execution(self) -> None:
  class TestRetryErrorCallback (line 1840) | class TestRetryErrorCallback(unittest.TestCase):
    method setUp (line 1841) | def setUp(self) -> None:
    method _callback (line 1845) | def _callback(self, fut: tenacity.Future) -> tenacity.Future:
    method test_retry_error_callback (line 1849) | def test_retry_error_callback(self) -> None:
  class TestContextManager (line 1873) | class TestContextManager(unittest.TestCase):
    method test_context_manager_retry_one (line 1874) | def test_context_manager_retry_one(self) -> None:
    method test_context_manager_on_error (line 1885) | def test_context_manager_on_error(self) -> None:
    method test_context_manager_retry_error (line 1900) | def test_context_manager_retry_error(self) -> None:
    method test_context_manager_reraise (line 1912) | def test_context_manager_reraise(self) -> None:
  class TestInvokeAsCallable (line 1928) | class TestInvokeAsCallable:
    method invoke (line 1932) | def invoke(retry: Retrying, f: typing.Callable[..., typing.Any]) -> ty...
    method test_retry_one (line 1940) | def test_retry_one(self) -> None:
    method test_on_error (line 1953) | def test_on_error(self) -> None:
    method test_retry_error (line 1970) | def test_retry_error(self) -> None:
    method test_reraise (line 1982) | def test_reraise(self) -> None:
  class TestRetryException (line 1998) | class TestRetryException(unittest.TestCase):
    method test_retry_error_is_pickleable (line 1999) | def test_retry_error_is_pickleable(self) -> None:
  class TestRetryTyping (line 2008) | class TestRetryTyping(unittest.TestCase):
    method test_retry_type_annotations (line 2009) | def test_retry_type_annotations(self) -> None:
  class TestMockingSleep (line 2031) | class TestMockingSleep:
    method _fail (line 2037) | def _fail(self) -> None:
    method _decorated_fail (line 2041) | def _decorated_fail(self) -> None:
    method mock_sleep (line 2045) | def mock_sleep(
    method test_decorated (line 2058) | def test_decorated(self, mock_sleep: typing.Any) -> None:
    method test_decorated_retry_with (line 2063) | def test_decorated_retry_with(self, mock_sleep: typing.Any) -> None:
  class TestPickle (line 2072) | class TestPickle(unittest.TestCase):
    method test_retrying_picklable (line 2073) | def test_retrying_picklable(self) -> None:
    method test_retrying_picklable_after_run (line 2081) | def test_retrying_picklable_after_run(self) -> None:
    method test_retry_strategies_picklable (line 2092) | def test_retry_strategies_picklable(self) -> None:
    method test_retrying_pickle_round_trip_works (line 2105) | def test_retrying_pickle_round_trip_works(self) -> None:

FILE: tests/test_tornado.py
  function _retryable_coroutine (line 28) | def _retryable_coroutine(thing: NoIOErrorAfterCount) -> Generator[Any, A...
  function _retryable_coroutine_with_2_attempts (line 35) | def _retryable_coroutine_with_2_attempts(
  class TestTornado (line 42) | class TestTornado(testing.AsyncTestCase):
    method test_retry (line 44) | def test_retry(self) -> Generator[Any, Any, None]:
    method test_stop_after_attempt (line 51) | def test_stop_after_attempt(self) -> Generator[Any, Any, None]:
    method test_repr (line 59) | def test_repr(self) -> None:
    method test_old_tornado (line 62) | def test_old_tornado(self) -> None:

FILE: tests/test_utils.py
  function test_is_coroutine_callable (line 6) | def test_is_coroutine_callable() -> None:
  function test_find_ordinal (line 44) | def test_find_ordinal() -> None:
Condensed preview — 81 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (242K chars).
[
  {
    "path": ".editorconfig",
    "chars": 294,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 187,
    "preview": "version: 2\nupdates:\n  - package-ecosystem: 'github-actions'\n    directory: '/'\n    schedule:\n      interval: 'monthly'\n "
  },
  {
    "path": ".github/workflows/ci.yaml",
    "chars": 1003,
    "preview": "name: Continuous Integration\npermissions: read-all\n\non:\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  # yam"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 520,
    "preview": "name: upload release to PyPI\non:\n  release:\n    types:\n      - published\n\njobs:\n  pypi-publish:\n    name: upload release"
  },
  {
    "path": ".gitignore",
    "chars": 115,
    "preview": ".idea\ndist\n*.pyc\n*.egg-info\nbuild\n.venv/\nuv.lock\nAUTHORS\nChangeLog\ndoc/_build\n\ntenacity/_version.py\n/.pytest_cache\n"
  },
  {
    "path": ".mergify.yml",
    "chars": 609,
    "preview": "queue_rules:\n  - name: default\n    merge_method: squash\n    autoqueue: true\n    queue_conditions:\n      - or:\n        - "
  },
  {
    "path": ".readthedocs.yml",
    "chars": 156,
    "preview": "version: 2\nbuild:\n  os: ubuntu-24.04\n  tools:\n    python: \"3.13\"\npython:\n  install:\n    - method: pip\n      path: .\n    "
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "doc/source/api.rst",
    "chars": 1543,
    "preview": "===============\n API Reference\n===============\n\nRetry Main API\n--------------\n\n.. autofunction:: tenacity.retry\n   :noin"
  },
  {
    "path": "doc/source/changelog.rst",
    "chars": 40,
    "preview": "Changelog\n=========\n\n.. release-notes::\n"
  },
  {
    "path": "doc/source/conf.py",
    "chars": 936,
    "preview": "# Copyright 2016 Étienne Bersac\n# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray "
  },
  {
    "path": "doc/source/index.rst",
    "chars": 21518,
    "preview": "Tenacity\n========\n.. image:: https://img.shields.io/pypi/v/tenacity.svg\n    :target: https://pypi.org/project/tenacity\n\n"
  },
  {
    "path": "pyproject.toml",
    "chars": 3262,
    "preview": "[build-system]\nrequires = [\"hatchling\", \"hatch-vcs\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.version]\nsource = \"v"
  },
  {
    "path": "releasenotes/notes/Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml",
    "chars": 56,
    "preview": "---\nfixes:\n  - \"Fixes test failures with typeguard 3.x\"\n"
  },
  {
    "path": "releasenotes/notes/Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml",
    "chars": 165,
    "preview": "---\nother:\n  - \"Use `black` for code formatting and validate using `black --check`. Code compatibility: py26-py39.\"\n  - "
  },
  {
    "path": "releasenotes/notes/add-async-actions-b249c527d99723bb.yaml",
    "chars": 158,
    "preview": "---\nfeatures:\n  - |\n    Added the ability to use async functions for retries. This way, you can now use\n    asyncio coro"
  },
  {
    "path": "releasenotes/notes/add-re-pattern-to-match-types-6a4c1d9e64e2a5e1.yaml",
    "chars": 64,
    "preview": "---\nfixes:\n  - |\n    Added `re.Pattern` to allowed match types.\n"
  },
  {
    "path": "releasenotes/notes/add-reno-d1ab5710f272650a.yaml",
    "chars": 46,
    "preview": "---\nfeatures:\n  - Add reno (changelog system)\n"
  },
  {
    "path": "releasenotes/notes/add-retry_except_exception_type-31b31da1924d55f4.yaml",
    "chars": 131,
    "preview": "---\nfeatures:\n  - Add ``retry_if_not_exception_type()`` that allows to retry if a raised exception doesn't match given e"
  },
  {
    "path": "releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml",
    "chars": 364,
    "preview": "---\nfeatures:\n  - |\n    Added a new stop function: stop_before_delay, which will stop execution\n    if the next sleep ti"
  },
  {
    "path": "releasenotes/notes/add-test-extra-55e869261b03e56d.yaml",
    "chars": 36,
    "preview": "---\nother:\n  - Add a \\\"test\\\" extra\n"
  },
  {
    "path": "releasenotes/notes/add_omitted_modules_to_import_all-2ab282f20a2c22f7.yaml",
    "chars": 101,
    "preview": "---\nother:\n  - Add `retry_if_exception_cause_type`and `wait_exponential_jitter` to __all__ of init.py"
  },
  {
    "path": "releasenotes/notes/add_retry_if_exception_cause_type-d16b918ace4ae0ad.yaml",
    "chars": 189,
    "preview": "---\nfeatures:\n  - |\n    Add a new `retry_base` class called `retry_if_exception_cause_type` that\n    checks, recursively"
  },
  {
    "path": "releasenotes/notes/added_a_link_to_documentation-eefaf8f074b539f8.yaml",
    "chars": 150,
    "preview": "---\nother:\n  - |\n    Added a link to the documentation, as code snippets are not being rendered properly\n    Changed bra"
  },
  {
    "path": "releasenotes/notes/after_log-50f4d73b24ce9203.yaml",
    "chars": 92,
    "preview": "---\nfixes:\n  - \"Fix after_log logger format: function name was used with delay formatting.\"\n"
  },
  {
    "path": "releasenotes/notes/allow-mocking-of-nap-sleep-6679c50e702446f1.yaml",
    "chars": 95,
    "preview": "---\nother:\n  - Unit tests can now mock ``nap.sleep()`` for testing in all tenacity usage styles"
  },
  {
    "path": "releasenotes/notes/annotate_code-197b93130df14042.yaml",
    "chars": 61,
    "preview": "---\nother:\n  - Add type annotations to cover all public API.\n"
  },
  {
    "path": "releasenotes/notes/async-sleep-retrying-32de5866f5d041.yaml",
    "chars": 290,
    "preview": "---\nfixes:\n  - |\n    Passing an async ``sleep`` callable (e.g. ``trio.sleep``) to ``@retry``\n    now correctly uses ``As"
  },
  {
    "path": "releasenotes/notes/before_sleep_log-improvements-d8149274dfb37d7c.yaml",
    "chars": 84,
    "preview": "---\nfeatures:\n  - Add an ``exc_info`` option to the ``before_sleep_log()`` strategy."
  },
  {
    "path": "releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml",
    "chars": 63,
    "preview": "---\nprelude: >\n    Clarify usage of `reraise` keyword argument\n"
  },
  {
    "path": "releasenotes/notes/dependabot-for-github-actions-4d2464f3c0928463.yaml",
    "chars": 127,
    "preview": "---\nother:\n  - |\n    Add a Dependabot configuration submit PRs monthly (as needed)\n    to keep GitHub action versions up"
  },
  {
    "path": "releasenotes/notes/deprecate-initial-for-multiplier-c7b4e2d9f1a83065.yaml",
    "chars": 369,
    "preview": "---\ndeprecations:\n  - |\n    The ``initial`` parameter of ``wait_exponential_jitter`` is deprecated in\n    favor of ``mul"
  },
  {
    "path": "releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml",
    "chars": 51,
    "preview": "---\nother:\n  - Do not package tests with tenacity.\n"
  },
  {
    "path": "releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml",
    "chars": 79,
    "preview": "---\nother:\n  - |\n    Drop support for deprecated Python versions (2.7 and 3.5)\n"
  },
  {
    "path": "releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml",
    "chars": 140,
    "preview": "---\nupgrade:\n  - |\n    Python 3.9 has reached end-of-life and is no longer supported.\n    The minimum supported version "
  },
  {
    "path": "releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml",
    "chars": 274,
    "preview": "---\nupgrade:\n  - \"Removed `BaseRetrying.call`: was long time deprecated and produced `DeprecationWarning`\"\n  - \"Removed "
  },
  {
    "path": "releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml",
    "chars": 82,
    "preview": "---\nfeatures:\n  - Explicitly export convenience symbols from tenacity root module\n"
  },
  {
    "path": "releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml",
    "chars": 87,
    "preview": "---\nfixes:\n  - |\n    Fix async loop with retrying code block when result is available.\n"
  },
  {
    "path": "releasenotes/notes/fix-async-retry-type-overloads-27f3e0c239ed6b.yaml",
    "chars": 428,
    "preview": "---\nfixes:\n  - |\n    The ``@retry`` decorator's type overloads for the ``sleep=`` parameter\n    (e.g. ``sleep=trio.sleep"
  },
  {
    "path": "releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml",
    "chars": 89,
    "preview": "---\nfixes:\n  - |\n    Avoid overwriting local contexts when applying the retry decorator.\n"
  },
  {
    "path": "releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml",
    "chars": 214,
    "preview": "---\nfixes:\n  - |\n    Restore the value of the `retry` attribute for wrapped functions. Also,\n    clarify that those attr"
  },
  {
    "path": "releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml",
    "chars": 99,
    "preview": "---\nfixes:\n  - Fix setuptools config to include tenacity.asyncio package in release distributions.\n"
  },
  {
    "path": "releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml",
    "chars": 203,
    "preview": "---\nfixes:\n  - |\n    Argument `wait` was improperly annotated, making mypy checks fail.\n    Now it's annotated as `typin"
  },
  {
    "path": "releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml",
    "chars": 84,
    "preview": "---\nfixes:\n  - \"Fix issue #288 : __name__ and other attributes for async functions\"\n"
  },
  {
    "path": "releasenotes/notes/logging-protocol-a4cf0f786f21e4ee.yaml",
    "chars": 105,
    "preview": "---\nother:\n  - |\n    Accept non-standard logger in helpers logging something (eg: structlog, loguru...)\n\n"
  },
  {
    "path": "releasenotes/notes/make-logger-more-compatible-5da1ddf1bab77047.yaml",
    "chars": 127,
    "preview": "---\nfixes:\n  - |\n    Use str.format to format the logs internally to make logging compatible with other logger such as l"
  },
  {
    "path": "releasenotes/notes/no-async-iter-6132a42e52348a75.yaml",
    "chars": 264,
    "preview": "---\nfixes:\n  - |\n    `AsyncRetrying` was erroneously implementing `__iter__()`, making tenacity\n    retrying mechanism w"
  },
  {
    "path": "releasenotes/notes/pr320-py3-only-wheel-tag.yaml",
    "chars": 125,
    "preview": "---\nother: >-\n  Corrected the PyPI-published wheel tag to match the\n  metadata saying that the release is Python 3 only."
  },
  {
    "path": "releasenotes/notes/py36_plus-c425fb3aa17c6682.yaml",
    "chars": 99,
    "preview": "---\nfeatures:\n  - Most part of the code is type annotated.\n  - Python 3.10 support has been added.\n"
  },
  {
    "path": "releasenotes/notes/remove-py36-876c0416cf279d15.yaml",
    "chars": 64,
    "preview": "---\nupgrade:\n  - |\n    Support for Python 3.6 has been removed.\n"
  },
  {
    "path": "releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml",
    "chars": 96,
    "preview": "---\nfeatures:\n  - Add a ``__repr__`` method to ``RetryCallState`` objects for easier debugging.\n"
  },
  {
    "path": "releasenotes/notes/some-slug-for-preserve-defaults-86682846dfa18005.yaml",
    "chars": 86,
    "preview": "---\nfixes:\n  - |\n    Preserve __defaults__ and __kwdefaults__ through retry decorator\n"
  },
  {
    "path": "releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml",
    "chars": 79,
    "preview": "---\nfixes: Sphinx build error where Sphinx complains about an undefined class.\n"
  },
  {
    "path": "releasenotes/notes/support-py3.14-14928188cab53b99.yaml",
    "chars": 54,
    "preview": "---\nfeatures:\n  - Python 3.14 support has been added.\n"
  },
  {
    "path": "releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml",
    "chars": 73,
    "preview": "---\nfeatures:\n  - Add ``datetime.timedelta`` as accepted wait unit type.\n"
  },
  {
    "path": "releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml",
    "chars": 116,
    "preview": "---\nfeatures:\n  - |\n    - accept ``datetime.timedelta`` instances as argument to ``tenacity.stop.stop_after_delay``\n"
  },
  {
    "path": "releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml",
    "chars": 184,
    "preview": "---\nfeatures:\n  - |\n    If you're using `Trio <https://trio.readthedocs.io>`__, then\n    ``@retry`` now works automatica"
  },
  {
    "path": "releasenotes/notes/wait-exponential-jitter-min-timedelta-a8e3c1f4b7d29e50.yaml",
    "chars": 232,
    "preview": "---\nfeatures:\n  - |\n    Add ``min`` parameter to ``wait_exponential_jitter`` to set a minimum wait\n    time floor, consi"
  },
  {
    "path": "releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml",
    "chars": 70,
    "preview": "---\nfixes:\n  - |\n    Respects `min` arg for `wait_random_exponential`\n"
  },
  {
    "path": "releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml",
    "chars": 160,
    "preview": "---\nfeatures:\n  - |\n    Implement a wait.wait_exponential_jitter per Google's storage retry guide.\n    See https://cloud"
  },
  {
    "path": "reno.yaml",
    "chars": 41,
    "preview": "---\nunreleased_version_title: Unreleased\n"
  },
  {
    "path": "tenacity/__init__.py",
    "chars": 26321,
    "preview": "# Copyright 2016-2018 Julien Danjou\n# Copyright 2017 Elisey Zanko\n# Copyright 2016 Étienne Bersac\n# Copyright 2016 Joshu"
  },
  {
    "path": "tenacity/_utils.py",
    "chars": 3175,
    "preview": "# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Ap"
  },
  {
    "path": "tenacity/after.py",
    "chars": 1516,
    "preview": "# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Ap"
  },
  {
    "path": "tenacity/asyncio/__init__.py",
    "chars": 7981,
    "preview": "# Copyright 2016 Étienne Bersac\n# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray "
  },
  {
    "path": "tenacity/asyncio/retry.py",
    "chars": 4642,
    "preview": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under t"
  },
  {
    "path": "tenacity/before.py",
    "chars": 1354,
    "preview": "# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Ap"
  },
  {
    "path": "tenacity/before_sleep.py",
    "chars": 2190,
    "preview": "# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Ap"
  },
  {
    "path": "tenacity/nap.py",
    "chars": 1372,
    "preview": "# Copyright 2016 Étienne Bersac\n# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray "
  },
  {
    "path": "tenacity/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tenacity/retry.py",
    "chars": 9775,
    "preview": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under t"
  },
  {
    "path": "tenacity/stop.py",
    "chars": 4098,
    "preview": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under t"
  },
  {
    "path": "tenacity/tornadoweb.py",
    "chars": 2091,
    "preview": "# Copyright 2017 Elisey Zanko\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this"
  },
  {
    "path": "tenacity/wait.py",
    "chars": 9957,
    "preview": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under t"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/test_after.py",
    "chars": 3035,
    "preview": "import logging\nimport random\nimport unittest.mock\n\nfrom tenacity import (\n    _utils,\n    after_log,\n)\n\nfrom . import te"
  },
  {
    "path": "tests/test_asyncio.py",
    "chars": 16261,
    "preview": "# Copyright 2016 Étienne Bersac\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use th"
  },
  {
    "path": "tests/test_issue_478.py",
    "chars": 3217,
    "preview": "import asyncio\nimport typing\nimport unittest\nfrom functools import wraps\n\nfrom tenacity import RetryCallState, retry\n\n\nd"
  },
  {
    "path": "tests/test_tenacity.py",
    "chars": 77512,
    "preview": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013 Ray Holder\n#\n# Licensed under the Ap"
  },
  {
    "path": "tests/test_tornado.py",
    "chars": 2378,
    "preview": "# Copyright 2017 Elisey Zanko\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this"
  },
  {
    "path": "tests/test_utils.py",
    "chars": 2039,
    "preview": "import functools\n\nfrom tenacity import _utils\n\n\ndef test_is_coroutine_callable() -> None:\n    async def async_func() -> "
  }
]

About this extraction

This page contains the full source code of the jd/tenacity GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 81 files (221.6 KB), approximately 56.0k tokens, and a symbol index with 485 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!