[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.{py,pyx,pxd,pyi}]\nindent_size = 4\nmax_line_length = 120\n\n[*.ini]\nindent_size = 4\n\n[*.rst]\nmax_line_length = 150\n\n[Makefile]\nindent_style = tab\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: 'github-actions'\n    directory: '/'\n    schedule:\n      interval: 'monthly'\n    groups:\n      github-actions:\n        patterns:\n          - '*'\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: Continuous Integration\npermissions: read-all\n\non:\n  pull_request:\n    branches:\n      - main\n\nconcurrency:\n  # yamllint disable-line rule:line-length\n  group: \"${{ github.workflow }}-${{ github.head_ref || github.run_id }}\"\n  cancel-in-progress: true\n\njobs:\n  test:\n    timeout-minutes: 20\n    runs-on: ubuntu-24.04\n    strategy:\n      matrix:\n        include:\n          - python: \"3.10\"\n            task: check\n          - python: \"3.11\"\n            task: check\n          - python: \"3.12\"\n            task: check\n          - python: \"3.13\"\n            task: check\n          - python: \"3.14\"\n            task: check\n          - python: \"3.14\"\n            task: lint\n          - python: \"3.14\"\n            task: mypy\n    steps:\n      - name: Checkout 🛎️\n        uses: actions/checkout@v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup uv 🔧\n        uses: astral-sh/setup-uv@v7\n\n      - name: Build 🔧 & Test 🔍\n        run: uv run --python ${{ matrix.python }} poe ${{ matrix.task }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: upload release to PyPI\non:\n  release:\n    types:\n      - published\n\njobs:\n  pypi-publish:\n    name: upload release to PyPI\n    runs-on: ubuntu-24.04\n    environment: release\n    permissions:\n      id-token: write\n    steps:\n      - uses: actions/checkout@v6.0.2\n        with:\n          fetch-depth: 0\n          fetch-tags: true\n\n      - uses: astral-sh/setup-uv@v7\n\n      - name: Build\n        run: uv build\n\n      - name: Publish package distributions to PyPI\n        uses: pypa/gh-action-pypi-publish@release/v1\n"
  },
  {
    "path": ".gitignore",
    "content": ".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",
    "content": "queue_rules:\n  - name: default\n    merge_method: squash\n    autoqueue: true\n    queue_conditions:\n      - or:\n        - author = jd\n        - \"#approved-reviews-by >= 1\"\n        - author = dependabot[bot]\n      - \"check-success=test (3.10, check)\"\n      - \"check-success=test (3.11, check)\"\n      - \"check-success=test (3.12, check)\"\n      - \"check-success=test (3.13, check)\"\n      - \"check-success=test (3.14, check)\"\n      - \"check-success=test (3.14, lint)\"\n      - \"check-success=test (3.14, mypy)\"\n\npull_request_rules:\n  - name: dismiss reviews\n    conditions: []\n    actions:\n      dismiss_reviews: {}\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "version: 2\nbuild:\n  os: ubuntu-24.04\n  tools:\n    python: \"3.13\"\npython:\n  install:\n    - method: pip\n      path: .\n      extra_requirements:\n        - doc\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "doc/source/api.rst",
    "content": "===============\n API Reference\n===============\n\nRetry Main API\n--------------\n\n.. autofunction:: tenacity.retry\n   :noindex:\n\n.. autoclass:: tenacity.Retrying\n   :members:\n\n.. autoclass:: tenacity.AsyncRetrying\n   :members:\n\n.. autoclass:: tenacity.tornadoweb.TornadoRetrying\n   :members:\n\n.. autoclass:: tenacity.RetryCallState\n   :members:\n\nAfter Functions\n---------------\n\nThose functions can be used as the `after` keyword argument of\n:py:func:`tenacity.retry`.\n\n.. automodule:: tenacity.after\n   :members:\n\nBefore Functions\n----------------\n\nThose functions can be used as the `before` keyword argument of\n:py:func:`tenacity.retry`.\n\n.. automodule:: tenacity.before\n   :members:\n\nBefore Sleep Functions\n----------------------\n\nThose functions can be used as the `before_sleep` keyword argument of\n:py:func:`tenacity.retry`.\n\n.. automodule:: tenacity.before_sleep\n   :members:\n\nNap Functions\n-------------\n\nThose functions can be used as the `sleep` keyword argument of\n:py:func:`tenacity.retry`.\n\n.. automodule:: tenacity.nap\n   :members:\n\nRetry Functions\n---------------\n\nThose functions can be used as the `retry` keyword argument of\n:py:func:`tenacity.retry`.\n\n.. automodule:: tenacity.retry\n   :members:\n\nStop Functions\n--------------\n\nThose functions can be used as the `stop` keyword argument of\n:py:func:`tenacity.retry`.\n\n.. automodule:: tenacity.stop\n   :members:\n\nWait Functions\n--------------\n\nThose functions can be used as the `wait` keyword argument of\n:py:func:`tenacity.retry`.\n\n.. automodule:: tenacity.wait\n   :members:\n"
  },
  {
    "path": "doc/source/changelog.rst",
    "content": "Changelog\n=========\n\n.. release-notes::\n"
  },
  {
    "path": "doc/source/conf.py",
    "content": "# Copyright 2016 Étienne Bersac\n# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom importlib.metadata import version as pkg_version\n\nmaster_doc = \"index\"\nproject = \"Tenacity\"\nrelease = pkg_version(\"tenacity\")\nversion = \".\".join(release.split(\".\")[:2])\n\nextensions = [\n    \"sphinx.ext.doctest\",\n    \"sphinx.ext.autodoc\",\n    \"reno.sphinxext\",\n]\n"
  },
  {
    "path": "doc/source/index.rst",
    "content": "Tenacity\n========\n.. image:: https://img.shields.io/pypi/v/tenacity.svg\n    :target: https://pypi.org/project/tenacity\n\n.. image:: https://img.shields.io/pypi/pyversions/tenacity.svg\n    :target: https://pypi.org/project/tenacity\n\n.. image:: https://github.com/jd/tenacity/actions/workflows/ci.yaml/badge.svg?branch=main\n    :target: https://github.com/jd/tenacity/actions/workflows/ci.yaml\n\n.. image:: https://img.shields.io/endpoint.svg?url=https://api.mergify.com/badges/jd/tenacity&style=flat\n   :target: https://mergify.io\n   :alt: Mergify Status\n\n**Please refer to the** `tenacity documentation <https://tenacity.readthedocs.io/en/latest/>`_ **for a better experience.**\n\nTenacity is an Apache 2.0 licensed general-purpose retrying library, written in\nPython, to simplify the task of adding retry behavior to just about anything.\nIt originates from `a fork of retrying\n<https://github.com/rholder/retrying/issues/65>`_ which is sadly no longer\n`maintained <https://julien.danjou.info/python-tenacity/>`_. Tenacity isn't\napi compatible with retrying but adds significant new functionality and\nfixes a number of longstanding bugs.\n\nThe simplest use case is retrying a flaky function whenever an `Exception`\noccurs until a value is returned.\n\n.. testcode::\n\n    import random\n    from tenacity import retry\n\n    @retry\n    def do_something_unreliable():\n        if random.randint(0, 10) > 1:\n            raise IOError(\"Broken sauce, everything is hosed!!!111one\")\n        else:\n            return \"Awesome sauce!\"\n\n    print(do_something_unreliable())\n\n.. testoutput::\n   :hide:\n\n   Awesome sauce!\n\n\n.. toctree::\n    :hidden:\n    :maxdepth: 2\n\n    changelog\n    api\n\n\nFeatures\n--------\n\n- Generic Decorator API\n- Specify stop condition (i.e. limit by number of attempts)\n- Specify wait condition (i.e. exponential backoff sleeping between attempts)\n- Customize retrying on Exceptions\n- Customize retrying on expected returned result\n- Retry on coroutines\n- Retry code block with context manager\n\n\nInstallation\n------------\n\nTo install *tenacity*, simply:\n\n.. code-block:: bash\n\n    $ pip install tenacity\n\n\nExamples\n----------\n\nBasic Retry\n~~~~~~~~~~~\n\n.. testsetup::\n\n    import logging\n    #\n    # Note the following import is used for demonstration convenience only.\n    # Production code should always explicitly import the names it needs.\n    #\n    from tenacity import *\n\n    class MyException(Exception):\n        pass\n\nAs you saw above, the default behavior is to retry forever without waiting when\nan exception is raised.\n\n.. testcode::\n\n    @retry\n    def never_gonna_give_you_up():\n        print(\"Retry forever ignoring Exceptions, don't wait between retries\")\n        raise Exception\n\nStopping\n~~~~~~~~\n\nLet's be a little less persistent and set some boundaries, such as the number\nof attempts before giving up.\n\n.. testcode::\n\n    @retry(stop=stop_after_attempt(7))\n    def stop_after_7_attempts():\n        print(\"Stopping after 7 attempts\")\n        raise Exception\n\nWe don't have all day, so let's set a boundary for how long we should be\nretrying stuff.\n\n.. testcode::\n\n    @retry(stop=stop_after_delay(10))\n    def stop_after_10_s():\n        print(\"Stopping after 10 seconds\")\n        raise Exception\n\nIf you're on a tight deadline, and exceeding your delay time isn't ok,\nthen you can give up on retries one attempt before you would exceed the delay.\n\n.. testcode::\n\n    @retry(stop=stop_before_delay(10))\n    def stop_before_10_s():\n        print(\"Stopping 1 attempt before 10 seconds\")\n        raise Exception\n\nYou can combine several stop conditions by using the `|` operator:\n\n.. testcode::\n\n    @retry(stop=(stop_after_delay(10) | stop_after_attempt(5)))\n    def stop_after_10_s_or_5_retries():\n        print(\"Stopping after 10 seconds or 5 retries\")\n        raise Exception\n\nWaiting before retrying\n~~~~~~~~~~~~~~~~~~~~~~~\n\nMost things don't like to be polled as fast as possible, so let's just wait 2\nseconds between retries.\n\n.. testcode::\n\n    @retry(wait=wait_fixed(2))\n    def wait_2_s():\n        print(\"Wait 2 second between retries\")\n        raise Exception\n\nSome things perform best with a bit of randomness injected.\n\n.. testcode::\n\n    @retry(wait=wait_random(min=1, max=2))\n    def wait_random_1_to_2_s():\n        print(\"Randomly wait 1 to 2 seconds between retries\")\n        raise Exception\n\nThen again, it's hard to beat exponential backoff when retrying distributed\nservices and other remote endpoints.\n\n.. testcode::\n\n    @retry(wait=wait_exponential(multiplier=1, min=4, max=10))\n    def wait_exponential_1():\n        print(\"Wait 2^x * 1 second between each retry starting with 4 seconds, then up to 10 seconds, then 10 seconds afterwards\")\n        raise Exception\n\n\nThen again, it's also hard to beat combining fixed waits and jitter (to\nhelp avoid thundering herds) when retrying distributed services and other\nremote endpoints.\n\n.. testcode::\n\n    @retry(wait=wait_fixed(3) + wait_random(0, 2))\n    def wait_fixed_jitter():\n        print(\"Wait at least 3 seconds, and add up to 2 seconds of random delay\")\n        raise Exception\n\nWhen multiple processes are in contention for a shared resource, exponentially\nincreasing jitter helps minimise collisions.\n\n.. testcode::\n\n    @retry(wait=wait_random_exponential(multiplier=1, max=60))\n    def wait_exponential_jitter():\n        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\")\n        raise Exception\n\n\nSometimes it's necessary to build a chain of backoffs.\n\n.. testcode::\n\n    @retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] +\n                           [wait_fixed(7) for i in range(2)] +\n                           [wait_fixed(9)]))\n    def wait_fixed_chained():\n        print(\"Wait 3s for 3 attempts, 7s for the next 2 attempts and 9s for all attempts thereafter\")\n        raise Exception\n\nWhether to retry\n~~~~~~~~~~~~~~~~\n\nWe have a few options for dealing with retries that raise specific or general\nexceptions, as in the cases here.\n\n.. testcode::\n\n    class ClientError(Exception):\n        \"\"\"Some type of client error.\"\"\"\n\n    @retry(retry=retry_if_exception_type(IOError))\n    def might_io_error():\n        print(\"Retry forever with no wait if an IOError occurs, raise any other errors\")\n        raise Exception\n\n    @retry(retry=retry_if_not_exception_type(ClientError))\n    def might_client_error():\n        print(\"Retry forever with no wait if any error other than ClientError occurs. Immediately raise ClientError.\")\n        raise Exception\n\nWe can also use the result of the function to alter the behavior of retrying.\n\n.. testcode::\n\n    def is_none_p(value):\n        \"\"\"Return True if value is None\"\"\"\n        return value is None\n\n    @retry(retry=retry_if_result(is_none_p))\n    def might_return_none():\n        print(\"Retry with no wait if return value is None\")\n\nSee also these methods:\n\n.. testcode::\n\n    retry_if_exception\n    retry_if_exception_type\n    retry_if_not_exception_type\n    retry_unless_exception_type\n    retry_if_result\n    retry_if_not_result\n    retry_if_exception_message\n    retry_if_not_exception_message\n    retry_any\n    retry_all\n\nWe can also combine several conditions:\n\n.. testcode::\n\n    def is_none_p(value):\n        \"\"\"Return True if value is None\"\"\"\n        return value is None\n\n    @retry(retry=(retry_if_result(is_none_p) | retry_if_exception_type()))\n    def might_return_none():\n        print(\"Retry forever ignoring Exceptions with no wait if return value is None\")\n\nAny combination of stop, wait, etc. is also supported to give you the freedom\nto mix and match.\n\nIt's also possible to retry explicitly at any time by raising the `TryAgain`\nexception:\n\n.. testcode::\n\n   @retry\n   def do_something():\n       result = something_else()\n       if result == 23:\n          raise TryAgain\n\nError Handling\n~~~~~~~~~~~~~~\n\nNormally when your function fails its final time (and will not be retried again based on your settings),\na `RetryError` is raised. The exception your code encountered will be shown somewhere in the *middle*\nof the stack trace.\n\nIf you would rather see the exception your code encountered at the *end* of the stack trace (where it\nis most visible), you can set `reraise=True`.\n\n.. testcode::\n\n    @retry(reraise=True, stop=stop_after_attempt(3))\n    def raise_my_exception():\n        raise MyException(\"Fail\")\n\n    try:\n        raise_my_exception()\n    except MyException:\n        # timed out retrying\n        pass\n\nBefore and After Retry, and Logging\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIt's possible to execute an action before any attempt of calling the function\nby using the before callback function:\n\n.. testcode::\n\n    import logging\n    import sys\n\n    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)\n\n    logger = logging.getLogger(__name__)\n\n    @retry(stop=stop_after_attempt(3), before=before_log(logger, logging.DEBUG))\n    def raise_my_exception():\n        raise MyException(\"Fail\")\n\nIn the same spirit, It's possible to execute after a call that failed:\n\n.. testcode::\n\n    import logging\n    import sys\n\n    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)\n\n    logger = logging.getLogger(__name__)\n\n    @retry(stop=stop_after_attempt(3), after=after_log(logger, logging.DEBUG))\n    def raise_my_exception():\n        raise MyException(\"Fail\")\n\nIt's also possible to only log failures that are going to be retried. Normally\nretries happen after a wait interval, so the keyword argument is called\n``before_sleep``:\n\n.. testcode::\n\n    import logging\n    import sys\n\n    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)\n\n    logger = logging.getLogger(__name__)\n\n    @retry(stop=stop_after_attempt(3),\n           before_sleep=before_sleep_log(logger, logging.DEBUG))\n    def raise_my_exception():\n        raise MyException(\"Fail\")\n\n\nStatistics\n~~~~~~~~~~\n\nYou can access the statistics about the retry made over a function by using the\n`statistics` attribute attached to the function:\n\n.. testcode::\n\n    @retry(stop=stop_after_attempt(3))\n    def raise_my_exception():\n        raise MyException(\"Fail\")\n\n    try:\n        raise_my_exception()\n    except Exception:\n        pass\n\n    print(raise_my_exception.statistics)\n\n.. testoutput::\n   :hide:\n\n   ...\n\nCustom Callbacks\n~~~~~~~~~~~~~~~~\n\nYou can also define your own callbacks. The callback should accept one\nparameter called ``retry_state`` that contains all information about current\nretry invocation.\n\nFor example, you can call a custom callback function after all retries failed,\nwithout raising an exception (or you can re-raise or do anything really)\n\n.. testcode::\n\n    def return_last_value(retry_state):\n        \"\"\"return the result of the last call attempt\"\"\"\n        return retry_state.outcome.result()\n\n    def is_false(value):\n        \"\"\"Return True if value is False\"\"\"\n        return value is False\n\n    # will return False after trying 3 times to get a different result\n    @retry(stop=stop_after_attempt(3),\n           retry_error_callback=return_last_value,\n           retry=retry_if_result(is_false))\n    def eventually_return_false():\n        return False\n\nRetryCallState\n~~~~~~~~~~~~~~\n\n``retry_state`` argument is an object of :class:`~tenacity.RetryCallState` class.\nIts most useful attributes are:\n\n* ``attempt_number`` — number of the current attempt (starts at 1)\n* ``outcome`` — a :class:`concurrent.futures.Future` holding the last result or exception\n* ``seconds_since_start`` — total elapsed seconds from the first attempt to the last outcome (``None`` if no outcome yet)\n* ``idle_for`` — cumulative seconds spent sleeping between attempts\n* ``start_time`` — :func:`time.monotonic` timestamp of the first attempt\n\nFor example, to log the total elapsed time after all retries:\n\n.. testcode::\n\n    import logging\n\n    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)\n\n    logger = logging.getLogger(__name__)\n\n    def log_elapsed(retry_state):\n        logger.info('Finished after %.3fs', retry_state.seconds_since_start)\n\n    @retry(stop=stop_after_attempt(3), after=log_elapsed)\n    def raise_my_exception():\n        raise MyException(\"Fail\")\n\n    try:\n        raise_my_exception()\n    except RetryError:\n        pass\n\nOther Custom Callbacks\n~~~~~~~~~~~~~~~~~~~~~~\n\nIt's also possible to define custom callbacks for other keyword arguments.\n\n.. function:: my_stop(retry_state)\n\n   :param RetryCallState retry_state: info about current retry invocation\n   :return: whether or not retrying should stop\n   :rtype: bool\n\n.. function:: my_wait(retry_state)\n\n   :param RetryCallState retry_state: info about current retry invocation\n   :return: number of seconds to wait before next retry\n   :rtype: float\n\n.. function:: my_retry(retry_state)\n\n   :param RetryCallState retry_state: info about current retry invocation\n   :return: whether or not retrying should continue\n   :rtype: bool\n\n.. function:: my_before(retry_state)\n\n   :param RetryCallState retry_state: info about current retry invocation\n\n.. function:: my_after(retry_state)\n\n   :param RetryCallState retry_state: info about current retry invocation\n\n.. function:: my_before_sleep(retry_state)\n\n   :param RetryCallState retry_state: info about current retry invocation\n\nHere's an example with a custom ``before_sleep`` function:\n\n.. testcode::\n\n    import logging\n\n    logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)\n\n    logger = logging.getLogger(__name__)\n\n    def my_before_sleep(retry_state):\n        if retry_state.attempt_number < 1:\n            loglevel = logging.INFO\n        else:\n            loglevel = logging.WARNING\n        logger.log(\n            loglevel, 'Retrying %s: attempt %s ended with: %s',\n            retry_state.fn, retry_state.attempt_number, retry_state.outcome)\n\n    @retry(stop=stop_after_attempt(3), before_sleep=my_before_sleep)\n    def raise_my_exception():\n        raise MyException(\"Fail\")\n\n    try:\n        raise_my_exception()\n    except RetryError:\n        pass\n\n\nCommon Patterns\n~~~~~~~~~~~~~~~\n\n**Running setup code between retries** (e.g. reconnecting):\n\n.. testcode::\n\n    def reconnect(retry_state):\n        print(\"Reconnecting before next attempt...\")\n\n    @retry(stop=stop_after_attempt(3), before_sleep=reconnect)\n    def send_data():\n        raise MyException(\"connection lost\")\n\n    try:\n        send_data()\n    except RetryError:\n        pass\n\n.. testoutput::\n   :hide:\n\n   ...\n\nThe ``before_sleep`` callback runs after a failed attempt and before sleeping,\nmaking it ideal for re-establishing connections, refreshing tokens, or any\nother setup that needs to happen before the next attempt.\n\n**Accessing the attempt number inside the function** using the iterator API:\n\n.. testcode::\n\n    from tenacity import Retrying\n\n    for attempt in Retrying(stop=stop_after_attempt(3)):\n        with attempt:\n            print(f\"Attempt {attempt.retry_state.attempt_number}\")\n            if attempt.retry_state.attempt_number < 3:\n                raise MyException(\"not yet\")\n\n.. testoutput::\n   :hide:\n\n   ...\n\n\nChanging Arguments at Run Time\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can change the arguments of a retry decorator as needed when calling it by\nusing the `retry_with` function attached to the wrapped function:\n\n.. testcode::\n\n    @retry(stop=stop_after_attempt(3))\n    def raise_my_exception():\n        raise MyException(\"Fail\")\n\n    try:\n        raise_my_exception.retry_with(stop=stop_after_attempt(4))()\n    except Exception:\n        pass\n\n    print(raise_my_exception.statistics)\n\n.. testoutput::\n   :hide:\n\n   ...\n\nIf you want to use variables to set up the retry parameters, you don't have\nto use the `retry` decorator - you can instead use `Retrying` directly:\n\n.. testcode::\n\n    def never_good_enough(arg1):\n        raise Exception('Invalid argument: {}'.format(arg1))\n\n    def try_never_good_enough(max_attempts=3):\n        retryer = Retrying(stop=stop_after_attempt(max_attempts), reraise=True)\n        retryer(never_good_enough, 'I really do try')\n\nYou may also want to change the behaviour of a decorated function temporarily,\nlike in tests to avoid unnecessary wait times. You can modify/patch the `retry`\nattribute attached to the function. Bear in mind this is a write-only attribute,\nstatistics should be read from the function `statistics` attribute.\n\n.. testcode::\n\n    @retry(stop=stop_after_attempt(3), wait=wait_fixed(3))\n    def raise_my_exception():\n        raise MyException(\"Fail\")\n\n    from unittest import mock\n\n    with mock.patch.object(raise_my_exception.retry, \"wait\", wait_fixed(0)):\n        try:\n            raise_my_exception()\n        except Exception:\n            pass\n\n    print(raise_my_exception.statistics)\n\n.. testoutput::\n   :hide:\n\n   ...\n\nDisabling Retries\n~~~~~~~~~~~~~~~~~\n\nYou can disable retrying entirely by passing ``enabled=False``. When disabled,\nthe decorated function is called directly without any retry logic. This is\nuseful during development or testing when you want fast feedback on failures:\n\n.. testcode::\n\n    import os\n\n    @retry(\n        enabled=os.getenv(\"ENABLE_RETRIES\", \"1\") != \"0\",\n        stop=stop_after_attempt(5),\n        wait=wait_fixed(1),\n    )\n    def call_api():\n        pass  # your code here\n\n    call_api()\n\nYou can also use ``retry_with`` to disable retries on a per-call basis:\n\n.. testcode::\n\n    @retry(stop=stop_after_attempt(5))\n    def call_api():\n        pass  # your code here\n\n    # In tests:\n    call_api.retry_with(enabled=False)()\n\nRetrying code block\n~~~~~~~~~~~~~~~~~~~\n\nTenacity allows you to retry a code block without the need to wraps it in an\nisolated function. This makes it easy to isolate failing block while sharing\ncontext. The trick is to combine a for loop and a context manager.\n\n.. testcode::\n\n   from tenacity import Retrying, RetryError, stop_after_attempt\n\n   try:\n       for attempt in Retrying(stop=stop_after_attempt(3)):\n           with attempt:\n               raise Exception('My code is failing!')\n   except RetryError:\n       pass\n\nYou can configure every details of retry policy by configuring the Retrying\nobject.\n\nWith async code you can use AsyncRetrying.\n\n.. testcode::\n\n   from tenacity import AsyncRetrying, RetryError, stop_after_attempt\n\n   async def function():\n      try:\n          async for attempt in AsyncRetrying(stop=stop_after_attempt(3)):\n              with attempt:\n                  raise Exception('My code is failing!')\n      except RetryError:\n          pass\n\nIn both cases, you may want to set the result to the attempt so it's available\nin retry strategies like ``retry_if_result``. This can be done accessing the\n``retry_state`` property:\n\n.. testcode::\n\n    from tenacity import AsyncRetrying, retry_if_result\n\n    async def function():\n       async for attempt in AsyncRetrying(retry=retry_if_result(lambda x: x < 3)):\n           with attempt:\n               result = 1  # Some complex calculation, function call, etc.\n           if not attempt.retry_state.outcome.failed:\n               attempt.retry_state.set_result(result)\n       return result\n\nAsync and retry\n~~~~~~~~~~~~~~~\n\nFinally, ``retry`` works also on asyncio, Trio, and Tornado coroutines.\nSleeps are done asynchronously too.\n\n.. code-block:: python\n\n    @retry\n    async def my_asyncio_function():\n        await asyncio.getaddrinfo('8.8.8.8', 53)\n\n.. code-block:: python\n\n    @retry\n    async def my_async_trio_function():\n        await trio.socket.getaddrinfo('8.8.8.8', 53)\n\n.. code-block:: python\n\n    @retry\n    async def my_async_tornado_function(http_client, url):\n        await http_client.fetch(url)\n\nYou can use alternative event loops by passing the correct sleep function:\n\n.. code-block:: python\n\n    @retry(sleep=trio.sleep)\n    async def my_async_trio_function_with_sleep():\n        ...\n\nGenerators\n~~~~~~~~~~\n\n``retry`` does not support generator or async generator functions. Decorating a\ngenerator with ``@retry`` will not retry on exceptions raised during iteration\n— the decorator wraps the function call itself, which for generators simply\nreturns a generator object without executing any of the body.\n\nAlso note that generators passed *as arguments* to a retried function will be\nexhausted after the first attempt and will not be rewound automatically on\nretry. If you need to pass a generator as an argument, consider passing a\nfactory function instead:\n\n.. code-block:: python\n\n    # Bad: generator will be exhausted after the first attempt\n    @retry\n    def process(items):\n        for item in items:\n            do_work(item)\n\n    process(my_generator())  # retries will see an empty generator\n\n    # Good: pass a factory so a fresh generator is created on each attempt\n    @retry\n    def process(items_factory):\n        for item in items_factory():\n            do_work(item)\n\n    process(my_generator)  # each retry gets a fresh generator\n\nContribute\n----------\n\n#. Check for open issues or open a fresh issue to start a discussion around a\n   feature idea or a bug.\n#. Fork `the repository`_ on GitHub to start making your changes to the\n   **main** branch (or branch off of it).\n#. Write a test which shows that the bug was fixed or that the feature works as\n   expected.\n#. Add a `changelog <#Changelogs>`_\n#. Make the docs better (or more detailed, or more easier to read, or ...)\n\nRunning the test suite locally::\n\n    uv run poe check    # run tests + build docs\n    uv run poe lint     # run ruff linter\n    uv run poe mypy     # run type checker\n    uv run poe all      # run everything\n\n.. _`the repository`: https://github.com/jd/tenacity\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"hatchling\", \"hatch-vcs\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.hatch.version]\nsource = \"vcs\"\n\n[tool.hatch.build.hooks.vcs]\nversion-file = \"tenacity/_version.py\"\n\n[project]\nname = \"tenacity\"\ndynamic = [\"version\"]\ndescription = \"Retry code until it succeeds\"\nreadme = \"README.rst\"\nlicense = \"Apache-2.0\"\nrequires-python = \">=3.10\"\nauthors = [\n    { name = \"Julien Danjou\", email = \"julien@danjou.info\" },\n]\nclassifiers = [\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: Apache Software License\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Topic :: Utilities\",\n]\n\n[project.urls]\nHomepage = \"https://github.com/jd/tenacity\"\nDocumentation = \"https://tenacity.readthedocs.io\"\nSource = \"https://github.com/jd/tenacity\"\nIssues = \"https://github.com/jd/tenacity/issues\"\nChangelog = \"https://tenacity.readthedocs.io/en/latest/changelog.html\"\n\n[project.optional-dependencies]\ndoc = [\n    \"reno\",\n    \"sphinx\",\n]\ntest = [\n    \"pytest\",\n    \"tornado>=6.0\",\n    \"typeguard\",\n]\n\n[dependency-groups]\ndev = [\n    \"poethepoet\",\n    \"pytest\",\n    \"tornado>=6.0\",\n    \"typeguard\",\n    \"ruff\",\n    \"mypy\",\n    \"sphinx\",\n    \"reno\",\n    \"trio\",\n]\n\n[tool.poe.tasks]\ntest = \"pytest\"\ndocs-doctest = \"sphinx-build -a -E -W -b doctest doc/source doc/build\"\ndocs-html = \"sphinx-build -a -E -W -b html doc/source doc/build\"\ndocs = [\"docs-doctest\", \"docs-html\"]\ncheck = [\"test\", \"docs\"]\nmypy = \"mypy\"\nreno = \"reno\"\n\n[tool.poe.tasks.fmt]\nsequence = [\n    { cmd = \"ruff check --fix .\" },\n    { cmd = \"ruff format .\" },\n]\n\n[tool.poe.tasks.lint]\nsequence = [\n    { cmd = \"ruff check .\" },\n    { cmd = \"ruff format --check .\" },\n]\n\n[tool.poe.tasks.all]\nsequence = [\n    { ref = \"lint\" },\n    { ref = \"mypy\" },\n    { ref = \"check\" },\n]\n\n[tool.pytest.ini_options]\nfilterwarnings = [\n    \"once::DeprecationWarning\",\n]\n\n[tool.ruff]\nline-length = 88\nindent-width = 4\ntarget-version = \"py310\"\nexclude = [\"tenacity/_version.py\"]\n\n[tool.ruff.lint]\nselect = [\"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\"]\nignore = [\n    \"B008\",  # function calls in default arguments (intentional API design)\n    \"B905\",  # zip() without strict= (not needed in existing code)\n    \"E501\",  # line too long (formatter handles what it can)\n    \"PYI036\",  # false positive on string-quoted __exit__ annotations\n    \"RUF003\",  # ambiguous unicode characters in comments (copyright names)\n    \"RUF005\",  # iterable unpacking vs concatenation (less readable for tuple +)\n    \"RUF012\",  # mutable class default (test constant, never mutated)\n    \"SIM108\",  # ternary instead of if-else (less readable in context)\n]\n\n[tool.mypy]\nstrict = true\nfiles = [\"tenacity\", \"tests\"]\nshow_error_codes = true\nexclude = [\"tenacity/_version\\\\.py\"]\n\n[[tool.mypy.overrides]]\nmodule = \"tornado.*\"\nignore_missing_imports = true\n\n"
  },
  {
    "path": "releasenotes/notes/Fix-tests-for-typeguard-3.x-6eebfea546b6207e.yaml",
    "content": "---\nfixes:\n  - \"Fixes test failures with typeguard 3.x\"\n"
  },
  {
    "path": "releasenotes/notes/Use--for-formatting-and-validate-using-black-39ec9d57d4691778.yaml",
    "content": "---\nother:\n  - \"Use `black` for code formatting and validate using `black --check`. Code compatibility: py26-py39.\"\n  - \"Enforce maximal line length to 120 symbols\"\n"
  },
  {
    "path": "releasenotes/notes/add-async-actions-b249c527d99723bb.yaml",
    "content": "---\nfeatures:\n  - |\n    Added the ability to use async functions for retries. This way, you can now use\n    asyncio coroutines for retry strategy predicates.\n"
  },
  {
    "path": "releasenotes/notes/add-re-pattern-to-match-types-6a4c1d9e64e2a5e1.yaml",
    "content": "---\nfixes:\n  - |\n    Added `re.Pattern` to allowed match types.\n"
  },
  {
    "path": "releasenotes/notes/add-reno-d1ab5710f272650a.yaml",
    "content": "---\nfeatures:\n  - Add reno (changelog system)\n"
  },
  {
    "path": "releasenotes/notes/add-retry_except_exception_type-31b31da1924d55f4.yaml",
    "content": "---\nfeatures:\n  - Add ``retry_if_not_exception_type()`` that allows to retry if a raised exception doesn't match given exceptions.\n"
  },
  {
    "path": "releasenotes/notes/add-stop-before-delay-a775f88ac872c923.yaml",
    "content": "---\nfeatures:\n  - |\n    Added a new stop function: stop_before_delay, which will stop execution\n    if the next sleep time would cause overall delay to exceed the specified delay. \n    Useful for use cases where you have some upper bound on retry times that you must\n    not exceed, so returning before that timeout is preferable than returning after that timeout."
  },
  {
    "path": "releasenotes/notes/add-test-extra-55e869261b03e56d.yaml",
    "content": "---\nother:\n  - Add a \\\"test\\\" extra\n"
  },
  {
    "path": "releasenotes/notes/add_omitted_modules_to_import_all-2ab282f20a2c22f7.yaml",
    "content": "---\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",
    "content": "---\nfeatures:\n  - |\n    Add a new `retry_base` class called `retry_if_exception_cause_type` that\n    checks, recursively, if any of the causes of the raised exception is of a certain type.\n"
  },
  {
    "path": "releasenotes/notes/added_a_link_to_documentation-eefaf8f074b539f8.yaml",
    "content": "---\nother:\n  - |\n    Added a link to the documentation, as code snippets are not being rendered properly\n    Changed branch name to main in index.rst\n"
  },
  {
    "path": "releasenotes/notes/after_log-50f4d73b24ce9203.yaml",
    "content": "---\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",
    "content": "---\nother:\n  - Unit tests can now mock ``nap.sleep()`` for testing in all tenacity usage styles"
  },
  {
    "path": "releasenotes/notes/annotate_code-197b93130df14042.yaml",
    "content": "---\nother:\n  - Add type annotations to cover all public API.\n"
  },
  {
    "path": "releasenotes/notes/async-sleep-retrying-32de5866f5d041.yaml",
    "content": "---\nfixes:\n  - |\n    Passing an async ``sleep`` callable (e.g. ``trio.sleep``) to ``@retry``\n    now correctly uses ``AsyncRetrying``, even when the decorated function is\n    synchronous. Previously, the async sleep would silently not be awaited,\n    resulting in no delay between retries.\n"
  },
  {
    "path": "releasenotes/notes/before_sleep_log-improvements-d8149274dfb37d7c.yaml",
    "content": "---\nfeatures:\n  - Add an ``exc_info`` option to the ``before_sleep_log()`` strategy."
  },
  {
    "path": "releasenotes/notes/clarify-reraise-option-6829667eacf4f599.yaml",
    "content": "---\nprelude: >\n    Clarify usage of `reraise` keyword argument\n"
  },
  {
    "path": "releasenotes/notes/dependabot-for-github-actions-4d2464f3c0928463.yaml",
    "content": "---\nother:\n  - |\n    Add a Dependabot configuration submit PRs monthly (as needed)\n    to keep GitHub action versions updated.\n"
  },
  {
    "path": "releasenotes/notes/deprecate-initial-for-multiplier-c7b4e2d9f1a83065.yaml",
    "content": "---\ndeprecations:\n  - |\n    The ``initial`` parameter of ``wait_exponential_jitter`` is deprecated in\n    favor of ``multiplier``, for consistency with ``wait_exponential``. Passing\n    ``initial`` still works but emits a ``DeprecationWarning``.\nfeatures:\n  - |\n    Add ``multiplier`` parameter to ``wait_exponential_jitter``, consistent\n    with ``wait_exponential``.\n"
  },
  {
    "path": "releasenotes/notes/do_not_package_tests-fe5ac61940b0a5ed.yaml",
    "content": "---\nother:\n  - Do not package tests with tenacity.\n"
  },
  {
    "path": "releasenotes/notes/drop-deprecated-python-versions-69a05cb2e0f1034c.yaml",
    "content": "---\nother:\n  - |\n    Drop support for deprecated Python versions (2.7 and 3.5)\n"
  },
  {
    "path": "releasenotes/notes/drop-python-3.9-ecfa2d7db9773e96.yaml",
    "content": "---\nupgrade:\n  - |\n    Python 3.9 has reached end-of-life and is no longer supported.\n    The minimum supported version is now Python 3.10.\n"
  },
  {
    "path": "releasenotes/notes/drop_deprecated-7ea90b212509b082.yaml",
    "content": "---\nupgrade:\n  - \"Removed `BaseRetrying.call`: was long time deprecated and produced `DeprecationWarning`\"\n  - \"Removed `BaseRetrying.fn`: was noted as deprecated\"\n  - \"API change: `BaseRetrying.begin()` do not require arguments anymore as it not setting `BaseRetrying.fn`\"\n"
  },
  {
    "path": "releasenotes/notes/export-convenience-symbols-981d9611c8b754f3.yaml",
    "content": "---\nfeatures:\n  - Explicitly export convenience symbols from tenacity root module\n"
  },
  {
    "path": "releasenotes/notes/fix-async-loop-with-result-f68e913ccb425aca.yaml",
    "content": "---\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",
    "content": "---\nfixes:\n  - |\n    The ``@retry`` decorator's type overloads for the ``sleep=`` parameter\n    (e.g. ``sleep=trio.sleep``) have been improved. Previously, the\n    async-sleep overload used ``R | Awaitable[R]`` as the return type\n    bound, which was ambiguous: for ``async def f() -> T``, pyright could\n    infer ``R = Coroutine[Any, Any, T]`` instead of ``R = T``, producing\n    false-positive type errors in downstream code.\n"
  },
  {
    "path": "releasenotes/notes/fix-local-context-overwrite-94190ba06a481631.yaml",
    "content": "---\nfixes:\n  - |\n    Avoid overwriting local contexts when applying the retry decorator.\n"
  },
  {
    "path": "releasenotes/notes/fix-retry-wrapper-attributes-f7a3a45b8e90f257.yaml",
    "content": "---\nfixes:\n  - |\n    Restore the value of the `retry` attribute for wrapped functions. Also,\n    clarify that those attributes are write-only and statistics should be\n    read from the function attribute directly.\n"
  },
  {
    "path": "releasenotes/notes/fix-setuptools-config-3af71aa3592b6948.yaml",
    "content": "---\nfixes:\n  - Fix setuptools config to include tenacity.asyncio package in release distributions.\n"
  },
  {
    "path": "releasenotes/notes/fix-wait-typing-b26eecdb6cc0a1de.yaml",
    "content": "---\nfixes:\n  - |\n    Argument `wait` was improperly annotated, making mypy checks fail.\n    Now it's annotated as `typing.Union[wait_base, typing.Callable[[\"RetryCallState\"], typing.Union[float, int]]]`\n"
  },
  {
    "path": "releasenotes/notes/fix_async-52b6594c8e75c4bc.yaml",
    "content": "---\nfixes:\n  - \"Fix issue #288 : __name__ and other attributes for async functions\"\n"
  },
  {
    "path": "releasenotes/notes/logging-protocol-a4cf0f786f21e4ee.yaml",
    "content": "---\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",
    "content": "---\nfixes:\n  - |\n    Use str.format to format the logs internally to make logging compatible with other logger such as loguru.\n"
  },
  {
    "path": "releasenotes/notes/no-async-iter-6132a42e52348a75.yaml",
    "content": "---\nfixes:\n  - |\n    `AsyncRetrying` was erroneously implementing `__iter__()`, making tenacity\n    retrying mechanism working but in a synchronous fashion and not waiting as\n    expected. This interface has been removed, `__aiter__()` should be used\n    instead.\n"
  },
  {
    "path": "releasenotes/notes/pr320-py3-only-wheel-tag.yaml",
    "content": "---\nother: >-\n  Corrected the PyPI-published wheel tag to match the\n  metadata saying that the release is Python 3 only.\n...\n"
  },
  {
    "path": "releasenotes/notes/py36_plus-c425fb3aa17c6682.yaml",
    "content": "---\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",
    "content": "---\nupgrade:\n  - |\n    Support for Python 3.6 has been removed.\n"
  },
  {
    "path": "releasenotes/notes/retrycallstate-repr-94947f7b00ee15e1.yaml",
    "content": "---\nfeatures:\n  - Add a ``__repr__`` method to ``RetryCallState`` objects for easier debugging.\n"
  },
  {
    "path": "releasenotes/notes/some-slug-for-preserve-defaults-86682846dfa18005.yaml",
    "content": "---\nfixes:\n  - |\n    Preserve __defaults__ and __kwdefaults__ through retry decorator\n"
  },
  {
    "path": "releasenotes/notes/sphinx_define_error-642c9cd5c165d39a.yaml",
    "content": "---\nfixes: Sphinx build error where Sphinx complains about an undefined class.\n"
  },
  {
    "path": "releasenotes/notes/support-py3.14-14928188cab53b99.yaml",
    "content": "---\nfeatures:\n  - Python 3.14 support has been added.\n"
  },
  {
    "path": "releasenotes/notes/support-timedelta-wait-unit-type-5ba1e9fc0fe45523.yaml",
    "content": "---\nfeatures:\n  - Add ``datetime.timedelta`` as accepted wait unit type.\n"
  },
  {
    "path": "releasenotes/notes/timedelta-for-stop-ef6bf71b88ce9988.yaml",
    "content": "---\nfeatures:\n  - |\n    - accept ``datetime.timedelta`` instances as argument to ``tenacity.stop.stop_after_delay``\n"
  },
  {
    "path": "releasenotes/notes/trio-support-retry-22bd544800cd1f36.yaml",
    "content": "---\nfeatures:\n  - |\n    If you're using `Trio <https://trio.readthedocs.io>`__, then\n    ``@retry`` now works automatically. It's no longer necessary to\n    pass ``sleep=trio.sleep``.\n"
  },
  {
    "path": "releasenotes/notes/wait-exponential-jitter-min-timedelta-a8e3c1f4b7d29e50.yaml",
    "content": "---\nfeatures:\n  - |\n    Add ``min`` parameter to ``wait_exponential_jitter`` to set a minimum wait\n    time floor, consistent with ``wait_exponential``. Also accept ``timedelta``\n    for ``max``, ``jitter``, and ``min`` parameters.\n"
  },
  {
    "path": "releasenotes/notes/wait-random-exponential-min-2a4b7eed9f002436.yaml",
    "content": "---\nfixes:\n  - |\n    Respects `min` arg for `wait_random_exponential`\n"
  },
  {
    "path": "releasenotes/notes/wait_exponential_jitter-6ffc81dddcbaa6d3.yaml",
    "content": "---\nfeatures:\n  - |\n    Implement a wait.wait_exponential_jitter per Google's storage retry guide.\n    See https://cloud.google.com/storage/docs/retry-strategy\n"
  },
  {
    "path": "reno.yaml",
    "content": "---\nunreleased_version_title: Unreleased\n"
  },
  {
    "path": "tenacity/__init__.py",
    "content": "# Copyright 2016-2018 Julien Danjou\n# Copyright 2017 Elisey Zanko\n# Copyright 2016 Étienne Bersac\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport dataclasses\nimport functools\nimport sys\nimport threading\nimport time\nimport typing as t\nimport warnings\nfrom abc import ABC, abstractmethod\nfrom concurrent import futures\n\nfrom . import _utils\n\n# Import all built-in after strategies for easier usage.\nfrom .after import after_log, after_nothing\n\n# Import all built-in before strategies for easier usage.\nfrom .before import before_log, before_nothing\n\n# Import all built-in before sleep strategies for easier usage.\nfrom .before_sleep import before_sleep_log, before_sleep_nothing\n\n# Import all nap strategies for easier usage.\nfrom .nap import sleep, sleep_using_event\n\n# Import all built-in retry strategies for easier usage.\nfrom .retry import (\n    retry_all,\n    retry_always,\n    retry_any,\n    retry_base,\n    retry_if_exception,\n    retry_if_exception_cause_type,\n    retry_if_exception_message,\n    retry_if_exception_type,\n    retry_if_not_exception_message,\n    retry_if_not_exception_type,\n    retry_if_not_result,\n    retry_if_result,\n    retry_never,\n    retry_unless_exception_type,\n)\n\n# Import all built-in stop strategies for easier usage.\nfrom .stop import (\n    stop_after_attempt,\n    stop_after_delay,\n    stop_all,\n    stop_any,\n    stop_before_delay,\n    stop_never,\n    stop_when_event_set,\n)\n\n# Import all built-in wait strategies for easier usage.\nfrom .wait import (\n    wait_chain,\n    wait_combine,\n    wait_exception,\n    wait_exponential,\n    wait_exponential_jitter,\n    wait_fixed,\n    wait_incrementing,\n    wait_none,\n    wait_random,\n    wait_random_exponential,\n)\nfrom .wait import wait_random_exponential as wait_full_jitter\n\ntry:\n    import tornado\nexcept ImportError:\n    tornado = None  # type: ignore[assignment]\n\nif t.TYPE_CHECKING:\n    if sys.version_info >= (3, 11):\n        from typing import Self\n    else:\n        from typing_extensions import Self\n\n    import types\n\n    from . import asyncio as tasyncio\n    from .retry import RetryBaseT\n    from .stop import StopBaseT\n    from .wait import WaitBaseT\n\n\nWrappedFnReturnT = t.TypeVar(\"WrappedFnReturnT\")\nWrappedFn = t.TypeVar(\"WrappedFn\", bound=t.Callable[..., t.Any])\nP = t.ParamSpec(\"P\")\nR = t.TypeVar(\"R\")\n\n\n@dataclasses.dataclass(slots=True)\nclass IterState:\n    actions: list[t.Callable[[\"RetryCallState\"], t.Any]] = dataclasses.field(\n        default_factory=list\n    )\n    retry_run_result: bool = False\n    stop_run_result: bool = False\n    is_explicit_retry: bool = False\n\n    def reset(self) -> None:\n        self.actions = []\n        self.retry_run_result = False\n        self.stop_run_result = False\n        self.is_explicit_retry = False\n\n\nclass TryAgain(Exception):\n    \"\"\"Always retry the executed function when raised.\"\"\"\n\n\nNO_RESULT = object()\n\n\nclass DoAttempt:\n    pass\n\n\nclass DoSleep(float):\n    pass\n\n\nclass BaseAction:\n    \"\"\"Base class for representing actions to take by retry object.\n\n    Concrete implementations must define:\n    - __init__: to initialize all necessary fields\n    - REPR_FIELDS: class variable specifying attributes to include in repr(self)\n    - NAME: for identification in retry object methods and callbacks\n    \"\"\"\n\n    REPR_FIELDS: t.Sequence[str] = ()\n    NAME: str | None = None\n\n    def __repr__(self) -> str:\n        state_str = \", \".join(\n            f\"{field}={getattr(self, field)!r}\" for field in self.REPR_FIELDS\n        )\n        return f\"{self.__class__.__name__}({state_str})\"\n\n    def __str__(self) -> str:\n        return repr(self)\n\n\nclass RetryAction(BaseAction):\n    REPR_FIELDS = (\"sleep\",)\n    NAME = \"retry\"\n\n    def __init__(self, sleep: t.SupportsFloat) -> None:\n        self.sleep = float(sleep)\n\n\n_unset = object()\n\n\ndef _first_set(first: t.Any | object, second: t.Any) -> t.Any:\n    return second if first is _unset else first\n\n\nclass RetryError(Exception):\n    \"\"\"Encapsulates the last attempt instance right before giving up.\"\"\"\n\n    def __init__(self, last_attempt: \"Future\") -> None:\n        self.last_attempt = last_attempt\n        super().__init__(last_attempt)\n\n    def reraise(self) -> t.NoReturn:\n        if self.last_attempt.failed:\n            raise self.last_attempt.result()\n        raise self\n\n    def __str__(self) -> str:\n        return f\"{self.__class__.__name__}[{self.last_attempt}]\"\n\n\nclass AttemptManager:\n    \"\"\"Manage attempt context.\"\"\"\n\n    def __init__(self, retry_state: \"RetryCallState\"):\n        self.retry_state = retry_state\n\n    def __enter__(self) -> None:\n        pass\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_value: BaseException | None,\n        traceback: \"types.TracebackType | None\",\n    ) -> bool | None:\n        if exc_type is not None and exc_value is not None:\n            self.retry_state.set_exception((exc_type, exc_value, traceback))\n            return True  # Swallow exception.\n        # We don't have the result, actually.\n        self.retry_state.set_result(None)\n        return None\n\n    async def __aenter__(self) -> None:\n        pass\n\n    async def __aexit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_value: BaseException | None,\n        traceback: \"types.TracebackType | None\",\n    ) -> bool | None:\n        return self.__exit__(exc_type, exc_value, traceback)\n\n\nclass BaseRetrying(ABC):\n    def __init__(\n        self,\n        sleep: t.Callable[[int | float], None] = sleep,\n        stop: \"StopBaseT\" = stop_never,\n        wait: \"WaitBaseT\" = wait_none(),\n        retry: \"RetryBaseT\" = retry_if_exception_type(),\n        before: t.Callable[[\"RetryCallState\"], None] = before_nothing,\n        after: t.Callable[[\"RetryCallState\"], None] = after_nothing,\n        before_sleep: t.Callable[[\"RetryCallState\"], None] | None = None,\n        reraise: bool = False,\n        retry_error_cls: type[RetryError] = RetryError,\n        retry_error_callback: t.Callable[[\"RetryCallState\"], t.Any] | None = None,\n        name: str | None = None,\n        enabled: bool = True,\n    ):\n        self.sleep = sleep\n        self.stop = stop\n        self.wait = wait\n        self.retry = retry\n        self.before = before\n        self.after = after\n        self.before_sleep = before_sleep\n        self.reraise = reraise\n        self._local = threading.local()\n        self.retry_error_cls = retry_error_cls\n        self.retry_error_callback = retry_error_callback\n        self._name = name\n        self.enabled = enabled\n\n    def copy(\n        self,\n        sleep: t.Callable[[int | float], None] | object = _unset,\n        stop: \"StopBaseT | object\" = _unset,\n        wait: \"WaitBaseT | object\" = _unset,\n        retry: retry_base | object = _unset,\n        before: t.Callable[[\"RetryCallState\"], None] | object = _unset,\n        after: t.Callable[[\"RetryCallState\"], None] | object = _unset,\n        before_sleep: t.Callable[[\"RetryCallState\"], None] | None | object = _unset,\n        reraise: bool | object = _unset,\n        retry_error_cls: type[RetryError] | object = _unset,\n        retry_error_callback: t.Callable[[\"RetryCallState\"], t.Any]\n        | None\n        | object = _unset,\n        name: str | None | object = _unset,\n        enabled: bool | object = _unset,\n    ) -> \"Self\":\n        \"\"\"Copy this object with some parameters changed if needed.\"\"\"\n        return self.__class__(\n            sleep=_first_set(sleep, self.sleep),\n            stop=_first_set(stop, self.stop),\n            wait=_first_set(wait, self.wait),\n            retry=_first_set(retry, self.retry),\n            before=_first_set(before, self.before),\n            after=_first_set(after, self.after),\n            before_sleep=_first_set(before_sleep, self.before_sleep),\n            reraise=_first_set(reraise, self.reraise),\n            retry_error_cls=_first_set(retry_error_cls, self.retry_error_cls),\n            retry_error_callback=_first_set(\n                retry_error_callback, self.retry_error_callback\n            ),\n            name=_first_set(name, self._name),\n            enabled=_first_set(enabled, self.enabled),\n        )\n\n    def __getstate__(self) -> dict[str, t.Any]:\n        # Exclude threading.local which cannot be pickled\n        return {k: v for k, v in self.__dict__.items() if k != \"_local\"}\n\n    def __setstate__(self, state: dict[str, t.Any]) -> None:\n        self.__dict__.update(state)\n        self._local = threading.local()\n\n    def __str__(self) -> str:\n        return self._name if self._name is not None else \"<unknown>\"\n\n    def __repr__(self) -> str:\n        return (\n            f\"<{self.__class__.__name__} object at 0x{id(self):x} (\"\n            f\"stop={self.stop}, \"\n            f\"wait={self.wait}, \"\n            f\"sleep={self.sleep}, \"\n            f\"retry={self.retry}, \"\n            f\"before={self.before}, \"\n            f\"after={self.after}, \"\n            f\"name={self._name!r})>\"\n        )\n\n    @property\n    def statistics(self) -> dict[str, t.Any]:\n        \"\"\"Return a dictionary of runtime statistics.\n\n        This dictionary will be empty when the controller has never been\n        ran. When it is running or has ran previously it should have (but\n        may not) have useful and/or informational keys and values when\n        running is underway and/or completed.\n\n        .. warning:: The keys in this dictionary **should** be somewhat\n                     stable (not changing), but their existence **may**\n                     change between major releases as new statistics are\n                     gathered or removed so before accessing keys ensure that\n                     they actually exist and handle when they do not.\n\n        .. note:: The values in this dictionary are local to the thread\n                  running call (so if multiple threads share the same retrying\n                  object - either directly or indirectly) they will each have\n                  their own view of statistics they have collected (in the\n                  future we may provide a way to aggregate the various\n                  statistics from each thread).\n        \"\"\"\n        if not hasattr(self._local, \"statistics\"):\n            self._local.statistics = t.cast(\"dict[str, t.Any]\", {})\n        return self._local.statistics  # type: ignore[no-any-return]\n\n    @property\n    def iter_state(self) -> IterState:\n        if not hasattr(self._local, \"iter_state\"):\n            self._local.iter_state = IterState()\n        return self._local.iter_state  # type: ignore[no-any-return]\n\n    def wraps(self, f: t.Callable[P, R]) -> \"_RetryDecorated[P, R]\":\n        \"\"\"Wrap a function for retrying.\n\n        :param f: A function to wrap for retrying.\n        \"\"\"\n\n        @functools.wraps(\n            f, functools.WRAPPER_ASSIGNMENTS + (\"__defaults__\", \"__kwdefaults__\")\n        )\n        def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:\n            if not self.enabled:\n                return f(*args, **kw)\n            # Always create a copy to prevent overwriting the local contexts when\n            # calling the same wrapped functions multiple times in the same stack\n            copy = self.copy()\n            wrapped_f.statistics = copy.statistics  # type: ignore[attr-defined]\n            self._local.statistics = copy.statistics\n            return copy(f, *args, **kw)\n\n        def retry_with(*args: t.Any, **kwargs: t.Any) -> \"_RetryDecorated[P, R]\":\n            return self.copy(*args, **kwargs).wraps(f)\n\n        # Preserve attributes\n        wrapped_f.retry = self  # type: ignore[attr-defined]\n        wrapped_f.retry_with = retry_with  # type: ignore[attr-defined]\n        wrapped_f.statistics = {}  # type: ignore[attr-defined]\n\n        return t.cast(\"_RetryDecorated[P, R]\", wrapped_f)\n\n    def begin(self) -> None:\n        self.statistics.clear()\n        self.statistics[\"start_time\"] = time.monotonic()\n        self.statistics[\"attempt_number\"] = 1\n        self.statistics[\"idle_for\"] = 0\n        self.statistics[\"delay_since_first_attempt\"] = 0\n\n    def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:\n        self.iter_state.actions.append(fn)\n\n    def _run_retry(self, retry_state: \"RetryCallState\") -> None:\n        self.iter_state.retry_run_result = self.retry(retry_state)\n\n    def _run_wait(self, retry_state: \"RetryCallState\") -> None:\n        if self.wait:\n            sleep = self.wait(retry_state)\n        else:\n            sleep = 0.0\n\n        retry_state.upcoming_sleep = sleep\n\n    def _run_stop(self, retry_state: \"RetryCallState\") -> None:\n        self.statistics[\"delay_since_first_attempt\"] = retry_state.seconds_since_start\n        self.iter_state.stop_run_result = self.stop(retry_state)\n\n    def iter(self, retry_state: \"RetryCallState\") -> DoAttempt | DoSleep | t.Any:\n        self._begin_iter(retry_state)\n        result = None\n        for action in self.iter_state.actions:\n            result = action(retry_state)\n        return result\n\n    def _begin_iter(self, retry_state: \"RetryCallState\") -> None:\n        self.iter_state.reset()\n\n        fut = retry_state.outcome\n        if fut is None:\n            if self.before is not None:\n                self._add_action_func(self.before)\n            self._add_action_func(lambda rs: DoAttempt())\n            return\n\n        self.iter_state.is_explicit_retry = fut.failed and isinstance(\n            fut.exception(), TryAgain\n        )\n        if not self.iter_state.is_explicit_retry:\n            self._add_action_func(self._run_retry)\n        self._add_action_func(self._post_retry_check_actions)\n\n    def _post_retry_check_actions(self, retry_state: \"RetryCallState\") -> None:\n        if not (self.iter_state.is_explicit_retry or self.iter_state.retry_run_result):\n            self._add_action_func(lambda rs: rs.outcome.result())\n            return\n\n        if self.after is not None:\n            self._add_action_func(self.after)\n\n        self._add_action_func(self._run_wait)\n        self._add_action_func(self._run_stop)\n        self._add_action_func(self._post_stop_check_actions)\n\n    def _post_stop_check_actions(self, retry_state: \"RetryCallState\") -> None:\n        if self.iter_state.stop_run_result:\n            if self.retry_error_callback:\n                self._add_action_func(self.retry_error_callback)\n                return\n\n            def exc_check(rs: \"RetryCallState\") -> None:\n                fut = t.cast(\"Future\", rs.outcome)\n                retry_exc = self.retry_error_cls(fut)\n                if self.reraise:\n                    retry_exc.reraise()\n                raise retry_exc from fut.exception()\n\n            self._add_action_func(exc_check)\n            return\n\n        def next_action(rs: \"RetryCallState\") -> None:\n            sleep = rs.upcoming_sleep\n            rs.next_action = RetryAction(sleep)\n            rs.idle_for += sleep\n            self.statistics[\"idle_for\"] += sleep\n            self.statistics[\"attempt_number\"] += 1\n\n        self._add_action_func(next_action)\n\n        if self.before_sleep is not None:\n            self._add_action_func(self.before_sleep)\n\n        self._add_action_func(lambda rs: DoSleep(rs.upcoming_sleep))\n\n    def __iter__(self) -> t.Generator[AttemptManager, None, None]:\n        self.begin()\n\n        retry_state = RetryCallState(self, fn=None, args=(), kwargs={})\n        while True:\n            do = self.iter(retry_state=retry_state)\n            if isinstance(do, DoAttempt):\n                yield AttemptManager(retry_state=retry_state)\n            elif isinstance(do, DoSleep):\n                retry_state.prepare_for_next_attempt()\n                self.sleep(do)\n            else:\n                break\n\n    @abstractmethod\n    def __call__(\n        self,\n        fn: t.Callable[..., WrappedFnReturnT],\n        *args: t.Any,\n        **kwargs: t.Any,\n    ) -> WrappedFnReturnT:\n        pass\n\n\nclass Retrying(BaseRetrying):\n    \"\"\"Retrying controller.\"\"\"\n\n    def __call__(\n        self,\n        fn: t.Callable[..., WrappedFnReturnT],\n        *args: t.Any,\n        **kwargs: t.Any,\n    ) -> WrappedFnReturnT:\n        self.begin()\n\n        retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)\n        while True:\n            do = self.iter(retry_state=retry_state)\n            if isinstance(do, DoAttempt):\n                try:\n                    result = fn(*args, **kwargs)\n                except BaseException:\n                    retry_state.set_exception(sys.exc_info())  # type: ignore[arg-type]\n                else:\n                    retry_state.set_result(result)\n            elif isinstance(do, DoSleep):\n                retry_state.prepare_for_next_attempt()\n                self.sleep(do)\n            else:\n                return do  # type: ignore[no-any-return]\n\n\nclass Future(futures.Future[t.Any]):\n    \"\"\"Encapsulates a (future or past) attempted call to a target function.\"\"\"\n\n    def __init__(self, attempt_number: int) -> None:\n        super().__init__()\n        self.attempt_number = attempt_number\n\n    @property\n    def failed(self) -> bool:\n        \"\"\"Return whether a exception is being held in this future.\"\"\"\n        return self.exception() is not None\n\n    @classmethod\n    def construct(\n        cls, attempt_number: int, value: t.Any, has_exception: bool\n    ) -> \"Future\":\n        \"\"\"Construct a new Future object.\"\"\"\n        fut = cls(attempt_number)\n        if has_exception:\n            fut.set_exception(value)\n        else:\n            fut.set_result(value)\n        return fut\n\n\nclass RetryCallState:\n    \"\"\"State related to a single call wrapped with Retrying.\"\"\"\n\n    def __init__(\n        self,\n        retry_object: BaseRetrying,\n        fn: WrappedFn | None,\n        args: t.Any,\n        kwargs: t.Any,\n    ) -> None:\n        #: Retry call start timestamp\n        self.start_time = time.monotonic()\n        #: Retry manager object\n        self.retry_object = retry_object\n        #: Function wrapped by this retry call\n        self.fn = fn\n        #: Arguments of the function wrapped by this retry call\n        self.args = args\n        #: Keyword arguments of the function wrapped by this retry call\n        self.kwargs = kwargs\n\n        #: The number of the current attempt\n        self.attempt_number: int = 1\n        #: Last outcome (result or exception) produced by the function\n        self.outcome: Future | None = None\n        #: Timestamp of the last outcome\n        self.outcome_timestamp: float | None = None\n        #: Time spent sleeping in retries\n        self.idle_for: float = 0.0\n        #: Next action as decided by the retry manager\n        self.next_action: RetryAction | None = None\n        #: Next sleep time as decided by the retry manager.\n        self.upcoming_sleep: float = 0.0\n\n    def get_fn_name(self) -> str:\n        \"\"\"Get the name of the function being retried.\n\n        Returns the fully-qualified name of the wrapped function when used as a\n        decorator, the ``name`` passed to the retrying object when used as a\n        context manager, or ``\"<unknown>\"`` if neither is available.\n        \"\"\"\n        if self.fn is not None:\n            return _utils.get_callback_name(self.fn)\n        return str(self.retry_object)\n\n    @property\n    def seconds_since_start(self) -> float | None:\n        if self.outcome_timestamp is None:\n            return None\n        return self.outcome_timestamp - self.start_time\n\n    def prepare_for_next_attempt(self) -> None:\n        self.outcome = None\n        self.outcome_timestamp = None\n        self.attempt_number += 1\n        self.next_action = None\n\n    def set_result(self, val: t.Any) -> None:\n        ts = time.monotonic()\n        fut = Future(self.attempt_number)\n        fut.set_result(val)\n        self.outcome, self.outcome_timestamp = fut, ts\n\n    def set_exception(\n        self,\n        exc_info: tuple[\n            type[BaseException], BaseException, \"types.TracebackType | None\"\n        ],\n    ) -> None:\n        ts = time.monotonic()\n        fut = Future(self.attempt_number)\n        fut.set_exception(exc_info[1])\n        self.outcome, self.outcome_timestamp = fut, ts\n\n    def __repr__(self) -> str:\n        if self.outcome is None:\n            result = \"none yet\"\n        elif self.outcome.failed:\n            exception = self.outcome.exception()\n            result = f\"failed ({exception.__class__.__name__} {exception})\"\n        else:\n            result = f\"returned {self.outcome.result()}\"\n\n        slept = float(round(self.idle_for, 2))\n        clsname = self.__class__.__name__\n        return f\"<{clsname} {id(self)}: attempt #{self.attempt_number}; slept for {slept}; last result: {result}>\"\n\n\nclass _RetryDecorated(t.Protocol[P, R]):\n    \"\"\"Protocol for functions decorated with @retry.\n\n    Provides the original callable signature plus retry control attributes.\n    \"\"\"\n\n    retry: \"BaseRetrying\"\n    statistics: dict[str, t.Any]\n\n    def retry_with(self, *args: t.Any, **kwargs: t.Any) -> \"_RetryDecorated[P, R]\": ...\n\n    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ...\n\n\nclass _AsyncRetryDecorator(t.Protocol):\n    @t.overload\n    def __call__(\n        self, fn: \"t.Callable[P, types.CoroutineType[t.Any, t.Any, R]]\"\n    ) -> \"_RetryDecorated[P, types.CoroutineType[t.Any, t.Any, R]]\": ...\n    @t.overload\n    def __call__(\n        self, fn: t.Callable[P, t.Coroutine[t.Any, t.Any, R]]\n    ) -> \"_RetryDecorated[P, t.Coroutine[t.Any, t.Any, R]]\": ...\n    @t.overload\n    def __call__(\n        self, fn: t.Callable[P, t.Awaitable[R]]\n    ) -> \"_RetryDecorated[P, t.Awaitable[R]]\": ...\n    @t.overload\n    def __call__(\n        self, fn: t.Callable[P, R]\n    ) -> \"_RetryDecorated[P, t.Awaitable[R]]\": ...\n\n\n@t.overload\ndef retry(func: t.Callable[P, R]) -> _RetryDecorated[P, R]: ...\n\n\n@t.overload\ndef retry(\n    *,\n    sleep: t.Callable[[int | float], t.Awaitable[None]],\n    stop: \"StopBaseT\" = ...,\n    wait: \"WaitBaseT\" = ...,\n    retry: \"RetryBaseT | tasyncio.retry.RetryBaseT\" = ...,\n    before: t.Callable[[\"RetryCallState\"], None | t.Awaitable[None]] = ...,\n    after: t.Callable[[\"RetryCallState\"], None | t.Awaitable[None]] = ...,\n    before_sleep: t.Callable[[\"RetryCallState\"], None | t.Awaitable[None]] | None = ...,\n    reraise: bool = ...,\n    retry_error_cls: type[\"RetryError\"] = ...,\n    retry_error_callback: t.Callable[[\"RetryCallState\"], t.Any | t.Awaitable[t.Any]]\n    | None = ...,\n    enabled: bool = ...,\n) -> _AsyncRetryDecorator: ...\n\n\n@t.overload\ndef retry(\n    sleep: t.Callable[[int | float], None] = sleep,\n    stop: \"StopBaseT\" = stop_never,\n    wait: \"WaitBaseT\" = wait_none(),\n    retry: \"RetryBaseT | tasyncio.retry.RetryBaseT\" = retry_if_exception_type(),\n    before: t.Callable[[\"RetryCallState\"], None | t.Awaitable[None]] = before_nothing,\n    after: t.Callable[[\"RetryCallState\"], None | t.Awaitable[None]] = after_nothing,\n    before_sleep: t.Callable[[\"RetryCallState\"], None | t.Awaitable[None]]\n    | None = None,\n    reraise: bool = False,\n    retry_error_cls: type[\"RetryError\"] = RetryError,\n    retry_error_callback: t.Callable[[\"RetryCallState\"], t.Any | t.Awaitable[t.Any]]\n    | None = None,\n    enabled: bool = True,\n) -> t.Callable[[t.Callable[P, R]], _RetryDecorated[P, R]]: ...\n\n\ndef retry(*dargs: t.Any, **dkw: t.Any) -> t.Any:\n    \"\"\"Wrap a function with a new `Retrying` object.\n\n    :param dargs: positional arguments passed to Retrying object\n    :param dkw: keyword arguments passed to the Retrying object\n    \"\"\"\n    # support both @retry and @retry() as valid syntax\n    if len(dargs) == 1 and callable(dargs[0]):\n        return retry()(dargs[0])\n\n    def wrap(f: t.Callable[P, R]) -> _RetryDecorated[P, R]:\n        if isinstance(f, retry_base):\n            warnings.warn(\n                f\"Got retry_base instance ({f.__class__.__name__}) as callable argument, \"\n                f\"this will probably hang indefinitely (did you mean retry={f.__class__.__name__}(...)?)\",\n                stacklevel=2,\n            )\n        r: BaseRetrying\n        sleep = dkw.get(\"sleep\")\n        if _utils.is_coroutine_callable(f) or (\n            sleep is not None and _utils.is_coroutine_callable(sleep)\n        ):\n            r = AsyncRetrying(*dargs, **dkw)\n        elif (\n            tornado\n            and hasattr(tornado.gen, \"is_coroutine_function\")\n            and tornado.gen.is_coroutine_function(f)\n        ):\n            r = TornadoRetrying(*dargs, **dkw)\n        else:\n            r = Retrying(*dargs, **dkw)\n\n        return r.wraps(f)\n\n    return wrap\n\n\nfrom tenacity.asyncio import AsyncRetrying  # noqa: E402\n\nif tornado:\n    from tenacity.tornadoweb import TornadoRetrying\n\n\n__all__ = [\n    \"NO_RESULT\",\n    \"AsyncRetrying\",\n    \"AttemptManager\",\n    \"BaseAction\",\n    \"BaseRetrying\",\n    \"DoAttempt\",\n    \"DoSleep\",\n    \"Future\",\n    \"RetryAction\",\n    \"RetryCallState\",\n    \"RetryError\",\n    \"Retrying\",\n    \"TryAgain\",\n    \"WrappedFn\",\n    \"after_log\",\n    \"after_nothing\",\n    \"before_log\",\n    \"before_nothing\",\n    \"before_sleep_log\",\n    \"before_sleep_nothing\",\n    \"retry\",\n    \"retry_all\",\n    \"retry_always\",\n    \"retry_any\",\n    \"retry_base\",\n    \"retry_if_exception\",\n    \"retry_if_exception_cause_type\",\n    \"retry_if_exception_message\",\n    \"retry_if_exception_type\",\n    \"retry_if_not_exception_message\",\n    \"retry_if_not_exception_type\",\n    \"retry_if_not_result\",\n    \"retry_if_result\",\n    \"retry_never\",\n    \"retry_unless_exception_type\",\n    \"sleep\",\n    \"sleep_using_event\",\n    \"stop_after_attempt\",\n    \"stop_after_delay\",\n    \"stop_all\",\n    \"stop_any\",\n    \"stop_before_delay\",\n    \"stop_never\",\n    \"stop_when_event_set\",\n    \"wait_chain\",\n    \"wait_combine\",\n    \"wait_exception\",\n    \"wait_exponential\",\n    \"wait_exponential_jitter\",\n    \"wait_fixed\",\n    \"wait_full_jitter\",\n    \"wait_incrementing\",\n    \"wait_none\",\n    \"wait_random\",\n    \"wait_random_exponential\",\n]\n"
  },
  {
    "path": "tenacity/_utils.py",
    "content": "# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport contextlib\nimport functools\nimport inspect\nimport sys\nimport typing\nfrom datetime import timedelta\n\n# sys.maxsize:\n# An integer giving the maximum value a variable of type Py_ssize_t can take.\nMAX_WAIT = sys.maxsize / 2\n\n\nclass LoggerProtocol(typing.Protocol):\n    \"\"\"\n    Protocol used by utils expecting a logger (eg: before_log).\n\n    Compatible with logging, structlog, loguru, etc...\n    \"\"\"\n\n    def log(self, level: int, msg: str, *args: typing.Any) -> typing.Any: ...\n\n\ndef find_ordinal(pos_num: int) -> str:\n    # See: https://en.wikipedia.org/wiki/English_numerals#Ordinal_numbers\n\n    if 11 <= (pos_num % 100) <= 13:\n        return \"th\"\n\n    if pos_num == 0:\n        return \"th\"\n    if pos_num == 1:\n        return \"st\"\n    if pos_num == 2:\n        return \"nd\"\n    if pos_num == 3:\n        return \"rd\"\n    if 4 <= pos_num <= 20:\n        return \"th\"\n    return find_ordinal(pos_num % 10)\n\n\ndef to_ordinal(pos_num: int) -> str:\n    return f\"{pos_num}{find_ordinal(pos_num)}\"\n\n\ndef get_callback_name(cb: typing.Callable[..., typing.Any]) -> str:\n    \"\"\"Get a callback fully-qualified name.\n\n    If no name can be produced ``repr(cb)`` is called and returned.\n    \"\"\"\n    segments = []\n    try:\n        segments.append(cb.__qualname__)\n    except AttributeError:\n        with contextlib.suppress(AttributeError):\n            segments.append(cb.__name__)\n    if not segments:\n        return repr(cb)\n    with contextlib.suppress(AttributeError):\n        # When running under sphinx it appears this can be none?\n        if cb.__module__:\n            segments.insert(0, cb.__module__)\n    return \".\".join(segments)\n\n\ntime_unit_type = int | float | timedelta\n\n\ndef to_seconds(time_unit: time_unit_type) -> float:\n    return float(\n        time_unit.total_seconds() if isinstance(time_unit, timedelta) else time_unit\n    )\n\n\ndef is_coroutine_callable(call: typing.Callable[..., typing.Any]) -> bool:\n    if inspect.isclass(call):\n        return False\n    if inspect.iscoroutinefunction(call):\n        return True\n    partial_call = isinstance(call, functools.partial) and call.func\n    dunder_call = partial_call or getattr(call, \"__call__\", None)  # noqa: B004\n    return inspect.iscoroutinefunction(dunder_call)\n\n\ndef wrap_to_async_func(\n    call: typing.Callable[..., typing.Any],\n) -> typing.Callable[..., typing.Awaitable[typing.Any]]:\n    if is_coroutine_callable(call):\n        return call\n\n    async def inner(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:\n        return call(*args, **kwargs)\n\n    return inner\n"
  },
  {
    "path": "tenacity/after.py",
    "content": "# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing\n\nfrom tenacity import _utils\n\nif typing.TYPE_CHECKING:\n    from tenacity import RetryCallState\n\n\ndef after_nothing(retry_state: \"RetryCallState\") -> None:\n    \"\"\"After call strategy that does nothing.\"\"\"\n\n\ndef after_log(\n    logger: _utils.LoggerProtocol,\n    log_level: int,\n    sec_format: str = \"%.3g\",\n) -> typing.Callable[[\"RetryCallState\"], None]:\n    \"\"\"After call strategy that logs to some logger the finished attempt.\"\"\"\n\n    def log_it(retry_state: \"RetryCallState\") -> None:\n        fn_name = retry_state.get_fn_name()\n        secs = retry_state.seconds_since_start\n        logger.log(\n            log_level,\n            f\"Finished call to '{fn_name}' \"\n            f\"after {sec_format % secs if secs is not None else '?'}(s), \"\n            f\"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.\",\n        )\n\n    return log_it\n"
  },
  {
    "path": "tenacity/asyncio/__init__.py",
    "content": "# Copyright 2016 Étienne Bersac\n# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport functools\nimport sys\nimport typing as t\n\nimport tenacity\nfrom tenacity import (\n    AttemptManager,\n    BaseRetrying,\n    DoAttempt,\n    DoSleep,\n    RetryCallState,\n    RetryError,\n    _RetryDecorated,\n    _utils,\n    after_nothing,\n    before_nothing,\n)\n\n# Import all built-in retry strategies for easier usage.\nfrom .retry import (\n    RetryBaseT,\n    retry_all,\n    retry_any,\n    retry_if_exception,\n    retry_if_result,\n)\n\nif t.TYPE_CHECKING:\n    from tenacity.retry import RetryBaseT as SyncRetryBaseT\n    from tenacity.stop import StopBaseT\n    from tenacity.wait import WaitBaseT\n\nWrappedFnReturnT = t.TypeVar(\"WrappedFnReturnT\")\nWrappedFn = t.TypeVar(\"WrappedFn\", bound=t.Callable[..., t.Awaitable[t.Any]])\nP = t.ParamSpec(\"P\")\nR = t.TypeVar(\"R\")\n\n\ndef _portable_async_sleep(seconds: float) -> t.Awaitable[None]:\n    # If trio is already imported, then importing it is cheap.\n    # If trio isn't already imported, then it's definitely not running, so we\n    # can skip further checks.\n    if \"trio\" in sys.modules:\n        # If trio is available, then sniffio is too\n        import sniffio\n        import trio\n\n        if sniffio.current_async_library() == \"trio\":\n            return trio.sleep(seconds)  # noqa: ASYNC105\n    # Otherwise, assume asyncio\n    # Lazy import asyncio as it's expensive (responsible for 25-50% of total import overhead).\n    import asyncio\n\n    return asyncio.sleep(seconds)\n\n\nclass AsyncRetrying(BaseRetrying):\n    def __init__(\n        self,\n        sleep: t.Callable[\n            [int | float], None | t.Awaitable[None]\n        ] = _portable_async_sleep,\n        stop: \"StopBaseT\" = tenacity.stop.stop_never,\n        wait: \"WaitBaseT\" = tenacity.wait.wait_none(),\n        retry: \"SyncRetryBaseT | RetryBaseT\" = tenacity.retry_if_exception_type(),\n        before: t.Callable[\n            [\"RetryCallState\"], None | t.Awaitable[None]\n        ] = before_nothing,\n        after: t.Callable[[\"RetryCallState\"], None | t.Awaitable[None]] = after_nothing,\n        before_sleep: t.Callable[[\"RetryCallState\"], None | t.Awaitable[None]]\n        | None = None,\n        reraise: bool = False,\n        retry_error_cls: type[\"RetryError\"] = RetryError,\n        retry_error_callback: t.Callable[[\"RetryCallState\"], t.Any | t.Awaitable[t.Any]]\n        | None = None,\n        name: str | None = None,\n        enabled: bool = True,\n    ) -> None:\n        super().__init__(\n            sleep=sleep,  # type: ignore[arg-type]\n            stop=stop,\n            wait=wait,\n            retry=retry,  # type: ignore[arg-type]\n            before=before,  # type: ignore[arg-type]\n            after=after,  # type: ignore[arg-type]\n            before_sleep=before_sleep,  # type: ignore[arg-type]\n            reraise=reraise,\n            retry_error_cls=retry_error_cls,\n            retry_error_callback=retry_error_callback,\n            name=name,\n            enabled=enabled,\n        )\n\n    async def __call__(  # type: ignore[override]\n        self, fn: WrappedFn, *args: t.Any, **kwargs: t.Any\n    ) -> WrappedFnReturnT:\n        self.begin()\n\n        retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)\n        is_async = _utils.is_coroutine_callable(fn)\n        while True:\n            do = await self.iter(retry_state=retry_state)\n            if isinstance(do, DoAttempt):\n                try:\n                    if is_async:\n                        result = await fn(*args, **kwargs)\n                    else:\n                        result = fn(*args, **kwargs)\n                except BaseException:\n                    retry_state.set_exception(sys.exc_info())  # type: ignore[arg-type]\n                else:\n                    retry_state.set_result(result)\n            elif isinstance(do, DoSleep):\n                retry_state.prepare_for_next_attempt()\n                await self.sleep(do)  # type: ignore[misc]\n            else:\n                return do  # type: ignore[no-any-return]\n\n    def _add_action_func(self, fn: t.Callable[..., t.Any]) -> None:\n        self.iter_state.actions.append(_utils.wrap_to_async_func(fn))\n\n    async def _run_retry(self, retry_state: \"RetryCallState\") -> None:  # type: ignore[override]\n        self.iter_state.retry_run_result = await _utils.wrap_to_async_func(self.retry)(\n            retry_state\n        )\n\n    async def _run_wait(self, retry_state: \"RetryCallState\") -> None:  # type: ignore[override]\n        if self.wait:\n            sleep = await _utils.wrap_to_async_func(self.wait)(retry_state)\n        else:\n            sleep = 0.0\n\n        retry_state.upcoming_sleep = sleep\n\n    async def _run_stop(self, retry_state: \"RetryCallState\") -> None:  # type: ignore[override]\n        self.statistics[\"delay_since_first_attempt\"] = retry_state.seconds_since_start\n        self.iter_state.stop_run_result = await _utils.wrap_to_async_func(self.stop)(\n            retry_state\n        )\n\n    async def iter(self, retry_state: \"RetryCallState\") -> DoAttempt | DoSleep | t.Any:\n        self._begin_iter(retry_state)\n        result = None\n        for action in self.iter_state.actions:\n            result = await action(retry_state)\n        return result\n\n    def __iter__(self) -> t.Generator[AttemptManager, None, None]:\n        raise TypeError(\"AsyncRetrying object is not iterable\")\n\n    def __aiter__(self) -> \"AsyncRetrying\":\n        self.begin()\n        self._retry_state = RetryCallState(self, fn=None, args=(), kwargs={})\n        return self\n\n    async def __anext__(self) -> AttemptManager:\n        while True:\n            do = await self.iter(retry_state=self._retry_state)\n            if do is None:\n                raise StopAsyncIteration\n            if isinstance(do, DoAttempt):\n                return AttemptManager(retry_state=self._retry_state)\n            if isinstance(do, DoSleep):\n                self._retry_state.prepare_for_next_attempt()\n                await self.sleep(do)  # type: ignore[misc]\n            else:\n                raise StopAsyncIteration\n\n    def wraps(self, fn: t.Callable[P, R]) -> _RetryDecorated[P, R]:\n        wrapped = super().wraps(fn)\n        # Ensure wrapper is recognized as a coroutine function.\n\n        @functools.wraps(\n            fn, functools.WRAPPER_ASSIGNMENTS + (\"__defaults__\", \"__kwdefaults__\")\n        )\n        async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:\n            if not self.enabled:\n                return await fn(*args, **kwargs)  # type: ignore[misc]\n            # Always create a copy to prevent overwriting the local contexts when\n            # calling the same wrapped functions multiple times in the same stack\n            copy = self.copy()\n            async_wrapped.statistics = copy.statistics  # type: ignore[attr-defined]\n            self._local.statistics = copy.statistics\n            return await copy(fn, *args, **kwargs)  # type: ignore[type-var]\n\n        # Preserve attributes\n        async_wrapped.retry = self  # type: ignore[attr-defined]\n        async_wrapped.retry_with = wrapped.retry_with  # type: ignore[attr-defined]\n        async_wrapped.statistics = {}  # type: ignore[attr-defined]\n\n        return t.cast(\"_RetryDecorated[P, R]\", async_wrapped)\n\n\n__all__ = [\n    \"AsyncRetrying\",\n    \"WrappedFn\",\n    \"retry_all\",\n    \"retry_any\",\n    \"retry_if_exception\",\n    \"retry_if_result\",\n]\n"
  },
  {
    "path": "tenacity/asyncio/retry.py",
    "content": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport abc\nimport typing\n\nfrom tenacity import _utils, retry_base\n\nif typing.TYPE_CHECKING:\n    from tenacity import RetryCallState\n\n\nclass async_retry_base(retry_base):\n    \"\"\"Abstract base class for async retry strategies.\"\"\"\n\n    @abc.abstractmethod\n    async def __call__(self, retry_state: \"RetryCallState\") -> bool:  # type: ignore[override]\n        pass\n\n    def __and__(  # type: ignore[override]\n        self, other: \"retry_base | async_retry_base\"\n    ) -> \"retry_all\":\n        return retry_all(self, other)\n\n    def __rand__(  # type: ignore[misc,override]\n        self, other: \"retry_base | async_retry_base\"\n    ) -> \"retry_all\":\n        return retry_all(other, self)\n\n    def __or__(  # type: ignore[override]\n        self, other: \"retry_base | async_retry_base\"\n    ) -> \"retry_any\":\n        return retry_any(self, other)\n\n    def __ror__(  # type: ignore[misc,override]\n        self, other: \"retry_base | async_retry_base\"\n    ) -> \"retry_any\":\n        return retry_any(other, self)\n\n\nRetryBaseT = (\n    async_retry_base | typing.Callable[[\"RetryCallState\"], typing.Awaitable[bool]]\n)\n\n\nclass retry_if_exception(async_retry_base):\n    \"\"\"Retry strategy that retries if an exception verifies a predicate.\"\"\"\n\n    def __init__(\n        self, predicate: typing.Callable[[BaseException], typing.Awaitable[bool]]\n    ) -> None:\n        self.predicate = predicate\n\n    async def __call__(self, retry_state: \"RetryCallState\") -> bool:  # type: ignore[override]\n        if retry_state.outcome is None:\n            raise RuntimeError(\"__call__() called before outcome was set\")\n\n        if retry_state.outcome.failed:\n            exception = retry_state.outcome.exception()\n            if exception is None:\n                raise RuntimeError(\"outcome failed but the exception is None\")\n            return await self.predicate(exception)\n        return False\n\n\nclass retry_if_result(async_retry_base):\n    \"\"\"Retries if the result verifies a predicate.\"\"\"\n\n    def __init__(\n        self, predicate: typing.Callable[[typing.Any], typing.Awaitable[bool]]\n    ) -> None:\n        self.predicate = predicate\n\n    async def __call__(self, retry_state: \"RetryCallState\") -> bool:  # type: ignore[override]\n        if retry_state.outcome is None:\n            raise RuntimeError(\"__call__() called before outcome was set\")\n\n        if not retry_state.outcome.failed:\n            return await self.predicate(retry_state.outcome.result())\n        return False\n\n\nclass retry_any(async_retry_base):\n    \"\"\"Retries if any of the retries condition is valid.\"\"\"\n\n    def __init__(self, *retries: retry_base | async_retry_base) -> None:\n        self.retries = retries\n\n    async def __call__(self, retry_state: \"RetryCallState\") -> bool:  # type: ignore[override]\n        result = False\n        for r in self.retries:\n            result = result or await _utils.wrap_to_async_func(r)(retry_state)\n            if result:\n                break\n        return result\n\n    def __ror__(  # type: ignore[misc,override]\n        self, other: \"retry_base | async_retry_base\"\n    ) -> \"retry_any\":\n        if isinstance(other, retry_any):\n            return retry_any(*other.retries, *self.retries)\n        return retry_any(other, *self.retries)\n\n\nclass retry_all(async_retry_base):\n    \"\"\"Retries if all the retries condition are valid.\"\"\"\n\n    def __init__(self, *retries: retry_base | async_retry_base) -> None:\n        self.retries = retries\n\n    async def __call__(self, retry_state: \"RetryCallState\") -> bool:  # type: ignore[override]\n        result = True\n        for r in self.retries:\n            result = result and await _utils.wrap_to_async_func(r)(retry_state)\n            if not result:\n                break\n        return result\n\n    def __rand__(  # type: ignore[misc,override]\n        self, other: \"retry_base | async_retry_base\"\n    ) -> \"retry_all\":\n        if isinstance(other, retry_all):\n            return retry_all(*other.retries, *self.retries)\n        return retry_all(other, *self.retries)\n"
  },
  {
    "path": "tenacity/before.py",
    "content": "# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport typing\n\nfrom tenacity import _utils\n\nif typing.TYPE_CHECKING:\n    from tenacity import RetryCallState\n\n\ndef before_nothing(retry_state: \"RetryCallState\") -> None:\n    \"\"\"Before call strategy that does nothing.\"\"\"\n\n\ndef before_log(\n    logger: _utils.LoggerProtocol, log_level: int\n) -> typing.Callable[[\"RetryCallState\"], None]:\n    \"\"\"Before call strategy that logs to some logger the attempt.\"\"\"\n\n    def log_it(retry_state: \"RetryCallState\") -> None:\n        fn_name = retry_state.get_fn_name()\n        logger.log(\n            log_level,\n            f\"Starting call to '{fn_name}', \"\n            f\"this is the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.\",\n        )\n\n    return log_it\n"
  },
  {
    "path": "tenacity/before_sleep.py",
    "content": "# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport traceback\nimport typing\n\nfrom tenacity import _utils\n\nif typing.TYPE_CHECKING:\n    from tenacity import RetryCallState\n\n\ndef before_sleep_nothing(retry_state: \"RetryCallState\") -> None:\n    \"\"\"Before sleep strategy that does nothing.\"\"\"\n\n\ndef before_sleep_log(\n    logger: _utils.LoggerProtocol,\n    log_level: int,\n    exc_info: bool = False,\n    sec_format: str = \"%.3g\",\n) -> typing.Callable[[\"RetryCallState\"], None]:\n    \"\"\"Before sleep strategy that logs to some logger the attempt.\"\"\"\n\n    def log_it(retry_state: \"RetryCallState\") -> None:\n        if retry_state.outcome is None:\n            raise RuntimeError(\"log_it() called before outcome was set\")\n\n        if retry_state.next_action is None:\n            raise RuntimeError(\"log_it() called before next_action was set\")\n\n        if retry_state.outcome.failed:\n            ex = retry_state.outcome.exception()\n            verb, value = \"raised\", f\"{ex.__class__.__name__}: {ex}\"\n        else:\n            verb, value = \"returned\", retry_state.outcome.result()\n\n        fn_name = retry_state.get_fn_name()\n\n        msg = (\n            f\"Retrying {fn_name} \"\n            f\"in {sec_format % retry_state.next_action.sleep} seconds as it {verb} {value}.\"\n        )\n\n        if exc_info and retry_state.outcome.failed:\n            ex = retry_state.outcome.exception()\n            if ex is not None:\n                tb = \"\".join(traceback.format_exception(type(ex), ex, ex.__traceback__))\n                msg = f\"{msg}\\n{tb.rstrip()}\"\n\n        logger.log(log_level, msg)\n\n    return log_it\n"
  },
  {
    "path": "tenacity/nap.py",
    "content": "# Copyright 2016 Étienne Bersac\n# Copyright 2016 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport time\nimport typing\n\nif typing.TYPE_CHECKING:\n    import threading\n\n\ndef sleep(seconds: float) -> None:\n    \"\"\"\n    Sleep strategy that delays execution for a given number of seconds.\n\n    This is the default strategy, and may be mocked out for unit testing.\n    \"\"\"\n    time.sleep(seconds)\n\n\nclass sleep_using_event:\n    \"\"\"Sleep strategy that waits on an event to be set.\"\"\"\n\n    def __init__(self, event: \"threading.Event\") -> None:\n        self.event = event\n\n    def __call__(self, timeout: float | None) -> None:\n        # NOTE(harlowja): this may *not* actually wait for timeout\n        # seconds if the event is set (ie this may eject out early).\n        self.event.wait(timeout=timeout)\n"
  },
  {
    "path": "tenacity/py.typed",
    "content": ""
  },
  {
    "path": "tenacity/retry.py",
    "content": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport abc\nimport re\nimport typing\n\nif typing.TYPE_CHECKING:\n    from tenacity import RetryCallState\n\n\nclass retry_base(abc.ABC):\n    \"\"\"Abstract base class for retry strategies.\"\"\"\n\n    @abc.abstractmethod\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        pass\n\n    def __and__(self, other: \"RetryBaseT\") -> \"retry_all\":\n        if isinstance(other, retry_base):\n            return other.__rand__(self)\n        # Plain callable: flatten if self is already a retry_all\n        if isinstance(self, retry_all):\n            return retry_all(*self.retries, other)\n        return retry_all(self, other)\n\n    def __rand__(self, other: \"RetryBaseT\") -> \"retry_all\":\n        # Flatten if other is already a retry_all\n        if isinstance(other, retry_all):\n            return retry_all(*other.retries, self)\n        return retry_all(other, self)\n\n    def __or__(self, other: \"RetryBaseT\") -> \"retry_any\":\n        if isinstance(other, retry_base):\n            return other.__ror__(self)\n        # Plain callable: flatten if self is already a retry_any\n        if isinstance(self, retry_any):\n            return retry_any(*self.retries, other)\n        return retry_any(self, other)\n\n    def __ror__(self, other: \"RetryBaseT\") -> \"retry_any\":\n        # Flatten if other is already a retry_any\n        if isinstance(other, retry_any):\n            return retry_any(*other.retries, self)\n        return retry_any(other, self)\n\n\nRetryBaseT = retry_base | typing.Callable[[\"RetryCallState\"], bool]\n\n\nclass _retry_never(retry_base):\n    \"\"\"Retry strategy that never rejects any result.\"\"\"\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        return False\n\n\nretry_never = _retry_never()\n\n\nclass _retry_always(retry_base):\n    \"\"\"Retry strategy that always rejects any result.\"\"\"\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        return True\n\n\nretry_always = _retry_always()\n\n\nclass retry_if_exception(retry_base):\n    \"\"\"Retry strategy that retries if an exception verifies a predicate.\"\"\"\n\n    def __init__(self, predicate: typing.Callable[[BaseException], bool]) -> None:\n        self.predicate = predicate\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        if retry_state.outcome is None:\n            raise RuntimeError(\"__call__() called before outcome was set\")\n\n        if retry_state.outcome.failed:\n            exception = retry_state.outcome.exception()\n            if exception is None:\n                raise RuntimeError(\"outcome failed but the exception is None\")\n            return self.predicate(exception)\n        return False\n\n\nclass retry_if_exception_type(retry_if_exception):\n    \"\"\"Retries if an exception has been raised of one or more types.\"\"\"\n\n    def __init__(\n        self,\n        exception_types: type[BaseException]\n        | tuple[type[BaseException], ...] = Exception,\n    ) -> None:\n        self.exception_types = exception_types\n        super().__init__(self._check)\n\n    def _check(self, e: BaseException) -> bool:\n        return isinstance(e, self.exception_types)\n\n\nclass retry_if_not_exception_type(retry_if_exception):\n    \"\"\"Retries except an exception has been raised of one or more types.\"\"\"\n\n    def __init__(\n        self,\n        exception_types: type[BaseException]\n        | tuple[type[BaseException], ...] = Exception,\n    ) -> None:\n        self.exception_types = exception_types\n        super().__init__(self._check)\n\n    def _check(self, e: BaseException) -> bool:\n        return not isinstance(e, self.exception_types)\n\n\nclass retry_unless_exception_type(retry_if_exception):\n    \"\"\"Retries until an exception is raised of one or more types.\"\"\"\n\n    def __init__(\n        self,\n        exception_types: type[BaseException]\n        | tuple[type[BaseException], ...] = Exception,\n    ) -> None:\n        self.exception_types = exception_types\n        super().__init__(self._check)\n\n    def _check(self, e: BaseException) -> bool:\n        return not isinstance(e, self.exception_types)\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        if retry_state.outcome is None:\n            raise RuntimeError(\"__call__() called before outcome was set\")\n\n        # always retry if no exception was raised\n        if not retry_state.outcome.failed:\n            return True\n\n        exception = retry_state.outcome.exception()\n        if exception is None:\n            raise RuntimeError(\"outcome failed but the exception is None\")\n        return self.predicate(exception)\n\n\nclass retry_if_exception_cause_type(retry_base):\n    \"\"\"Retries if any of the causes of the raised exception is of one or more types.\n\n    The check on the type of the cause of the exception is done recursively (until finding\n    an exception in the chain that has no `__cause__`)\n    \"\"\"\n\n    def __init__(\n        self,\n        exception_types: type[BaseException]\n        | tuple[type[BaseException], ...] = Exception,\n    ) -> None:\n        self.exception_cause_types = exception_types\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        if retry_state.outcome is None:\n            raise RuntimeError(\"__call__ called before outcome was set\")\n\n        if retry_state.outcome.failed:\n            exc = retry_state.outcome.exception()\n            while exc is not None:\n                if isinstance(exc.__cause__, self.exception_cause_types):\n                    return True\n                exc = exc.__cause__\n\n        return False\n\n\nclass retry_if_result(retry_base):\n    \"\"\"Retries if the result verifies a predicate.\"\"\"\n\n    def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None:\n        self.predicate = predicate\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        if retry_state.outcome is None:\n            raise RuntimeError(\"__call__() called before outcome was set\")\n\n        if not retry_state.outcome.failed:\n            return self.predicate(retry_state.outcome.result())\n        return False\n\n\nclass retry_if_not_result(retry_base):\n    \"\"\"Retries if the result refutes a predicate.\"\"\"\n\n    def __init__(self, predicate: typing.Callable[[typing.Any], bool]) -> None:\n        self.predicate = predicate\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        if retry_state.outcome is None:\n            raise RuntimeError(\"__call__() called before outcome was set\")\n\n        if not retry_state.outcome.failed:\n            return not self.predicate(retry_state.outcome.result())\n        return False\n\n\nclass retry_if_exception_message(retry_if_exception):\n    \"\"\"Retries if an exception message equals or matches.\"\"\"\n\n    def __init__(\n        self,\n        message: str | None = None,\n        match: None | str | re.Pattern[str] = None,\n    ) -> None:\n        if message and match:\n            raise TypeError(\n                f\"{self.__class__.__name__}() takes either 'message' or 'match', not both\"\n            )\n\n        if not message and not match:\n            raise TypeError(\n                f\"{self.__class__.__name__}() missing 1 required argument 'message' or 'match'\"\n            )\n\n        self.message = message\n        self.match = re.compile(match) if match else None\n        super().__init__(self._check)\n\n    def _check(self, exception: BaseException) -> bool:\n        if self.message:\n            return self.message == str(exception)\n        assert self.match is not None\n        return bool(self.match.match(str(exception)))\n\n\nclass retry_if_not_exception_message(retry_if_exception_message):\n    \"\"\"Retries until an exception message equals or matches.\"\"\"\n\n    def _check(self, exception: BaseException) -> bool:\n        return not super()._check(exception)\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        if retry_state.outcome is None:\n            raise RuntimeError(\"__call__() called before outcome was set\")\n\n        if not retry_state.outcome.failed:\n            return True\n\n        exception = retry_state.outcome.exception()\n        if exception is None:\n            raise RuntimeError(\"outcome failed but the exception is None\")\n        return self.predicate(exception)\n\n\nclass retry_any(retry_base):\n    \"\"\"Retries if any of the retries condition is valid.\"\"\"\n\n    def __init__(self, *retries: \"RetryBaseT\") -> None:\n        self.retries = retries\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        return any(r(retry_state) for r in self.retries)\n\n    def __ror__(self, other: \"RetryBaseT\") -> \"retry_any\":\n        if isinstance(other, retry_any):\n            return retry_any(*other.retries, *self.retries)\n        return retry_any(other, *self.retries)\n\n\nclass retry_all(retry_base):\n    \"\"\"Retries if all the retries condition are valid.\"\"\"\n\n    def __init__(self, *retries: \"RetryBaseT\") -> None:\n        self.retries = retries\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        return all(r(retry_state) for r in self.retries)\n\n    def __rand__(self, other: \"RetryBaseT\") -> \"retry_all\":\n        if isinstance(other, retry_all):\n            return retry_all(*other.retries, *self.retries)\n        return retry_all(other, *self.retries)\n"
  },
  {
    "path": "tenacity/stop.py",
    "content": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport abc\nimport typing\n\nfrom tenacity import _utils\n\nif typing.TYPE_CHECKING:\n    import threading\n\n    from tenacity import RetryCallState\n\n\nclass stop_base(abc.ABC):\n    \"\"\"Abstract base class for stop strategies.\"\"\"\n\n    @abc.abstractmethod\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        pass\n\n    def __and__(self, other: \"stop_base\") -> \"stop_all\":\n        return stop_all(self, other)\n\n    def __or__(self, other: \"stop_base\") -> \"stop_any\":\n        return stop_any(self, other)\n\n\nStopBaseT = stop_base | typing.Callable[[\"RetryCallState\"], bool]\n\n\nclass stop_any(stop_base):\n    \"\"\"Stop if any of the stop condition is valid.\"\"\"\n\n    def __init__(self, *stops: stop_base) -> None:\n        self.stops = stops\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        return any(x(retry_state) for x in self.stops)\n\n\nclass stop_all(stop_base):\n    \"\"\"Stop if all the stop conditions are valid.\"\"\"\n\n    def __init__(self, *stops: stop_base) -> None:\n        self.stops = stops\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        return all(x(retry_state) for x in self.stops)\n\n\nclass _stop_never(stop_base):\n    \"\"\"Never stop.\"\"\"\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        return False\n\n\nstop_never = _stop_never()\n\n\nclass stop_when_event_set(stop_base):\n    \"\"\"Stop when the given event is set.\"\"\"\n\n    def __init__(self, event: \"threading.Event\") -> None:\n        self.event = event\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        return self.event.is_set()\n\n\nclass stop_after_attempt(stop_base):\n    \"\"\"Stop when the previous attempt >= max_attempt.\"\"\"\n\n    def __init__(self, max_attempt_number: int) -> None:\n        self.max_attempt_number = max_attempt_number\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        return retry_state.attempt_number >= self.max_attempt_number\n\n\nclass stop_after_delay(stop_base):\n    \"\"\"\n    Stop when the time from the first attempt >= limit.\n\n    Note: `max_delay` will be exceeded, so when used with a `wait`, the actual total delay will be greater\n    than `max_delay` by some of the final sleep period before `max_delay` is exceeded.\n\n    If you need stricter timing with waits, consider `stop_before_delay` instead.\n    \"\"\"\n\n    def __init__(self, max_delay: _utils.time_unit_type) -> None:\n        self.max_delay = _utils.to_seconds(max_delay)\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        if retry_state.seconds_since_start is None:\n            raise RuntimeError(\"__call__() called but seconds_since_start is not set\")\n        return retry_state.seconds_since_start >= self.max_delay\n\n\nclass stop_before_delay(stop_base):\n    \"\"\"\n    Stop right before the next attempt would take place after the time from the first attempt >= limit.\n\n    Most useful when you are using with a `wait` function like wait_random_exponential, but need to make\n    sure that the max_delay is not exceeded.\n    \"\"\"\n\n    def __init__(self, max_delay: _utils.time_unit_type) -> None:\n        self.max_delay = _utils.to_seconds(max_delay)\n\n    def __call__(self, retry_state: \"RetryCallState\") -> bool:\n        if retry_state.seconds_since_start is None:\n            raise RuntimeError(\"__call__() called but seconds_since_start is not set\")\n        return (\n            retry_state.seconds_since_start + retry_state.upcoming_sleep\n            >= self.max_delay\n        )\n"
  },
  {
    "path": "tenacity/tornadoweb.py",
    "content": "# Copyright 2017 Elisey Zanko\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport sys\nimport typing\n\nfrom tornado import gen\n\nfrom tenacity import BaseRetrying, DoAttempt, DoSleep, RetryCallState\n\nif typing.TYPE_CHECKING:\n    from tornado.concurrent import Future\n\n_RetValT = typing.TypeVar(\"_RetValT\")\n\n\nclass TornadoRetrying(BaseRetrying):\n    sleep: typing.Callable[..., \"Future[None]\"]\n\n    def __init__(\n        self,\n        sleep: \"typing.Callable[[float], Future[None]]\" = gen.sleep,\n        **kwargs: typing.Any,\n    ) -> None:\n        super().__init__(**kwargs)\n        self.sleep = sleep\n\n    @gen.coroutine\n    def __call__(  # type: ignore[override]\n        self,\n        fn: \"typing.Callable[..., typing.Generator[typing.Any, typing.Any, _RetValT] | Future[_RetValT]]\",\n        *args: typing.Any,\n        **kwargs: typing.Any,\n    ) -> \"typing.Generator[typing.Any, typing.Any, _RetValT]\":\n        self.begin()\n\n        retry_state = RetryCallState(retry_object=self, fn=fn, args=args, kwargs=kwargs)\n        while True:\n            do = self.iter(retry_state=retry_state)\n            if isinstance(do, DoAttempt):\n                try:\n                    result = yield fn(*args, **kwargs)\n                except BaseException:\n                    retry_state.set_exception(sys.exc_info())  # type: ignore[arg-type]\n                else:\n                    retry_state.set_result(result)\n            elif isinstance(do, DoSleep):\n                retry_state.prepare_for_next_attempt()\n                yield self.sleep(do)\n            else:\n                raise gen.Return(do)\n"
  },
  {
    "path": "tenacity/wait.py",
    "content": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013-2014 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport abc\nimport random\nimport typing\nimport warnings\n\nfrom tenacity import _utils\n\nif typing.TYPE_CHECKING:\n    from tenacity import RetryCallState\n\n\nclass wait_base(abc.ABC):\n    \"\"\"Abstract base class for wait strategies.\"\"\"\n\n    @abc.abstractmethod\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        pass\n\n    def __add__(self, other: \"wait_base\") -> \"wait_combine\":\n        return wait_combine(self, other)\n\n    def __radd__(self, other: \"wait_base\") -> \"wait_combine | wait_base\":\n        # make it possible to use multiple waits with the built-in sum function\n        if other == 0:  # type: ignore[comparison-overlap]\n            return self\n        return self.__add__(other)\n\n\nWaitBaseT = wait_base | typing.Callable[[\"RetryCallState\"], float | int]\n\n\nclass wait_fixed(wait_base):\n    \"\"\"Wait strategy that waits a fixed amount of time between each retry.\"\"\"\n\n    def __init__(self, wait: _utils.time_unit_type) -> None:\n        self.wait_fixed = _utils.to_seconds(wait)\n\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        return self.wait_fixed\n\n\nclass wait_none(wait_fixed):\n    \"\"\"Wait strategy that doesn't wait at all before retrying.\"\"\"\n\n    def __init__(self) -> None:\n        super().__init__(0)\n\n\nclass wait_random(wait_base):\n    \"\"\"Wait strategy that waits a random amount of time between min/max.\"\"\"\n\n    def __init__(\n        self, min: _utils.time_unit_type = 0, max: _utils.time_unit_type = 1\n    ) -> None:\n        self.wait_random_min = _utils.to_seconds(min)\n        self.wait_random_max = _utils.to_seconds(max)\n\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        return self.wait_random_min + (\n            random.random() * (self.wait_random_max - self.wait_random_min)\n        )\n\n\nclass wait_combine(wait_base):\n    \"\"\"Combine several waiting strategies.\"\"\"\n\n    def __init__(self, *strategies: wait_base) -> None:\n        self.wait_funcs = strategies\n\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        return sum(x(retry_state=retry_state) for x in self.wait_funcs)\n\n\nclass wait_chain(wait_base):\n    \"\"\"Chain two or more waiting strategies.\n\n    If all strategies are exhausted, the very last strategy is used\n    thereafter.\n\n    For example::\n\n        @retry(wait=wait_chain(*[wait_fixed(1) for i in range(3)] +\n                               [wait_fixed(2) for j in range(5)] +\n                               [wait_fixed(5) for k in range(4)]))\n        def wait_chained():\n            print(\"Wait 1s for 3 attempts, 2s for 5 attempts and 5s \"\n                  \"thereafter.\")\n    \"\"\"\n\n    def __init__(self, *strategies: wait_base) -> None:\n        self.strategies = strategies\n\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        wait_func_no = min(max(retry_state.attempt_number, 1), len(self.strategies))\n        wait_func = self.strategies[wait_func_no - 1]\n        return wait_func(retry_state=retry_state)\n\n\nclass wait_exception(wait_base):\n    \"\"\"Wait strategy that waits the amount of time returned by the predicate.\n\n    The predicate is passed the exception object. Based on the exception, the\n    user can decide how much time to wait before retrying.\n\n    For example::\n\n        def http_error(exception: BaseException) -> float:\n            if (\n                isinstance(exception, requests.HTTPError)\n                and exception.response.status_code == requests.codes.too_many_requests\n            ):\n                return float(exception.response.headers.get(\"Retry-After\", \"1\"))\n            return 60.0\n\n\n        @retry(\n            stop=stop_after_attempt(3),\n            wait=wait_exception(http_error),\n        )\n        def http_get_request(url: str) -> None:\n            response = requests.get(url)\n            response.raise_for_status()\n    \"\"\"\n\n    def __init__(self, predicate: typing.Callable[[BaseException], float]) -> None:\n        self.predicate = predicate\n\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        if retry_state.outcome is None:\n            raise RuntimeError(\"__call__() called before outcome was set\")\n\n        exception = retry_state.outcome.exception()\n        if exception is None:\n            raise RuntimeError(\"outcome failed but the exception is None\")\n        return self.predicate(exception)\n\n\nclass wait_incrementing(wait_base):\n    \"\"\"Wait an incremental amount of time after each attempt.\n\n    Starting at a starting value and incrementing by a value for each attempt\n    (and restricting the upper limit to some maximum value).\n    \"\"\"\n\n    def __init__(\n        self,\n        start: _utils.time_unit_type = 0,\n        increment: _utils.time_unit_type = 100,\n        max: _utils.time_unit_type = _utils.MAX_WAIT,\n    ) -> None:\n        self.start = _utils.to_seconds(start)\n        self.increment = _utils.to_seconds(increment)\n        self.max = _utils.to_seconds(max)\n\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        result = self.start + (self.increment * (retry_state.attempt_number - 1))\n        return max(0, min(result, self.max))\n\n\nclass wait_exponential(wait_base):\n    \"\"\"Wait strategy that applies exponential backoff.\n\n    It allows for a customized multiplier and an ability to restrict the\n    upper and lower limits to some maximum and minimum value.\n\n    The intervals are fixed (i.e. there is no jitter), so this strategy is\n    suitable for balancing retries against latency when a required resource is\n    unavailable for an unknown duration, but *not* suitable for resolving\n    contention between multiple processes for a shared resource. Use\n    wait_random_exponential for the latter case.\n    \"\"\"\n\n    def __init__(\n        self,\n        multiplier: float = 1,\n        max: _utils.time_unit_type = _utils.MAX_WAIT,\n        exp_base: float = 2,\n        min: _utils.time_unit_type = 0,\n    ) -> None:\n        self.multiplier = multiplier\n        self.min = _utils.to_seconds(min)\n        self.max = _utils.to_seconds(max)\n        self.exp_base = exp_base\n\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        try:\n            exp = self.exp_base ** (retry_state.attempt_number - 1)\n            result = self.multiplier * exp\n        except OverflowError:\n            return self.max\n        return max(max(0, self.min), min(result, self.max))\n\n\nclass wait_random_exponential(wait_exponential):\n    \"\"\"Random wait with exponentially widening window.\n\n    An exponential backoff strategy used to mediate contention between multiple\n    uncoordinated processes for a shared resource in distributed systems. This\n    is the sense in which \"exponential backoff\" is meant in e.g. Ethernet\n    networking, and corresponds to the \"Full Jitter\" algorithm described in\n    this blog post:\n\n    https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n\n    Each retry occurs at a random time in a geometrically expanding interval.\n    It allows for a custom multiplier and an ability to restrict the upper\n    limit of the random interval to some maximum value.\n\n    Example::\n\n        wait_random_exponential(multiplier=0.5,  # initial window 0.5s\n                                max=60)          # max 60s timeout\n\n    When waiting for an unavailable resource to become available again, as\n    opposed to trying to resolve contention for a shared resource, the\n    wait_exponential strategy (which uses a fixed interval) may be preferable.\n\n    \"\"\"\n\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        high = super().__call__(retry_state=retry_state)\n        return random.uniform(self.min, high)\n\n\nclass wait_exponential_jitter(wait_base):\n    \"\"\"Wait strategy that applies exponential backoff and jitter.\n\n    It allows for a customized multiplier, maximum wait, jitter and minimum.\n\n    This implements the strategy described here:\n    https://cloud.google.com/storage/docs/retry-strategy\n\n    The wait time is max(min, min(multiplier * 2**n + random.uniform(0, jitter), maximum))\n    where n is the retry count.\n    \"\"\"\n\n    def __init__(\n        self,\n        initial: float = 1,\n        max: _utils.time_unit_type = _utils.MAX_WAIT,\n        exp_base: float = 2,\n        jitter: _utils.time_unit_type = 1,\n        min: _utils.time_unit_type = 0,\n        multiplier: float = 1,\n    ) -> None:\n        if initial != 1 and multiplier != 1:\n            raise ValueError(\n                \"Cannot specify both 'initial' and 'multiplier' — use 'multiplier' only\"\n            )\n\n        if initial != 1:\n            warnings.warn(\n                \"The 'initial' parameter is deprecated, use 'multiplier' instead\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n            multiplier = initial\n\n        self.multiplier = multiplier\n        self.max = _utils.to_seconds(max)\n        self.exp_base = exp_base\n        self.jitter = _utils.to_seconds(jitter)\n        self.min = _utils.to_seconds(min)\n\n    def __call__(self, retry_state: \"RetryCallState\") -> float:\n        jitter = random.uniform(0, self.jitter)\n        try:\n            exp = self.exp_base ** (retry_state.attempt_number - 1)\n            result = self.multiplier * exp + jitter\n        except OverflowError:\n            result = self.max\n        return max(max(0, self.min), min(result, self.max))\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_after.py",
    "content": "import logging\nimport random\nimport unittest.mock\n\nfrom tenacity import (\n    _utils,\n    after_log,\n)\n\nfrom . import test_tenacity\n\n\nclass TestAfterLogFormat(unittest.TestCase):\n    def setUp(self) -> None:\n        self.log_level = random.choice(\n            (\n                logging.DEBUG,\n                logging.INFO,\n                logging.WARNING,\n                logging.ERROR,\n                logging.CRITICAL,\n            )\n        )\n        self.previous_attempt_number = random.randint(1, 512)\n\n    def test_01_default(self) -> None:\n        \"\"\"Test log formatting.\"\"\"\n        log = unittest.mock.MagicMock(spec=\"logging.Logger.log\")\n        logger = unittest.mock.MagicMock(spec=\"logging.Logger\", log=log)\n\n        sec_format = \"%.3g\"\n        delay_since_first_attempt = 0.1\n\n        retry_state = test_tenacity.make_retry_state(\n            self.previous_attempt_number, delay_since_first_attempt\n        )\n        fun = after_log(\n            logger=logger, log_level=self.log_level\n        )  # use default sec_format\n        fun(retry_state)\n        log.assert_called_once_with(\n            self.log_level,\n            f\"Finished call to '{retry_state.get_fn_name()}' \"\n            f\"after {sec_format % retry_state.seconds_since_start}(s), \"\n            f\"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.\",\n        )\n\n    def test_02_none_seconds_since_start(self) -> None:\n        \"\"\"Test log formatting when seconds_since_start is None.\"\"\"\n        log = unittest.mock.MagicMock(spec=\"logging.Logger.log\")\n        logger = unittest.mock.MagicMock(spec=\"logging.Logger\", log=log)\n\n        retry_state = test_tenacity.make_retry_state(self.previous_attempt_number, 0.1)\n        retry_state.outcome_timestamp = None\n        assert retry_state.seconds_since_start is None\n\n        fun = after_log(logger=logger, log_level=self.log_level)\n        fun(retry_state)\n        log.assert_called_once_with(\n            self.log_level,\n            f\"Finished call to '{retry_state.get_fn_name()}' \"\n            f\"after ?(s), \"\n            f\"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.\",\n        )\n\n    def test_02_custom_sec_format(self) -> None:\n        \"\"\"Test log formatting with custom int format..\"\"\"\n        log = unittest.mock.MagicMock(spec=\"logging.Logger.log\")\n        logger = unittest.mock.MagicMock(spec=\"logging.Logger\", log=log)\n\n        sec_format = \"%.1f\"\n        delay_since_first_attempt = 0.1\n\n        retry_state = test_tenacity.make_retry_state(\n            self.previous_attempt_number, delay_since_first_attempt\n        )\n        fun = after_log(logger=logger, log_level=self.log_level, sec_format=sec_format)\n        fun(retry_state)\n        log.assert_called_once_with(\n            self.log_level,\n            f\"Finished call to '{retry_state.get_fn_name()}' \"\n            f\"after {sec_format % retry_state.seconds_since_start}(s), \"\n            f\"this was the {_utils.to_ordinal(retry_state.attempt_number)} time calling it.\",\n        )\n"
  },
  {
    "path": "tests/test_asyncio.py",
    "content": "# Copyright 2016 Étienne Bersac\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport asyncio\nimport inspect\nimport unittest\nfrom collections.abc import Callable, Coroutine\nfrom functools import wraps\nfrom typing import Any, TypeVar\nfrom unittest import mock\n\ntry:\n    import trio\nexcept ImportError:\n    have_trio = False\nelse:\n    have_trio = True\n\nimport pytest\n\nimport tenacity\nfrom tenacity import (\n    AsyncRetrying,\n    RetryCallState,\n    RetryError,\n    retry,\n    retry_if_exception,\n    retry_if_result,\n    stop_after_attempt,\n)\nfrom tenacity import asyncio as tasyncio\nfrom tenacity.wait import wait_fixed\n\nfrom .test_tenacity import (\n    NoIOErrorAfterCount,\n    NoneReturnUntilAfterCount,\n    current_time_ms,\n)\n\n_F = TypeVar(\"_F\", bound=Callable[..., Coroutine[Any, Any, Any]])\n\n\ndef asynctest(callable_: _F) -> Callable[..., Any]:\n    @wraps(callable_)\n    def wrapper(*a: Any, **kw: Any) -> Any:\n        return asyncio.run(callable_(*a, **kw))\n\n    return wrapper\n\n\nasync def _async_function(thing: NoIOErrorAfterCount) -> Any:\n    await asyncio.sleep(0.00001)\n    return thing.go()\n\n\n@retry\nasync def _retryable_coroutine(thing: NoIOErrorAfterCount) -> Any:\n    await asyncio.sleep(0.00001)\n    return thing.go()\n\n\n@retry(stop=stop_after_attempt(2))\nasync def _retryable_coroutine_with_2_attempts(thing: NoIOErrorAfterCount) -> Any:\n    await asyncio.sleep(0.00001)\n    return thing.go()\n\n\nclass TestAsyncio(unittest.TestCase):\n    @asynctest\n    async def test_retry(self) -> None:\n        thing = NoIOErrorAfterCount(5)\n        await _retryable_coroutine(thing)\n        assert thing.counter == thing.count\n\n    @asynctest\n    async def test_iscoroutinefunction(self) -> None:\n        assert asyncio.iscoroutinefunction(_retryable_coroutine)\n        assert inspect.iscoroutinefunction(_retryable_coroutine)\n\n    @asynctest\n    async def test_retry_using_async_retying(self) -> None:\n        thing = NoIOErrorAfterCount(5)\n        retrying = AsyncRetrying()\n        await retrying(_async_function, thing)\n        assert thing.counter == thing.count\n\n    @asynctest\n    async def test_stop_after_attempt(self) -> None:\n        thing = NoIOErrorAfterCount(2)\n        try:\n            await _retryable_coroutine_with_2_attempts(thing)\n        except RetryError:\n            assert thing.counter == 2\n\n    def test_repr(self) -> None:\n        repr(tasyncio.AsyncRetrying())\n\n    def test_retry_attributes(self) -> None:\n        assert hasattr(_retryable_coroutine, \"retry\")\n        assert hasattr(_retryable_coroutine, \"retry_with\")\n\n    def test_retry_preserves_argument_defaults(self) -> None:\n        async def function_with_defaults(a: int = 1) -> int:\n            return a\n\n        async def function_with_kwdefaults(*, a: int = 1) -> int:\n            return a\n\n        retrying = AsyncRetrying(\n            wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3)\n        )\n        wrapped_defaults_function = retrying.wraps(function_with_defaults)\n        wrapped_kwdefaults_function = retrying.wraps(function_with_kwdefaults)\n\n        self.assertEqual(\n            function_with_defaults.__defaults__,\n            wrapped_defaults_function.__defaults__,  # type: ignore[attr-defined]\n        )\n        self.assertEqual(\n            function_with_kwdefaults.__kwdefaults__,\n            wrapped_kwdefaults_function.__kwdefaults__,  # type: ignore[attr-defined]\n        )\n\n    @asynctest\n    async def test_attempt_number_is_correct_for_interleaved_coroutines(self) -> None:\n        attempts: list[Any] = []\n\n        def after(retry_state: RetryCallState) -> None:\n            attempts.append((retry_state.args[0], retry_state.attempt_number))\n\n        thing1 = NoIOErrorAfterCount(3)\n        thing2 = NoIOErrorAfterCount(3)\n\n        await asyncio.gather(\n            _retryable_coroutine.retry_with(after=after)(thing1),\n            _retryable_coroutine.retry_with(after=after)(thing2),\n        )\n\n        # There's no waiting on retry, only a wait in the coroutine, so the\n        # executions should be interleaved.\n        even_thing_attempts = attempts[::2]\n        things, attempt_nos1 = zip(*even_thing_attempts)\n        assert len(set(things)) == 1\n        assert list(attempt_nos1) == [1, 2, 3]\n\n        odd_thing_attempts = attempts[1::2]\n        things, attempt_nos2 = zip(*odd_thing_attempts)\n        assert len(set(things)) == 1\n        assert list(attempt_nos2) == [1, 2, 3]\n\n\nclass TestAsyncEnabled(unittest.TestCase):\n    @asynctest\n    async def test_enabled_false_skips_retry(self) -> None:\n        \"\"\"When enabled=False, async function is called directly without retrying.\"\"\"\n        call_count = 0\n\n        @retry(enabled=False, stop=stop_after_attempt(3))\n        async def always_fails() -> None:\n            nonlocal call_count\n            call_count += 1\n            raise ValueError(\"fail\")\n\n        with pytest.raises(ValueError, match=\"fail\"):\n            await always_fails()\n        assert call_count == 1\n\n\n@unittest.skipIf(not have_trio, \"trio not installed\")\nclass TestTrio(unittest.TestCase):\n    def test_trio_basic(self) -> None:\n        thing = NoIOErrorAfterCount(5)\n\n        @retry\n        async def trio_function() -> Any:\n            await trio.sleep(0.00001)\n            return thing.go()\n\n        trio.run(trio_function)\n\n        assert thing.counter == thing.count\n\n\nclass TestContextManager(unittest.TestCase):\n    @asynctest\n    async def test_do_max_attempts(self) -> None:\n        attempts = 0\n        retrying = tasyncio.AsyncRetrying(stop=stop_after_attempt(3))\n        try:\n            async for attempt in retrying:\n                with attempt:\n                    attempts += 1\n                    raise Exception\n        except RetryError:\n            pass\n\n        assert attempts == 3\n\n    @asynctest\n    async def test_async_with_attempt_manager(self) -> None:\n        \"\"\"AttemptManager supports async with for use inside async for.\"\"\"\n        attempts = 0\n        retrying = tasyncio.AsyncRetrying(stop=stop_after_attempt(3))\n        try:\n            async for attempt in retrying:\n                async with attempt:\n                    attempts += 1\n                    raise Exception\n        except RetryError:\n            pass\n\n        assert attempts == 3\n\n    @asynctest\n    async def test_reraise(self) -> None:\n        class CustomError(Exception):\n            pass\n\n        try:\n            async for attempt in tasyncio.AsyncRetrying(\n                stop=stop_after_attempt(1), reraise=True\n            ):\n                with attempt:\n                    raise CustomError\n        except CustomError:\n            pass\n        else:\n            raise Exception\n\n    @asynctest\n    async def test_sleeps(self) -> None:\n        start = current_time_ms()\n        try:\n            async for attempt in tasyncio.AsyncRetrying(\n                stop=stop_after_attempt(1), wait=wait_fixed(1)\n            ):\n                with attempt:\n                    raise Exception\n        except RetryError:\n            pass\n        t = current_time_ms() - start\n        self.assertLess(t, 1.1)\n\n    @asynctest\n    async def test_retry_with_result(self) -> None:\n        async def test() -> int:\n            attempts = 0\n\n            # mypy doesn't have great lambda support\n            def lt_3(x: float) -> bool:\n                return x < 3\n\n            async for attempt in tasyncio.AsyncRetrying(retry=retry_if_result(lt_3)):\n                with attempt:\n                    attempts += 1\n                attempt.retry_state.set_result(attempts)\n            return attempts\n\n        result = await test()\n\n        self.assertEqual(3, result)\n\n    @asynctest\n    async def test_retry_with_async_result(self) -> None:\n        async def test() -> int:\n            attempts = 0\n\n            async def lt_3(x: float) -> bool:\n                return x < 3\n\n            async for attempt in tasyncio.AsyncRetrying(\n                retry=tasyncio.retry_if_result(lt_3)\n            ):\n                with attempt:\n                    attempts += 1\n\n                assert attempt.retry_state.outcome  # help mypy\n                if not attempt.retry_state.outcome.failed:\n                    attempt.retry_state.set_result(attempts)\n\n            return attempts\n\n        result = await test()\n\n        self.assertEqual(3, result)\n\n    @asynctest\n    async def test_retry_with_async_exc(self) -> None:\n        async def test() -> int:\n            attempts = 0\n\n            class CustomException(Exception):\n                pass\n\n            async def is_exc(e: BaseException) -> bool:\n                return isinstance(e, CustomException)\n\n            async for attempt in tasyncio.AsyncRetrying(\n                retry=tasyncio.retry_if_exception(is_exc)\n            ):\n                with attempt:\n                    attempts += 1\n                    if attempts < 3:\n                        raise CustomException\n\n                assert attempt.retry_state.outcome  # help mypy\n                if not attempt.retry_state.outcome.failed:\n                    attempt.retry_state.set_result(attempts)\n\n            return attempts\n\n        result = await test()\n\n        self.assertEqual(3, result)\n\n    @asynctest\n    async def test_retry_with_async_result_or(self) -> None:\n        async def test() -> int:\n            attempts = 0\n\n            async def lt_3(x: float) -> bool:\n                return x < 3\n\n            class CustomException(Exception):\n                pass\n\n            def is_exc(e: BaseException) -> bool:\n                return isinstance(e, CustomException)\n\n            retry_strategy = tasyncio.retry_if_result(lt_3) | retry_if_exception(is_exc)\n            async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):\n                with attempt:\n                    attempts += 1\n                    if 2 < attempts < 4:\n                        raise CustomException\n\n                assert attempt.retry_state.outcome  # help mypy\n                if not attempt.retry_state.outcome.failed:\n                    attempt.retry_state.set_result(attempts)\n\n            return attempts\n\n        result = await test()\n\n        self.assertEqual(4, result)\n\n    @asynctest\n    async def test_retry_with_async_result_ror(self) -> None:\n        async def test() -> int:\n            attempts = 0\n\n            def lt_3(x: float) -> bool:\n                return x < 3\n\n            class CustomException(Exception):\n                pass\n\n            async def is_exc(e: BaseException) -> bool:\n                return isinstance(e, CustomException)\n\n            retry_strategy = retry_if_result(lt_3) | tasyncio.retry_if_exception(is_exc)\n            async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):\n                with attempt:\n                    attempts += 1\n                    if 2 < attempts < 4:\n                        raise CustomException\n\n                assert attempt.retry_state.outcome  # help mypy\n                if not attempt.retry_state.outcome.failed:\n                    attempt.retry_state.set_result(attempts)\n\n            return attempts\n\n        result = await test()\n\n        self.assertEqual(4, result)\n\n    @asynctest\n    async def test_retry_with_async_result_and(self) -> None:\n        async def test() -> int:\n            attempts = 0\n\n            async def lt_3(x: float) -> bool:\n                return x < 3\n\n            def gt_0(x: float) -> bool:\n                return x > 0\n\n            retry_strategy = tasyncio.retry_if_result(lt_3) & retry_if_result(gt_0)\n            async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):\n                with attempt:\n                    attempts += 1\n                attempt.retry_state.set_result(attempts)\n\n            return attempts\n\n        result = await test()\n\n        self.assertEqual(3, result)\n\n    @asynctest\n    async def test_retry_with_async_result_rand(self) -> None:\n        async def test() -> int:\n            attempts = 0\n\n            async def lt_3(x: float) -> bool:\n                return x < 3\n\n            def gt_0(x: float) -> bool:\n                return x > 0\n\n            retry_strategy = retry_if_result(gt_0) & tasyncio.retry_if_result(lt_3)\n            async for attempt in tasyncio.AsyncRetrying(retry=retry_strategy):\n                with attempt:\n                    attempts += 1\n                attempt.retry_state.set_result(attempts)\n\n            return attempts\n\n        result = await test()\n\n        self.assertEqual(3, result)\n\n    @asynctest\n    async def test_async_retying_iterator(self) -> None:\n        thing = NoIOErrorAfterCount(5)\n        with pytest.raises(TypeError):\n            for attempts in AsyncRetrying():\n                with attempts:\n                    await _async_function(thing)\n\n\nclass TestDecoratorWrapper(unittest.TestCase):\n    @asynctest\n    async def test_retry_function_attributes(self) -> None:\n        \"\"\"Test that the wrapped function attributes are exposed as intended.\n\n        - statistics contains the value for the latest function run\n        - retry object can be modified to change its behaviour (useful to patch in tests)\n        - retry object statistics are synced with function statistics\n        \"\"\"\n\n        self.assertTrue(\n            await _retryable_coroutine_with_2_attempts(NoIOErrorAfterCount(1))\n        )\n\n        expected_stats = {\n            \"attempt_number\": 2,\n            \"delay_since_first_attempt\": mock.ANY,\n            \"idle_for\": mock.ANY,\n            \"start_time\": mock.ANY,\n        }\n        self.assertEqual(\n            _retryable_coroutine_with_2_attempts.statistics,\n            expected_stats,\n        )\n        self.assertEqual(\n            _retryable_coroutine_with_2_attempts.retry.statistics,\n            expected_stats,\n        )\n\n        with mock.patch.object(\n            _retryable_coroutine_with_2_attempts.retry,\n            \"stop\",\n            tenacity.stop_after_attempt(1),\n        ):\n            try:\n                self.assertTrue(\n                    await _retryable_coroutine_with_2_attempts(NoIOErrorAfterCount(2))\n                )\n            except RetryError as exc:\n                expected_stats = {\n                    \"attempt_number\": 1,\n                    \"delay_since_first_attempt\": mock.ANY,\n                    \"idle_for\": mock.ANY,\n                    \"start_time\": mock.ANY,\n                }\n                self.assertEqual(\n                    _retryable_coroutine_with_2_attempts.statistics,\n                    expected_stats,\n                )\n                self.assertEqual(exc.last_attempt.attempt_number, 1)\n                self.assertEqual(\n                    _retryable_coroutine_with_2_attempts.retry.statistics,\n                    expected_stats,\n                )\n            else:\n                self.fail(\"RetryError should have been raised after 1 attempt\")\n\n\n# make sure mypy accepts passing an async sleep function\n# https://github.com/jd/tenacity/issues/399\nasync def my_async_sleep(x: float) -> None:\n    await asyncio.sleep(x)\n\n\n@retry(sleep=my_async_sleep)\nasync def foo() -> None:\n    pass\n\n\nclass TestSyncFunctionWithAsyncSleep(unittest.TestCase):\n    @asynctest\n    async def test_sync_function_with_async_sleep(self) -> None:\n        \"\"\"A sync function with an async sleep callable uses AsyncRetrying.\"\"\"\n        mock_sleep = mock.AsyncMock()\n\n        thing = NoneReturnUntilAfterCount(2)\n\n        @retry(\n            sleep=mock_sleep,\n            wait=wait_fixed(1),\n            retry=retry_if_result(lambda x: x is None),\n        )\n        def sync_function() -> Any:\n            return thing.go()\n\n        result = await sync_function()\n        assert result is True\n        assert mock_sleep.await_count == 2\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_issue_478.py",
    "content": "import asyncio\nimport typing\nimport unittest\nfrom functools import wraps\n\nfrom tenacity import RetryCallState, retry\n\n\ndef asynctest(\n    callable_: typing.Callable[..., typing.Any],\n) -> typing.Callable[..., typing.Any]:\n    @wraps(callable_)\n    def wrapper(*a: typing.Any, **kw: typing.Any) -> typing.Any:\n        return asyncio.run(callable_(*a, **kw))\n\n    return wrapper\n\n\nMAX_RETRY_FIX_ATTEMPTS = 2\n\n\nclass TestIssue478(unittest.TestCase):\n    def test_issue(self) -> None:\n        results = []\n\n        def do_retry(retry_state: RetryCallState) -> bool:\n            outcome = retry_state.outcome\n            assert outcome\n            ex = outcome.exception()\n            _subject_: str = retry_state.args[0]\n\n            if _subject_ == \"Fix\":  # no retry on fix failure\n                return False\n\n            if retry_state.attempt_number >= MAX_RETRY_FIX_ATTEMPTS:\n                return False\n\n            if ex:\n                do_fix_work()\n                return True\n\n            return False\n\n        @retry(reraise=True, retry=do_retry)\n        def _do_work(subject: str) -> None:\n            if subject == \"Error\":\n                results.append(f\"{subject} is not working\")\n                raise Exception(f\"{subject} is not working\")\n            results.append(f\"{subject} is working\")\n\n        def do_any_work(subject: str) -> None:\n            _do_work(subject)\n\n        def do_fix_work() -> None:\n            _do_work(\"Fix\")\n\n        try:\n            do_any_work(\"Error\")\n        except Exception as exc:\n            assert str(exc) == \"Error is not working\"\n        else:\n            raise AssertionError(\"No exception caught\")\n\n        assert results == [\n            \"Error is not working\",\n            \"Fix is working\",\n            \"Error is not working\",\n        ]\n\n    @asynctest\n    async def test_async(self) -> None:\n        results = []\n\n        async def do_retry(retry_state: RetryCallState) -> bool:\n            outcome = retry_state.outcome\n            assert outcome\n            ex = outcome.exception()\n            _subject_: str = retry_state.args[0]\n\n            if _subject_ == \"Fix\":  # no retry on fix failure\n                return False\n\n            if retry_state.attempt_number >= MAX_RETRY_FIX_ATTEMPTS:\n                return False\n\n            if ex:\n                await do_fix_work()\n                return True\n\n            return False\n\n        @retry(reraise=True, retry=do_retry)\n        async def _do_work(subject: str) -> None:\n            if subject == \"Error\":\n                results.append(f\"{subject} is not working\")\n                raise Exception(f\"{subject} is not working\")\n            results.append(f\"{subject} is working\")\n\n        async def do_any_work(subject: str) -> None:\n            await _do_work(subject)\n\n        async def do_fix_work() -> None:\n            await _do_work(\"Fix\")\n\n        try:\n            await do_any_work(\"Error\")\n        except Exception as exc:\n            assert str(exc) == \"Error is not working\"\n        else:\n            raise AssertionError(\"No exception caught\")\n\n        assert results == [\n            \"Error is not working\",\n            \"Fix is working\",\n            \"Error is not working\",\n        ]\n"
  },
  {
    "path": "tests/test_tenacity.py",
    "content": "# Copyright 2016–2021 Julien Danjou\n# Copyright 2016 Joshua Harlow\n# Copyright 2013 Ray Holder\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport contextlib\nimport datetime\nimport logging\nimport pickle\nimport re\nimport time\nimport typing\nimport unittest\nfrom fractions import Fraction\nfrom unittest import mock\n\nimport pytest\nfrom typeguard import check_type\n\nimport tenacity\nfrom tenacity import RetryCallState, RetryError, Retrying, retry\nfrom tenacity.retry import retry_all, retry_any\n\n_unset = object()\n\n\ndef _make_unset_exception(func_name: str, **kwargs: typing.Any) -> TypeError:\n    missing = []\n    for k, v in kwargs.items():\n        if v is _unset:\n            missing.append(k)\n    missing_str = \", \".join(repr(s) for s in missing)\n    return TypeError(func_name + \" func missing parameters: \" + missing_str)\n\n\ndef _set_delay_since_start(retry_state: RetryCallState, delay: typing.Any) -> None:\n    # Ensure outcome_timestamp - start_time is *exactly* equal to the delay to\n    # avoid complexity in test code.\n    retry_state.start_time = Fraction(retry_state.start_time)  # type: ignore[assignment]\n    retry_state.outcome_timestamp = retry_state.start_time + Fraction(delay)\n    assert retry_state.seconds_since_start == delay\n\n\ndef make_retry_state(\n    previous_attempt_number: typing.Any,\n    delay_since_first_attempt: typing.Any,\n    last_result: typing.Any = None,\n    upcoming_sleep: typing.Any = 0,\n) -> RetryCallState:\n    \"\"\"Construct RetryCallState for given attempt number & delay.\n\n    Only used in testing and thus is extra careful about timestamp arithmetics.\n    \"\"\"\n    required_parameter_unset = (\n        previous_attempt_number is _unset or delay_since_first_attempt is _unset\n    )\n    if required_parameter_unset:\n        raise _make_unset_exception(\n            \"wait/stop\",\n            previous_attempt_number=previous_attempt_number,\n            delay_since_first_attempt=delay_since_first_attempt,\n        )\n\n    retry_state = RetryCallState(None, None, (), {})  # type: ignore[arg-type]\n    retry_state.attempt_number = previous_attempt_number\n    if last_result is not None:\n        retry_state.outcome = last_result\n    else:\n        retry_state.set_result(None)\n\n    retry_state.upcoming_sleep = upcoming_sleep\n\n    _set_delay_since_start(retry_state, delay_since_first_attempt)\n    return retry_state\n\n\nclass TestBase(unittest.TestCase):\n    def test_retrying_repr(self) -> None:\n        class ConcreteRetrying(tenacity.BaseRetrying):\n            def __call__(\n                self, fn: typing.Any, *args: typing.Any, **kwargs: typing.Any\n            ) -> typing.Any:\n                pass\n\n        repr(ConcreteRetrying())\n\n    def test_callstate_repr(self) -> None:\n        rs = RetryCallState(None, None, (), {})  # type: ignore[arg-type]\n        rs.idle_for = 1.1111111\n        assert repr(rs).endswith(\"attempt #1; slept for 1.11; last result: none yet>\")\n        rs = make_retry_state(2, 5)\n        assert repr(rs).endswith(\n            \"attempt #2; slept for 0.0; last result: returned None>\"\n        )\n        rs = make_retry_state(\n            0, 0, last_result=tenacity.Future.construct(1, ValueError(\"aaa\"), True)\n        )\n        assert repr(rs).endswith(\n            \"attempt #0; slept for 0.0; last result: failed (ValueError aaa)>\"\n        )\n\n\nclass TestRetryingName(unittest.TestCase):\n    def test_str_default(self) -> None:\n        \"\"\"Without a name, str() returns '<unknown>'.\"\"\"\n        assert str(Retrying()) == \"<unknown>\"\n\n    def test_str_with_name(self) -> None:\n        \"\"\"With a name, str() returns the given name.\"\"\"\n        assert str(Retrying(name=\"my_block\")) == \"my_block\"\n\n    def test_str_preserved_by_copy(self) -> None:\n        \"\"\"copy() preserves the name.\"\"\"\n        r = Retrying(name=\"my_block\")\n        assert str(r.copy()) == \"my_block\"\n\n    def test_str_overridden_by_copy(self) -> None:\n        \"\"\"copy() allows overriding the name.\"\"\"\n        r = Retrying(name=\"original\")\n        assert str(r.copy(name=\"overridden\")) == \"overridden\"\n\n    def test_get_fn_name_decorator(self) -> None:\n        \"\"\"get_fn_name() returns the function's qualified name when used as decorator.\"\"\"\n        captured: list[RetryCallState] = []\n\n        @tenacity.retry(\n            stop=tenacity.stop_after_attempt(1),\n            after=lambda rs: captured.append(rs),\n        )\n        def my_func() -> None:\n            raise ValueError\n\n        with contextlib.suppress(Exception):\n            my_func()\n        assert captured\n        assert \"my_func\" in captured[0].get_fn_name()\n\n    def test_get_fn_name_context_manager_no_name(self) -> None:\n        \"\"\"get_fn_name() returns '<unknown>' in context manager mode without a name.\"\"\"\n        r = Retrying(stop=tenacity.stop_after_attempt(1))\n        rs = RetryCallState(r, None, (), {})\n        assert rs.get_fn_name() == \"<unknown>\"\n\n    def test_get_fn_name_context_manager_with_name(self) -> None:\n        \"\"\"get_fn_name() returns the given name in context manager mode.\"\"\"\n        r = Retrying(name=\"ws_listener\", stop=tenacity.stop_after_attempt(1))\n        rs = RetryCallState(r, None, (), {})\n        assert rs.get_fn_name() == \"ws_listener\"\n\n    def test_logging_uses_name(self) -> None:\n        \"\"\"before_log uses the name parameter in context manager mode.\"\"\"\n        import unittest.mock\n\n        log = unittest.mock.MagicMock()\n        logger = unittest.mock.MagicMock(log=log)\n\n        with contextlib.suppress(Exception):\n            for attempt in Retrying(\n                name=\"my_block\",\n                before=tenacity.before_log(logger, logging.INFO),\n                stop=tenacity.stop_after_attempt(1),\n            ):\n                with attempt:\n                    raise ValueError\n\n        args = log.call_args[0]\n        assert \"my_block\" in args[1]\n\n\nclass TestStopConditions(unittest.TestCase):\n    def test_never_stop(self) -> None:\n        r = Retrying()\n        self.assertFalse(r.stop(make_retry_state(3, 6546)))\n\n    def test_stop_any(self) -> None:\n        stop = tenacity.stop_any(\n            tenacity.stop_after_delay(1), tenacity.stop_after_attempt(4)\n        )\n\n        def s(*args: typing.Any) -> bool:\n            return stop(make_retry_state(*args))\n\n        self.assertFalse(s(1, 0.1))\n        self.assertFalse(s(2, 0.2))\n        self.assertFalse(s(2, 0.8))\n        self.assertTrue(s(4, 0.8))\n        self.assertTrue(s(3, 1.8))\n        self.assertTrue(s(4, 1.8))\n\n    def test_stop_all(self) -> None:\n        stop = tenacity.stop_all(\n            tenacity.stop_after_delay(1), tenacity.stop_after_attempt(4)\n        )\n\n        def s(*args: typing.Any) -> bool:\n            return stop(make_retry_state(*args))\n\n        self.assertFalse(s(1, 0.1))\n        self.assertFalse(s(2, 0.2))\n        self.assertFalse(s(2, 0.8))\n        self.assertFalse(s(4, 0.8))\n        self.assertFalse(s(3, 1.8))\n        self.assertTrue(s(4, 1.8))\n\n    def test_stop_or(self) -> None:\n        stop = tenacity.stop_after_delay(1) | tenacity.stop_after_attempt(4)\n\n        def s(*args: typing.Any) -> bool:\n            return stop(make_retry_state(*args))\n\n        self.assertFalse(s(1, 0.1))\n        self.assertFalse(s(2, 0.2))\n        self.assertFalse(s(2, 0.8))\n        self.assertTrue(s(4, 0.8))\n        self.assertTrue(s(3, 1.8))\n        self.assertTrue(s(4, 1.8))\n\n    def test_stop_and(self) -> None:\n        stop = tenacity.stop_after_delay(1) & tenacity.stop_after_attempt(4)\n\n        def s(*args: typing.Any) -> bool:\n            return stop(make_retry_state(*args))\n\n        self.assertFalse(s(1, 0.1))\n        self.assertFalse(s(2, 0.2))\n        self.assertFalse(s(2, 0.8))\n        self.assertFalse(s(4, 0.8))\n        self.assertFalse(s(3, 1.8))\n        self.assertTrue(s(4, 1.8))\n\n    def test_stop_after_attempt(self) -> None:\n        r = Retrying(stop=tenacity.stop_after_attempt(3))\n        self.assertFalse(r.stop(make_retry_state(2, 6546)))\n        self.assertTrue(r.stop(make_retry_state(3, 6546)))\n        self.assertTrue(r.stop(make_retry_state(4, 6546)))\n\n    def test_stop_after_delay(self) -> None:\n        for delay in (1, datetime.timedelta(seconds=1)):\n            with self.subTest():\n                r = Retrying(stop=tenacity.stop_after_delay(delay))\n                self.assertFalse(r.stop(make_retry_state(2, 0.999)))\n                self.assertTrue(r.stop(make_retry_state(2, 1)))\n                self.assertTrue(r.stop(make_retry_state(2, 1.001)))\n\n    def test_stop_before_delay(self) -> None:\n        for delay in (1, datetime.timedelta(seconds=1)):\n            with self.subTest():\n                r = Retrying(stop=tenacity.stop_before_delay(delay))\n                self.assertFalse(\n                    r.stop(make_retry_state(2, 0.999, upcoming_sleep=0.0001))\n                )\n                self.assertTrue(r.stop(make_retry_state(2, 1, upcoming_sleep=0.001)))\n                self.assertTrue(r.stop(make_retry_state(2, 1, upcoming_sleep=1)))\n\n                # It should act the same as stop_after_delay if upcoming sleep is 0\n                self.assertFalse(r.stop(make_retry_state(2, 0.999, upcoming_sleep=0)))\n                self.assertTrue(r.stop(make_retry_state(2, 1, upcoming_sleep=0)))\n                self.assertTrue(r.stop(make_retry_state(2, 1.001, upcoming_sleep=0)))\n\n    def test_legacy_explicit_stop_type(self) -> None:\n        Retrying(stop=\"stop_after_attempt\")  # type: ignore[arg-type]\n\n    def test_stop_func_with_retry_state(self) -> None:\n        def stop_func(retry_state: RetryCallState) -> bool:\n            rs = retry_state\n            return rs.attempt_number == rs.seconds_since_start\n\n        r = Retrying(stop=stop_func)\n        self.assertFalse(r.stop(make_retry_state(1, 3)))\n        self.assertFalse(r.stop(make_retry_state(100, 99)))\n        self.assertTrue(r.stop(make_retry_state(101, 101)))\n\n\nclass TestWaitConditions(unittest.TestCase):\n    def test_no_sleep(self) -> None:\n        r = Retrying()\n        self.assertEqual(0, r.wait(make_retry_state(18, 9879)))\n\n    def test_fixed_sleep(self) -> None:\n        for wait in (1, datetime.timedelta(seconds=1)):\n            with self.subTest():\n                r = Retrying(wait=tenacity.wait_fixed(wait))\n                self.assertEqual(1, r.wait(make_retry_state(12, 6546)))\n\n    def test_incrementing_sleep(self) -> None:\n        for start, increment in (\n            (500, 100),\n            (datetime.timedelta(seconds=500), datetime.timedelta(seconds=100)),\n        ):\n            with self.subTest():\n                r = Retrying(\n                    wait=tenacity.wait_incrementing(start=start, increment=increment)\n                )\n                self.assertEqual(500, r.wait(make_retry_state(1, 6546)))\n                self.assertEqual(600, r.wait(make_retry_state(2, 6546)))\n                self.assertEqual(700, r.wait(make_retry_state(3, 6546)))\n\n    def test_random_sleep(self) -> None:\n        for min_, max_ in (\n            (1, 20),\n            (datetime.timedelta(seconds=1), datetime.timedelta(seconds=20)),\n        ):\n            with self.subTest():\n                r = Retrying(wait=tenacity.wait_random(min=min_, max=max_))\n                times = set()\n                for _ in range(1000):\n                    times.add(r.wait(make_retry_state(1, 6546)))\n\n                # this is kind of non-deterministic...\n                self.assertTrue(len(times) > 1)\n                for t in times:\n                    self.assertTrue(t >= 1)\n                    self.assertTrue(t < 20)\n\n    def test_random_sleep_withoutmin_(self) -> None:\n        r = Retrying(wait=tenacity.wait_random(max=2))\n        times = set()\n        times.add(r.wait(make_retry_state(1, 6546)))\n        times.add(r.wait(make_retry_state(1, 6546)))\n        times.add(r.wait(make_retry_state(1, 6546)))\n        times.add(r.wait(make_retry_state(1, 6546)))\n\n        # this is kind of non-deterministic...\n        self.assertTrue(len(times) > 1)\n        for t in times:\n            self.assertTrue(t >= 0)\n            self.assertTrue(t <= 2)\n\n    def test_exponential(self) -> None:\n        r = Retrying(wait=tenacity.wait_exponential())\n        self.assertEqual(r.wait(make_retry_state(1, 0)), 1)\n        self.assertEqual(r.wait(make_retry_state(2, 0)), 2)\n        self.assertEqual(r.wait(make_retry_state(3, 0)), 4)\n        self.assertEqual(r.wait(make_retry_state(4, 0)), 8)\n        self.assertEqual(r.wait(make_retry_state(5, 0)), 16)\n        self.assertEqual(r.wait(make_retry_state(6, 0)), 32)\n        self.assertEqual(r.wait(make_retry_state(7, 0)), 64)\n        self.assertEqual(r.wait(make_retry_state(8, 0)), 128)\n\n    def test_exponential_with_max_wait(self) -> None:\n        r = Retrying(wait=tenacity.wait_exponential(max=40))\n        self.assertEqual(r.wait(make_retry_state(1, 0)), 1)\n        self.assertEqual(r.wait(make_retry_state(2, 0)), 2)\n        self.assertEqual(r.wait(make_retry_state(3, 0)), 4)\n        self.assertEqual(r.wait(make_retry_state(4, 0)), 8)\n        self.assertEqual(r.wait(make_retry_state(5, 0)), 16)\n        self.assertEqual(r.wait(make_retry_state(6, 0)), 32)\n        self.assertEqual(r.wait(make_retry_state(7, 0)), 40)\n        self.assertEqual(r.wait(make_retry_state(8, 0)), 40)\n        self.assertEqual(r.wait(make_retry_state(50, 0)), 40)\n\n    def test_exponential_with_min_wait(self) -> None:\n        r = Retrying(wait=tenacity.wait_exponential(min=20))\n        self.assertEqual(r.wait(make_retry_state(1, 0)), 20)\n        self.assertEqual(r.wait(make_retry_state(2, 0)), 20)\n        self.assertEqual(r.wait(make_retry_state(3, 0)), 20)\n        self.assertEqual(r.wait(make_retry_state(4, 0)), 20)\n        self.assertEqual(r.wait(make_retry_state(5, 0)), 20)\n        self.assertEqual(r.wait(make_retry_state(6, 0)), 32)\n        self.assertEqual(r.wait(make_retry_state(7, 0)), 64)\n        self.assertEqual(r.wait(make_retry_state(8, 0)), 128)\n        self.assertEqual(r.wait(make_retry_state(20, 0)), 524288)\n\n    def test_exponential_with_max_wait_and_multiplier(self) -> None:\n        r = Retrying(wait=tenacity.wait_exponential(max=50, multiplier=1))\n        self.assertEqual(r.wait(make_retry_state(1, 0)), 1)\n        self.assertEqual(r.wait(make_retry_state(2, 0)), 2)\n        self.assertEqual(r.wait(make_retry_state(3, 0)), 4)\n        self.assertEqual(r.wait(make_retry_state(4, 0)), 8)\n        self.assertEqual(r.wait(make_retry_state(5, 0)), 16)\n        self.assertEqual(r.wait(make_retry_state(6, 0)), 32)\n        self.assertEqual(r.wait(make_retry_state(7, 0)), 50)\n        self.assertEqual(r.wait(make_retry_state(8, 0)), 50)\n        self.assertEqual(r.wait(make_retry_state(50, 0)), 50)\n\n    def test_exponential_with_min_wait_and_multiplier(self) -> None:\n        r = Retrying(wait=tenacity.wait_exponential(min=20, multiplier=2))\n        self.assertEqual(r.wait(make_retry_state(1, 0)), 20)\n        self.assertEqual(r.wait(make_retry_state(2, 0)), 20)\n        self.assertEqual(r.wait(make_retry_state(3, 0)), 20)\n        self.assertEqual(r.wait(make_retry_state(4, 0)), 20)\n        self.assertEqual(r.wait(make_retry_state(5, 0)), 32)\n        self.assertEqual(r.wait(make_retry_state(6, 0)), 64)\n        self.assertEqual(r.wait(make_retry_state(7, 0)), 128)\n        self.assertEqual(r.wait(make_retry_state(8, 0)), 256)\n        self.assertEqual(r.wait(make_retry_state(20, 0)), 1048576)\n\n    def test_exponential_with_min_wait_andmax__wait(self) -> None:\n        for min_, max_ in (\n            (10, 100),\n            (datetime.timedelta(seconds=10), datetime.timedelta(seconds=100)),\n        ):\n            with self.subTest():\n                r = Retrying(wait=tenacity.wait_exponential(min=min_, max=max_))\n                self.assertEqual(r.wait(make_retry_state(1, 0)), 10)\n                self.assertEqual(r.wait(make_retry_state(2, 0)), 10)\n                self.assertEqual(r.wait(make_retry_state(3, 0)), 10)\n                self.assertEqual(r.wait(make_retry_state(4, 0)), 10)\n                self.assertEqual(r.wait(make_retry_state(5, 0)), 16)\n                self.assertEqual(r.wait(make_retry_state(6, 0)), 32)\n                self.assertEqual(r.wait(make_retry_state(7, 0)), 64)\n                self.assertEqual(r.wait(make_retry_state(8, 0)), 100)\n                self.assertEqual(r.wait(make_retry_state(9, 0)), 100)\n                self.assertEqual(r.wait(make_retry_state(20, 0)), 100)\n\n    def test_legacy_explicit_wait_type(self) -> None:\n        Retrying(wait=\"exponential_sleep\")  # type: ignore[arg-type]\n\n    def test_wait_func(self) -> None:\n        def wait_func(retry_state: RetryCallState) -> typing.Any:\n            return retry_state.attempt_number * retry_state.seconds_since_start  # type: ignore[operator]\n\n        r = Retrying(wait=wait_func)\n        self.assertEqual(r.wait(make_retry_state(1, 5)), 5)\n        self.assertEqual(r.wait(make_retry_state(2, 11)), 22)\n        self.assertEqual(r.wait(make_retry_state(10, 100)), 1000)\n\n    def test_wait_combine(self) -> None:\n        r = Retrying(\n            wait=tenacity.wait_combine(\n                tenacity.wait_random(0, 3), tenacity.wait_fixed(5)\n            )\n        )\n        # Test it a few time since it's random\n        for _i in range(1000):\n            w = r.wait(make_retry_state(1, 5))\n            self.assertLess(w, 8)\n            self.assertGreaterEqual(w, 5)\n\n    def test_wait_exception(self) -> None:\n        def predicate(exc: BaseException) -> float:\n            if isinstance(exc, ValueError):\n                return 3.5\n            return 10.0\n\n        r = Retrying(wait=tenacity.wait_exception(predicate))\n\n        fut1 = tenacity.Future.construct(1, ValueError(), True)\n        self.assertEqual(r.wait(make_retry_state(1, 0, last_result=fut1)), 3.5)\n\n        fut2 = tenacity.Future.construct(1, KeyError(), True)\n        self.assertEqual(r.wait(make_retry_state(1, 0, last_result=fut2)), 10.0)\n\n        fut3 = tenacity.Future.construct(1, None, False)\n        with self.assertRaises(RuntimeError):\n            r.wait(make_retry_state(1, 0, last_result=fut3))\n\n    def test_wait_double_sum(self) -> None:\n        r = Retrying(wait=tenacity.wait_random(0, 3) + tenacity.wait_fixed(5))\n        # Test it a few time since it's random\n        for _i in range(1000):\n            w = r.wait(make_retry_state(1, 5))\n            self.assertLess(w, 8)\n            self.assertGreaterEqual(w, 5)\n\n    def test_wait_triple_sum(self) -> None:\n        r = Retrying(\n            wait=tenacity.wait_fixed(1)\n            + tenacity.wait_random(0, 3)\n            + tenacity.wait_fixed(5)\n        )\n        # Test it a few time since it's random\n        for _i in range(1000):\n            w = r.wait(make_retry_state(1, 5))\n            self.assertLess(w, 9)\n            self.assertGreaterEqual(w, 6)\n\n    def test_wait_arbitrary_sum(self) -> None:\n        r = Retrying(\n            wait=sum(  # type: ignore[arg-type]\n                [\n                    tenacity.wait_fixed(1),  # type: ignore[list-item]\n                    tenacity.wait_random(0, 3),  # type: ignore[list-item]\n                    tenacity.wait_fixed(5),  # type: ignore[list-item]\n                    tenacity.wait_none(),  # type: ignore[list-item]\n                ]\n            )\n        )\n        # Test it a few time since it's random\n        for _ in range(1000):\n            w = r.wait(make_retry_state(1, 5))\n            self.assertLess(w, 9)\n            self.assertGreaterEqual(w, 6)\n\n    def _assert_range(self, wait: float, min_: float, max_: float) -> None:\n        self.assertLess(wait, max_)\n        self.assertGreaterEqual(wait, min_)\n\n    def _assert_inclusive_range(self, wait: float, low: float, high: float) -> None:\n        self.assertLessEqual(wait, high)\n        self.assertGreaterEqual(wait, low)\n\n    def _assert_inclusive_epsilon(\n        self, wait: float, target: float, epsilon: float\n    ) -> None:\n        self.assertLessEqual(wait, target + epsilon)\n        self.assertGreaterEqual(wait, target - epsilon)\n\n    def test_wait_chain(self) -> None:\n        r = Retrying(\n            wait=tenacity.wait_chain(\n                *[tenacity.wait_fixed(1) for i in range(2)]\n                + [tenacity.wait_fixed(4) for i in range(2)]\n                + [tenacity.wait_fixed(8) for i in range(1)]\n            )\n        )\n\n        for i in range(10):\n            w = r.wait(make_retry_state(i + 1, 1))\n            if i < 2:\n                self._assert_range(w, 1, 2)\n            elif i < 4:\n                self._assert_range(w, 4, 5)\n            else:\n                self._assert_range(w, 8, 9)\n\n    def test_wait_chain_multiple_invocations(self) -> None:\n        sleep_intervals: list[float] = []\n        r = Retrying(\n            sleep=sleep_intervals.append,\n            wait=tenacity.wait_chain(*[tenacity.wait_fixed(i + 1) for i in range(3)]),\n            stop=tenacity.stop_after_attempt(5),\n            retry=tenacity.retry_if_result(lambda x: x == 1),\n        )\n\n        @r.wraps\n        def always_return_1() -> int:\n            return 1\n\n        self.assertRaises(tenacity.RetryError, always_return_1)\n        self.assertEqual(sleep_intervals, [1.0, 2.0, 3.0, 3.0])\n        sleep_intervals[:] = []\n\n        # Clear and restart retrying.\n        self.assertRaises(tenacity.RetryError, always_return_1)\n        self.assertEqual(sleep_intervals, [1.0, 2.0, 3.0, 3.0])\n        sleep_intervals[:] = []\n\n    def test_wait_random_exponential(self) -> None:\n        fn = tenacity.wait_random_exponential(0.5, 60.0)\n\n        for _ in range(1000):\n            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 0, 0.5)\n            self._assert_inclusive_range(fn(make_retry_state(2, 0)), 0, 1.0)\n            self._assert_inclusive_range(fn(make_retry_state(3, 0)), 0, 2.0)\n            self._assert_inclusive_range(fn(make_retry_state(4, 0)), 0, 4.0)\n            self._assert_inclusive_range(fn(make_retry_state(5, 0)), 0, 8.0)\n            self._assert_inclusive_range(fn(make_retry_state(6, 0)), 0, 16.0)\n            self._assert_inclusive_range(fn(make_retry_state(7, 0)), 0, 32.0)\n            self._assert_inclusive_range(fn(make_retry_state(8, 0)), 0, 60.0)\n            self._assert_inclusive_range(fn(make_retry_state(9, 0)), 0, 60.0)\n\n        # max wait\n        max_wait = 5\n        fn = tenacity.wait_random_exponential(10, max_wait)\n        for _ in range(1000):\n            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 0.00, max_wait)\n\n        # min wait\n        min_wait = 5\n        fn = tenacity.wait_random_exponential(min=min_wait)\n        for _ in range(1000):\n            self._assert_inclusive_range(fn(make_retry_state(1, 0)), min_wait, 5)\n\n        # Default arguments exist\n        fn = tenacity.wait_random_exponential()\n        fn(make_retry_state(0, 0))\n\n    def test_wait_random_exponential_statistically(self) -> None:\n        fn = tenacity.wait_random_exponential(0.5, 60.0)\n\n        attempt = [[fn(make_retry_state(i, 0)) for _ in range(4000)] for i in range(10)]\n\n        def mean(lst: list[float]) -> float:\n            return float(sum(lst)) / float(len(lst))\n\n        # skipping attempt 0\n        self._assert_inclusive_epsilon(mean(attempt[1]), 0.25, 0.02)\n        self._assert_inclusive_epsilon(mean(attempt[2]), 0.50, 0.04)\n        self._assert_inclusive_epsilon(mean(attempt[3]), 1, 0.08)\n        self._assert_inclusive_epsilon(mean(attempt[4]), 2, 0.16)\n        self._assert_inclusive_epsilon(mean(attempt[5]), 4, 0.32)\n        self._assert_inclusive_epsilon(mean(attempt[6]), 8, 0.64)\n        self._assert_inclusive_epsilon(mean(attempt[7]), 16, 1.28)\n        self._assert_inclusive_epsilon(mean(attempt[8]), 30, 2.56)\n        self._assert_inclusive_epsilon(mean(attempt[9]), 30, 2.56)\n\n    def test_wait_exponential_jitter(self) -> None:\n        fn = tenacity.wait_exponential_jitter(max=60)\n\n        for _ in range(1000):\n            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 1, 2)\n            self._assert_inclusive_range(fn(make_retry_state(2, 0)), 2, 3)\n            self._assert_inclusive_range(fn(make_retry_state(3, 0)), 4, 5)\n            self._assert_inclusive_range(fn(make_retry_state(4, 0)), 8, 9)\n            self._assert_inclusive_range(fn(make_retry_state(5, 0)), 16, 17)\n            self._assert_inclusive_range(fn(make_retry_state(6, 0)), 32, 33)\n            self.assertEqual(fn(make_retry_state(7, 0)), 60)\n            self.assertEqual(fn(make_retry_state(8, 0)), 60)\n            self.assertEqual(fn(make_retry_state(9, 0)), 60)\n\n        with self.assertWarns(DeprecationWarning):\n            fn = tenacity.wait_exponential_jitter(10, 5)\n        for _ in range(1000):\n            self.assertEqual(fn(make_retry_state(1, 0)), 5)\n\n        # Default arguments exist\n        fn = tenacity.wait_exponential_jitter()\n        fn(make_retry_state(0, 0))\n\n    def test_wait_exponential_jitter_min(self) -> None:\n        fn = tenacity.wait_exponential_jitter(initial=1, max=60, jitter=1, min=5)\n        for _ in range(1000):\n            # Even for attempt 1 (base wait=1 + jitter 0..1 = 1..2), min=5 applies\n            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 5, 5)\n            self._assert_inclusive_range(fn(make_retry_state(2, 0)), 5, 5)\n            self._assert_inclusive_range(fn(make_retry_state(3, 0)), 5, 5)\n            # For attempt 4, base wait=8 + jitter 0..1 = 8..9, above min\n            self._assert_inclusive_range(fn(make_retry_state(4, 0)), 8, 9)\n\n    def test_wait_exponential_jitter_timedelta(self) -> None:\n        from datetime import timedelta\n\n        fn = tenacity.wait_exponential_jitter(\n            max=timedelta(seconds=60),\n            jitter=timedelta(seconds=1),\n            min=timedelta(seconds=5),\n        )\n        for _ in range(1000):\n            self._assert_inclusive_range(fn(make_retry_state(1, 0)), 5, 5)\n            self._assert_inclusive_range(fn(make_retry_state(5, 0)), 16, 17)\n            self.assertEqual(fn(make_retry_state(7, 0)), 60)\n\n    def test_wait_exponential_jitter_multiplier(self) -> None:\n        fn = tenacity.wait_exponential_jitter(multiplier=10, max=60, jitter=0)\n        self.assertEqual(fn(make_retry_state(1, 0)), 10)\n        self.assertEqual(fn(make_retry_state(2, 0)), 20)\n        self.assertEqual(fn(make_retry_state(3, 0)), 40)\n        self.assertEqual(fn(make_retry_state(4, 0)), 60)\n\n    def test_wait_exponential_jitter_initial_deprecated(self) -> None:\n        with self.assertWarns(DeprecationWarning):\n            fn = tenacity.wait_exponential_jitter(initial=10, max=60, jitter=0)\n        self.assertEqual(fn(make_retry_state(1, 0)), 10)\n        self.assertEqual(fn(make_retry_state(2, 0)), 20)\n\n    def test_wait_exponential_jitter_initial_and_multiplier_raises(self) -> None:\n        with self.assertRaises(ValueError):\n            tenacity.wait_exponential_jitter(initial=5, multiplier=10)\n\n    def test_wait_retry_state_attributes(self) -> None:\n        class ExtractCallState(Exception):\n            pass\n\n        # retry_state is mutable, so return it as an exception to extract the\n        # exact values it has when wait is called and bypass any other logic.\n        def waitfunc(retry_state: RetryCallState) -> float:\n            raise ExtractCallState(retry_state)\n\n        retrying = Retrying(\n            wait=waitfunc,\n            retry=(\n                tenacity.retry_if_exception_type()\n                | tenacity.retry_if_result(lambda result: result == 123)\n            ),\n        )\n\n        def returnval() -> int:\n            return 123\n\n        try:\n            retrying(returnval)\n        except ExtractCallState as err:\n            retry_state = err.args[0]\n        self.assertIs(retry_state.fn, returnval)\n        self.assertEqual(retry_state.args, ())\n        self.assertEqual(retry_state.kwargs, {})\n        self.assertEqual(retry_state.outcome.result(), 123)\n        self.assertEqual(retry_state.attempt_number, 1)\n        self.assertGreaterEqual(retry_state.outcome_timestamp, retry_state.start_time)\n\n        def dying() -> None:\n            raise Exception(\"Broken\")\n\n        try:\n            retrying(dying)\n        except ExtractCallState as err:\n            retry_state = err.args[0]\n        self.assertIs(retry_state.fn, dying)\n        self.assertEqual(retry_state.args, ())\n        self.assertEqual(retry_state.kwargs, {})\n        self.assertEqual(str(retry_state.outcome.exception()), \"Broken\")\n        self.assertEqual(retry_state.attempt_number, 1)\n        self.assertGreaterEqual(retry_state.outcome_timestamp, retry_state.start_time)\n\n\nclass TestRetryConditions(unittest.TestCase):\n    def test_retry_if_result(self) -> None:\n        retry = tenacity.retry_if_result(lambda x: x == 1)\n\n        def r(fut: tenacity.Future) -> bool:\n            retry_state = make_retry_state(1, 1.0, last_result=fut)\n            return retry(retry_state)\n\n        self.assertTrue(r(tenacity.Future.construct(1, 1, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 2, False)))\n\n    def test_retry_if_not_result(self) -> None:\n        retry = tenacity.retry_if_not_result(lambda x: x == 1)\n\n        def r(fut: tenacity.Future) -> bool:\n            retry_state = make_retry_state(1, 1.0, last_result=fut)\n            return retry(retry_state)\n\n        self.assertTrue(r(tenacity.Future.construct(1, 2, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 1, False)))\n\n    def test_retry_any(self) -> None:\n        retry = tenacity.retry_any(\n            tenacity.retry_if_result(lambda x: x == 1),\n            tenacity.retry_if_result(lambda x: x == 2),\n        )\n\n        def r(fut: tenacity.Future) -> bool:\n            retry_state = make_retry_state(1, 1.0, last_result=fut)\n            return retry(retry_state)\n\n        self.assertTrue(r(tenacity.Future.construct(1, 1, False)))\n        self.assertTrue(r(tenacity.Future.construct(1, 2, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 3, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 1, True)))\n\n    def test_retry_all(self) -> None:\n        retry = tenacity.retry_all(\n            tenacity.retry_if_result(lambda x: x == 1),\n            tenacity.retry_if_result(lambda x: isinstance(x, int)),\n        )\n\n        def r(fut: tenacity.Future) -> bool:\n            retry_state = make_retry_state(1, 1.0, last_result=fut)\n            return retry(retry_state)\n\n        self.assertTrue(r(tenacity.Future.construct(1, 1, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 2, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 3, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 1, True)))\n\n    def test_retry_and(self) -> None:\n        retry = tenacity.retry_if_result(lambda x: x == 1) & tenacity.retry_if_result(\n            lambda x: isinstance(x, int)\n        )\n\n        def r(fut: tenacity.Future) -> bool:\n            retry_state = make_retry_state(1, 1.0, last_result=fut)\n            return retry(retry_state)\n\n        self.assertTrue(r(tenacity.Future.construct(1, 1, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 2, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 3, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 1, True)))\n\n    def test_retry_or(self) -> None:\n        retry = tenacity.retry_if_result(\n            lambda x: x == \"foo\"\n        ) | tenacity.retry_if_result(lambda x: isinstance(x, int))\n\n        def r(fut: tenacity.Future) -> bool:\n            retry_state = make_retry_state(1, 1.0, last_result=fut)\n            return retry(retry_state)\n\n        self.assertTrue(r(tenacity.Future.construct(1, \"foo\", False)))\n        self.assertFalse(r(tenacity.Future.construct(1, \"foobar\", False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 2.2, False)))\n        self.assertFalse(r(tenacity.Future.construct(1, 42, True)))\n\n    def test_retry_or_with_plain_function(self) -> None:\n        \"\"\"Plain callables can be composed with retry_base via |.\"\"\"\n\n        def my_retry(retry_state: tenacity.RetryCallState) -> bool:\n            return retry_state.outcome is not None and not retry_state.outcome.failed\n\n        # retry_base | plain_callable (exercises __or__ fallback)\n        retry = tenacity.retry_if_exception_type(Exception) | my_retry\n        retry_state = make_retry_state(\n            1, 1.0, last_result=tenacity.Future.construct(1, \"ok\", False)\n        )\n        self.assertTrue(retry(retry_state))\n\n        # plain_callable | retry_base (exercises __ror__ via reflection)\n        retry2 = my_retry | tenacity.retry_if_exception_type(Exception)\n        self.assertTrue(retry2(retry_state))\n\n    def test_retry_and_with_plain_function(self) -> None:\n        \"\"\"Plain callables can be composed with retry_base via &.\"\"\"\n\n        def my_retry(retry_state: tenacity.RetryCallState) -> bool:\n            return True\n\n        # retry_base & plain_callable (exercises __and__ fallback)\n        retry = tenacity.retry_if_result(lambda x: x == 1) & my_retry\n        retry_state = make_retry_state(\n            1, 1.0, last_result=tenacity.Future.construct(1, 1, False)\n        )\n        self.assertTrue(retry(retry_state))\n\n        # plain_callable & retry_base (exercises __rand__ via reflection)\n        retry2 = my_retry & tenacity.retry_if_result(lambda x: x == 1)\n        self.assertTrue(retry2(retry_state))\n\n    def test_retry_or_coalesces(self) -> None:\n        \"\"\"Multiple | operations flatten into a single retry_any.\"\"\"\n        a = tenacity.retry_if_exception_type(IOError)\n        b = tenacity.retry_if_exception_type(OSError)\n        c = tenacity.retry_if_exception_type(ValueError)\n\n        combined = a | b | c\n        self.assertIsInstance(combined, retry_any)\n        self.assertEqual(len(combined.retries), 3)\n\n    def test_retry_and_coalesces(self) -> None:\n        \"\"\"Multiple & operations flatten into a single retry_all.\"\"\"\n        a = tenacity.retry_if_result(lambda x: x == 1)\n        b = tenacity.retry_if_result(lambda x: x > 0)\n        c = tenacity.retry_if_result(lambda x: x < 10)\n\n        combined = a & b & c\n        self.assertIsInstance(combined, retry_all)\n        self.assertEqual(len(combined.retries), 3)\n\n    def _raise_try_again(self) -> None:\n        self._attempts += 1\n        if self._attempts < 3:\n            raise tenacity.TryAgain\n\n    def test_retry_try_again(self) -> None:\n        self._attempts = 0\n        Retrying(stop=tenacity.stop_after_attempt(5), retry=tenacity.retry_never)(\n            self._raise_try_again\n        )\n        self.assertEqual(3, self._attempts)\n\n    def test_retry_try_again_forever(self) -> None:\n        def _r() -> None:\n            raise tenacity.TryAgain\n\n        r = Retrying(stop=tenacity.stop_after_attempt(5), retry=tenacity.retry_never)\n        self.assertRaises(tenacity.RetryError, r, _r)\n        self.assertEqual(5, r.statistics[\"attempt_number\"])\n\n    def test_retry_try_again_forever_reraise(self) -> None:\n        def _r() -> None:\n            raise tenacity.TryAgain\n\n        r = Retrying(\n            stop=tenacity.stop_after_attempt(5),\n            retry=tenacity.retry_never,\n            reraise=True,\n        )\n        self.assertRaises(tenacity.TryAgain, r, _r)\n        self.assertEqual(5, r.statistics[\"attempt_number\"])\n\n    def test_retry_if_exception_message_negative_no_inputs(self) -> None:\n        with self.assertRaises(TypeError):\n            tenacity.retry_if_exception_message()\n\n    def test_retry_if_exception_message_negative_too_many_inputs(self) -> None:\n        with self.assertRaises(TypeError):\n            tenacity.retry_if_exception_message(message=\"negative\", match=\"negative\")\n\n\nclass NoneReturnUntilAfterCount:\n    \"\"\"Holds counter state for invoking a method several times in a row.\"\"\"\n\n    def __init__(self, count: int) -> None:\n        self.counter = 0\n        self.count = count\n\n    def go(self) -> typing.Any:\n        \"\"\"Return None until after count threshold has been crossed.\n\n        Then return True.\n        \"\"\"\n        if self.counter < self.count:\n            self.counter += 1\n            return None\n        return True\n\n\nclass NoIOErrorAfterCount:\n    \"\"\"Holds counter state for invoking a method several times in a row.\"\"\"\n\n    def __init__(self, count: int) -> None:\n        self.counter = 0\n        self.count = count\n\n    def go(self) -> typing.Any:\n        \"\"\"Raise an IOError until after count threshold has been crossed.\n\n        Then return True.\n        \"\"\"\n        if self.counter < self.count:\n            self.counter += 1\n            raise OSError(\"Hi there, I'm an IOError\")\n        return True\n\n\nclass NoNameErrorAfterCount:\n    \"\"\"Holds counter state for invoking a method several times in a row.\"\"\"\n\n    def __init__(self, count: int) -> None:\n        self.counter = 0\n        self.count = count\n\n    def go(self) -> typing.Any:\n        \"\"\"Raise a NameError until after count threshold has been crossed.\n\n        Then return True.\n        \"\"\"\n        if self.counter < self.count:\n            self.counter += 1\n            raise NameError(\"Hi there, I'm a NameError\")\n        return True\n\n\nclass NoNameErrorCauseAfterCount:\n    \"\"\"Holds counter state for invoking a method several times in a row.\"\"\"\n\n    def __init__(self, count: int) -> None:\n        self.counter = 0\n        self.count = count\n\n    def go2(self) -> typing.Any:\n        raise NameError(\"Hi there, I'm a NameError\")\n\n    def go(self) -> typing.Any:\n        \"\"\"Raise an IOError with a NameError as cause until after count threshold has been crossed.\n\n        Then return True.\n        \"\"\"\n        if self.counter < self.count:\n            self.counter += 1\n            try:\n                self.go2()\n            except NameError as e:\n                raise OSError from e\n\n        return True\n\n\nclass NoIOErrorCauseAfterCount:\n    \"\"\"Holds counter state for invoking a method several times in a row.\"\"\"\n\n    def __init__(self, count: int) -> None:\n        self.counter = 0\n        self.count = count\n\n    def go2(self) -> typing.Any:\n        raise OSError(\"Hi there, I'm an IOError\")\n\n    def go(self) -> typing.Any:\n        \"\"\"Raise a NameError with an IOError as cause until after count threshold has been crossed.\n\n        Then return True.\n        \"\"\"\n        if self.counter < self.count:\n            self.counter += 1\n            try:\n                self.go2()\n            except OSError as e:\n                raise NameError from e\n\n        return True\n\n\nclass NameErrorUntilCount:\n    \"\"\"Holds counter state for invoking a method several times in a row.\"\"\"\n\n    derived_message = \"Hi there, I'm a NameError\"\n\n    def __init__(self, count: int) -> None:\n        self.counter = 0\n        self.count = count\n\n    def go(self) -> typing.Any:\n        \"\"\"Return True until after count threshold has been crossed.\n\n        Then raise a NameError.\n        \"\"\"\n        if self.counter < self.count:\n            self.counter += 1\n            return True\n        raise NameError(self.derived_message)\n\n\nclass IOErrorUntilCount:\n    \"\"\"Holds counter state for invoking a method several times in a row.\"\"\"\n\n    def __init__(self, count: int) -> None:\n        self.counter = 0\n        self.count = count\n\n    def go(self) -> typing.Any:\n        \"\"\"Return True until after count threshold has been crossed.\n\n        Then raise an IOError.\n        \"\"\"\n        if self.counter < self.count:\n            self.counter += 1\n            return True\n        raise OSError(\"Hi there, I'm an IOError\")\n\n\nclass CustomError(Exception):\n    \"\"\"This is a custom exception class.\n\n    Note that For Python 2.x, we don't strictly need to extend BaseException,\n    however, Python 3.x will complain. While this test suite won't run\n    correctly under Python 3.x without extending from the Python exception\n    hierarchy, the actual module code is backwards compatible Python 2.x and\n    will allow for cases where exception classes don't extend from the\n    hierarchy.\n    \"\"\"\n\n    def __init__(self, value: str) -> None:\n        self.value = value\n\n    def __str__(self) -> str:\n        return self.value\n\n\nclass NoCustomErrorAfterCount:\n    \"\"\"Holds counter state for invoking a method several times in a row.\"\"\"\n\n    derived_message = \"This is a Custom exception class\"\n\n    def __init__(self, count: int) -> None:\n        self.counter = 0\n        self.count = count\n\n    def go(self) -> typing.Any:\n        \"\"\"Raise a CustomError until after count threshold has been crossed.\n\n        Then return True.\n        \"\"\"\n        if self.counter < self.count:\n            self.counter += 1\n            raise CustomError(self.derived_message)\n        return True\n\n\nclass CapturingHandler(logging.Handler):\n    \"\"\"Captures log records for inspection.\"\"\"\n\n    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:\n        super().__init__(*args, **kwargs)\n        self.records: list[logging.LogRecord] = []\n\n    def emit(self, record: logging.LogRecord) -> None:\n        self.records.append(record)\n\n\ndef current_time_ms() -> int:\n    return round(time.time() * 1000)\n\n\n@retry(\n    wait=tenacity.wait_fixed(0.05),\n    retry=tenacity.retry_if_result(lambda result: result is None),\n)\ndef _retryable_test_with_wait(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(\n    stop=tenacity.stop_after_attempt(3),\n    retry=tenacity.retry_if_result(lambda result: result is None),\n)\ndef _retryable_test_with_stop(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(retry=tenacity.retry_if_exception_cause_type(NameError))\ndef _retryable_test_with_exception_cause_type(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(retry=tenacity.retry_if_exception_type(IOError))\ndef _retryable_test_with_exception_type_io(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(retry=tenacity.retry_if_not_exception_type(IOError))\ndef _retryable_test_if_not_exception_type_io(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(\n    stop=tenacity.stop_after_attempt(3), retry=tenacity.retry_if_exception_type(IOError)\n)\ndef _retryable_test_with_exception_type_io_attempt_limit(\n    thing: typing.Any,\n) -> typing.Any:\n    return thing.go()\n\n\n@retry(retry=tenacity.retry_unless_exception_type(NameError))\ndef _retryable_test_with_unless_exception_type_name(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(\n    stop=tenacity.stop_after_attempt(3),\n    retry=tenacity.retry_unless_exception_type(NameError),\n)\ndef _retryable_test_with_unless_exception_type_name_attempt_limit(\n    thing: typing.Any,\n) -> typing.Any:\n    return thing.go()\n\n\n@retry(retry=tenacity.retry_unless_exception_type())\ndef _retryable_test_with_unless_exception_type_no_input(\n    thing: typing.Any,\n) -> typing.Any:\n    return thing.go()\n\n\n@retry(\n    stop=tenacity.stop_after_attempt(5),\n    retry=tenacity.retry_if_exception_message(\n        message=NoCustomErrorAfterCount.derived_message\n    ),\n)\ndef _retryable_test_if_exception_message_message(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(\n    retry=tenacity.retry_if_not_exception_message(\n        message=NoCustomErrorAfterCount.derived_message\n    )\n)\ndef _retryable_test_if_not_exception_message_message(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(\n    retry=tenacity.retry_if_exception_message(\n        match=NoCustomErrorAfterCount.derived_message[:3] + \".*\"\n    )\n)\ndef _retryable_test_if_exception_message_match(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(\n    retry=tenacity.retry_if_not_exception_message(\n        match=NoCustomErrorAfterCount.derived_message[:3] + \".*\"\n    )\n)\ndef _retryable_test_if_not_exception_message_match(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(\n    retry=tenacity.retry_if_not_exception_message(\n        message=NameErrorUntilCount.derived_message\n    )\n)\ndef _retryable_test_not_exception_message_delay(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry\ndef _retryable_default(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry()\ndef _retryable_default_f(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(retry=tenacity.retry_if_exception_type(CustomError))\ndef _retryable_test_with_exception_type_custom(thing: typing.Any) -> typing.Any:\n    return thing.go()\n\n\n@retry(\n    stop=tenacity.stop_after_attempt(3),\n    retry=tenacity.retry_if_exception_type(CustomError),\n)\ndef _retryable_test_with_exception_type_custom_attempt_limit(\n    thing: typing.Any,\n) -> typing.Any:\n    return thing.go()\n\n\nclass TestDecoratorWrapper(unittest.TestCase):\n    def test_with_wait(self) -> None:\n        start = current_time_ms()\n        result = _retryable_test_with_wait(NoneReturnUntilAfterCount(5))\n        t = current_time_ms() - start\n        self.assertGreaterEqual(t, 250)\n        self.assertTrue(result)\n\n    def test_with_stop_on_return_value(self) -> None:\n        try:\n            _retryable_test_with_stop(NoneReturnUntilAfterCount(5))\n            self.fail(\"Expected RetryError after 3 attempts\")\n        except RetryError as re:\n            self.assertFalse(re.last_attempt.failed)\n            self.assertEqual(3, re.last_attempt.attempt_number)\n            self.assertTrue(re.last_attempt.result() is None)\n            print(re)\n\n    def test_with_stop_on_exception(self) -> None:\n        try:\n            _retryable_test_with_stop(NoIOErrorAfterCount(5))\n            self.fail(\"Expected IOError\")\n        except OSError as re:\n            self.assertTrue(isinstance(re, IOError))\n            print(re)\n\n    def test_retry_if_exception_of_type(self) -> None:\n        self.assertTrue(_retryable_test_with_exception_type_io(NoIOErrorAfterCount(5)))\n\n        try:\n            _retryable_test_with_exception_type_io(NoNameErrorAfterCount(5))\n            self.fail(\"Expected NameError\")\n        except NameError as n:\n            self.assertTrue(isinstance(n, NameError))\n            print(n)\n\n        self.assertTrue(\n            _retryable_test_with_exception_type_custom(NoCustomErrorAfterCount(5))\n        )\n\n        try:\n            _retryable_test_with_exception_type_custom(NoNameErrorAfterCount(5))\n            self.fail(\"Expected NameError\")\n        except NameError as n:\n            self.assertTrue(isinstance(n, NameError))\n            print(n)\n\n    def test_retry_except_exception_of_type(self) -> None:\n        self.assertTrue(\n            _retryable_test_if_not_exception_type_io(NoNameErrorAfterCount(5))\n        )\n\n        try:\n            _retryable_test_if_not_exception_type_io(NoIOErrorAfterCount(5))\n            self.fail(\"Expected IOError\")\n        except OSError as err:\n            self.assertTrue(isinstance(err, IOError))\n            print(err)\n\n    def test_retry_until_exception_of_type_attempt_number(self) -> None:\n        try:\n            self.assertTrue(\n                _retryable_test_with_unless_exception_type_name(NameErrorUntilCount(5))\n            )\n        except NameError as e:\n            s = _retryable_test_with_unless_exception_type_name.statistics\n            self.assertTrue(s[\"attempt_number\"] == 6)\n            print(e)\n        else:\n            self.fail(\"Expected NameError\")\n\n    def test_retry_until_exception_of_type_no_type(self) -> None:\n        try:\n            # no input should catch all subclasses of Exception\n            self.assertTrue(\n                _retryable_test_with_unless_exception_type_no_input(\n                    NameErrorUntilCount(5)\n                )\n            )\n        except NameError as e:\n            s = _retryable_test_with_unless_exception_type_no_input.statistics\n            self.assertTrue(s[\"attempt_number\"] == 6)\n            print(e)\n        else:\n            self.fail(\"Expected NameError\")\n\n    def test_retry_until_exception_of_type_wrong_exception(self) -> None:\n        try:\n            # two iterations with IOError, one that returns True\n            _retryable_test_with_unless_exception_type_name_attempt_limit(\n                IOErrorUntilCount(2)\n            )\n            self.fail(\"Expected RetryError\")\n        except RetryError as e:\n            self.assertTrue(isinstance(e, RetryError))\n            print(e)\n\n    def test_retry_if_exception_message(self) -> None:\n        try:\n            self.assertTrue(\n                _retryable_test_if_exception_message_message(NoCustomErrorAfterCount(3))\n            )\n        except CustomError:\n            print(_retryable_test_if_exception_message_message.statistics)\n            self.fail(\"CustomError should've been retried from errormessage\")\n\n    def test_retry_if_not_exception_message(self) -> None:\n        try:\n            self.assertTrue(\n                _retryable_test_if_not_exception_message_message(\n                    NoCustomErrorAfterCount(2)\n                )\n            )\n        except CustomError:\n            s = _retryable_test_if_not_exception_message_message.statistics\n            self.assertTrue(s[\"attempt_number\"] == 1)\n\n    def test_retry_if_not_exception_message_delay(self) -> None:\n        try:\n            self.assertTrue(\n                _retryable_test_not_exception_message_delay(NameErrorUntilCount(3))\n            )\n        except NameError:\n            s = _retryable_test_not_exception_message_delay.statistics\n            print(s[\"attempt_number\"])\n            self.assertTrue(s[\"attempt_number\"] == 4)\n\n    def test_retry_if_exception_message_match(self) -> None:\n        try:\n            self.assertTrue(\n                _retryable_test_if_exception_message_match(NoCustomErrorAfterCount(3))\n            )\n        except CustomError:\n            self.fail(\"CustomError should've been retried from errormessage\")\n\n    def test_retry_if_not_exception_message_match(self) -> None:\n        try:\n            self.assertTrue(\n                _retryable_test_if_not_exception_message_message(\n                    NoCustomErrorAfterCount(2)\n                )\n            )\n        except CustomError:\n            s = _retryable_test_if_not_exception_message_message.statistics\n            self.assertTrue(s[\"attempt_number\"] == 1)\n\n    def test_retry_if_exception_cause_type(self) -> None:\n        self.assertTrue(\n            _retryable_test_with_exception_cause_type(NoNameErrorCauseAfterCount(5))\n        )\n\n        try:\n            _retryable_test_with_exception_cause_type(NoIOErrorCauseAfterCount(5))\n            self.fail(\"Expected exception without NameError as cause\")\n        except NameError:\n            pass\n\n    def test_retry_preserves_argument_defaults(self) -> None:\n        def function_with_defaults(a: int = 1) -> int:\n            return a\n\n        def function_with_kwdefaults(*, a: int = 1) -> int:\n            return a\n\n        retrying = Retrying(\n            wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3)\n        )\n        wrapped_defaults_function = retrying.wraps(function_with_defaults)\n        wrapped_kwdefaults_function = retrying.wraps(function_with_kwdefaults)\n\n        self.assertEqual(\n            function_with_defaults.__defaults__,\n            wrapped_defaults_function.__defaults__,  # type: ignore[attr-defined]\n        )\n        self.assertEqual(\n            function_with_kwdefaults.__kwdefaults__,\n            wrapped_kwdefaults_function.__kwdefaults__,  # type: ignore[attr-defined]\n        )\n\n    def test_defaults(self) -> None:\n        self.assertTrue(_retryable_default(NoNameErrorAfterCount(5)))\n        self.assertTrue(_retryable_default_f(NoNameErrorAfterCount(5)))\n        self.assertTrue(_retryable_default(NoCustomErrorAfterCount(5)))\n        self.assertTrue(_retryable_default_f(NoCustomErrorAfterCount(5)))\n\n    def test_retry_function_object(self) -> None:\n        \"\"\"Test that functools.wraps doesn't cause problems with callable objects.\n\n        It raises an error upon trying to wrap it in Py2, because __name__\n        attribute is missing. It's fixed in Py3 but was never backported.\n        \"\"\"\n\n        class Hello:\n            def __call__(self) -> str:\n                return \"Hello\"\n\n        retrying = Retrying(\n            wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3)\n        )\n        h = retrying.wraps(Hello())\n        self.assertEqual(h(), \"Hello\")\n\n    def test_retry_function_attributes(self) -> None:\n        \"\"\"Test that the wrapped function attributes are exposed as intended.\n\n        - statistics contains the value for the latest function run\n        - retry object can be modified to change its behaviour (useful to patch in tests)\n        - retry object statistics are synced with function statistics\n        \"\"\"\n\n        self.assertTrue(_retryable_test_with_stop(NoneReturnUntilAfterCount(2)))\n\n        expected_stats = {\n            \"attempt_number\": 3,\n            \"delay_since_first_attempt\": mock.ANY,\n            \"idle_for\": mock.ANY,\n            \"start_time\": mock.ANY,\n        }\n        self.assertEqual(_retryable_test_with_stop.statistics, expected_stats)\n        self.assertEqual(_retryable_test_with_stop.retry.statistics, expected_stats)\n\n        with mock.patch.object(\n            _retryable_test_with_stop.retry,\n            \"stop\",\n            tenacity.stop_after_attempt(1),\n        ):\n            try:\n                self.assertTrue(_retryable_test_with_stop(NoneReturnUntilAfterCount(2)))\n            except RetryError as exc:\n                expected_stats = {\n                    \"attempt_number\": 1,\n                    \"delay_since_first_attempt\": mock.ANY,\n                    \"idle_for\": mock.ANY,\n                    \"start_time\": mock.ANY,\n                }\n                self.assertEqual(_retryable_test_with_stop.statistics, expected_stats)\n                self.assertEqual(exc.last_attempt.attempt_number, 1)\n                self.assertEqual(\n                    _retryable_test_with_stop.retry.statistics, expected_stats\n                )\n            else:\n                self.fail(\"RetryError should have been raised after 1 attempt\")\n\n\nclass TestStatisticsKeys:\n    def test_delay_since_first_attempt_available_on_first_attempt(self) -> None:\n        \"\"\"delay_since_first_attempt should be in statistics from the start.\"\"\"\n\n        @retry(\n            stop=tenacity.stop_after_attempt(3),\n            retry=tenacity.retry_if_result(lambda x: x is None),\n        )\n        def succeeds_first_try() -> bool:\n            assert \"delay_since_first_attempt\" in succeeds_first_try.statistics\n            assert succeeds_first_try.statistics[\"delay_since_first_attempt\"] == 0\n            return True\n\n        succeeds_first_try()\n        assert succeeds_first_try.statistics[\"delay_since_first_attempt\"] == 0\n\n\nclass TestEnabled:\n    def test_enabled_false_skips_retry(self) -> None:\n        \"\"\"When enabled=False, the function is called directly without retrying.\"\"\"\n        call_count = 0\n\n        @retry(enabled=False, stop=tenacity.stop_after_attempt(3))\n        def always_fails() -> None:\n            nonlocal call_count\n            call_count += 1\n            raise ValueError(\"fail\")\n\n        with pytest.raises(ValueError, match=\"fail\"):\n            always_fails()\n        assert call_count == 1\n\n    def test_enabled_false_preserves_attributes(self) -> None:\n        \"\"\"When enabled=False, .retry, .retry_with, .statistics are still available.\"\"\"\n\n        @retry(enabled=False, stop=tenacity.stop_after_attempt(3))\n        def my_func() -> str:\n            return \"ok\"\n\n        assert hasattr(my_func, \"retry\")\n        assert hasattr(my_func, \"retry_with\")\n        assert hasattr(my_func, \"statistics\")\n        assert my_func() == \"ok\"\n\n    def test_enabled_false_via_retry_with(self) -> None:\n        \"\"\"retry_with(enabled=False) disables retrying.\"\"\"\n        call_count = 0\n\n        @retry(stop=tenacity.stop_after_attempt(3))\n        def always_fails() -> None:\n            nonlocal call_count\n            call_count += 1\n            raise ValueError(\"fail\")\n\n        disabled = always_fails.retry_with(enabled=False)\n        with pytest.raises(ValueError, match=\"fail\"):\n            disabled()\n        assert call_count == 1\n\n    def test_enabled_true_retries_normally(self) -> None:\n        \"\"\"When enabled=True (default), retrying works as usual.\"\"\"\n        call_count = 0\n\n        @retry(enabled=True, stop=tenacity.stop_after_attempt(3), reraise=True)\n        def fails_twice() -> bool:\n            nonlocal call_count\n            call_count += 1\n            if call_count < 3:\n                raise ValueError(\"fail\")\n            return True\n\n        assert fails_twice() is True\n        assert call_count == 3\n\n\nclass TestRetryWith:\n    def test_redefine_wait(self) -> None:\n        start = current_time_ms()\n        result = _retryable_test_with_wait.retry_with(wait=tenacity.wait_fixed(0.1))(\n            NoneReturnUntilAfterCount(5)\n        )\n        t = current_time_ms() - start\n        assert t >= 500\n        assert result is True\n\n    def test_redefine_stop(self) -> None:\n        result = _retryable_test_with_stop.retry_with(\n            stop=tenacity.stop_after_attempt(5)\n        )(NoneReturnUntilAfterCount(4))\n        assert result is True\n\n    def test_retry_error_cls_should_be_preserved(self) -> None:\n        @retry(stop=tenacity.stop_after_attempt(10), retry_error_cls=ValueError)  # type: ignore[arg-type]\n        def _retryable() -> None:\n            raise Exception(\"raised for test purposes\")\n\n        with pytest.raises(Exception) as exc_ctx:\n            _retryable.retry_with(stop=tenacity.stop_after_attempt(2))()\n\n        assert exc_ctx.type is ValueError, \"Should remap to specific exception type\"\n\n    def test_retry_error_callback_should_be_preserved(self) -> None:\n        def return_text(retry_state: RetryCallState) -> str:\n            return f\"Calling {retry_state.fn.__name__} keeps raising errors after {retry_state.attempt_number} attempts\"  # type: ignore[union-attr]\n\n        @retry(stop=tenacity.stop_after_attempt(10), retry_error_callback=return_text)\n        def _retryable() -> None:\n            raise Exception(\"raised for test purposes\")\n\n        result = _retryable.retry_with(stop=tenacity.stop_after_attempt(5))()\n        assert result == \"Calling _retryable keeps raising errors after 5 attempts\"\n\n\nclass TestBeforeAfterAttempts(unittest.TestCase):\n    _attempt_number = 0\n\n    def test_before_attempts(self) -> None:\n        TestBeforeAfterAttempts._attempt_number = 0\n\n        def _before(retry_state: RetryCallState) -> None:\n            TestBeforeAfterAttempts._attempt_number = retry_state.attempt_number\n\n        @retry(\n            wait=tenacity.wait_fixed(1),\n            stop=tenacity.stop_after_attempt(1),\n            before=_before,\n        )\n        def _test_before() -> None:\n            pass\n\n        _test_before()\n\n        self.assertTrue(TestBeforeAfterAttempts._attempt_number == 1)\n\n    def test_after_attempts(self) -> None:\n        TestBeforeAfterAttempts._attempt_number = 0\n\n        def _after(retry_state: RetryCallState) -> None:\n            TestBeforeAfterAttempts._attempt_number = retry_state.attempt_number\n\n        @retry(\n            wait=tenacity.wait_fixed(0.1),\n            stop=tenacity.stop_after_attempt(3),\n            after=_after,\n        )\n        def _test_after() -> None:\n            if TestBeforeAfterAttempts._attempt_number < 2:\n                raise Exception(\"testing after_attempts handler\")\n\n        _test_after()\n\n        self.assertTrue(TestBeforeAfterAttempts._attempt_number == 2)\n\n    def test_before_sleep(self) -> None:\n        def _before_sleep(retry_state: RetryCallState) -> None:\n            self.assertGreater(retry_state.next_action.sleep, 0)  # type: ignore[union-attr]\n            _before_sleep.attempt_number = retry_state.attempt_number  # type: ignore[attr-defined]\n\n        @retry(\n            wait=tenacity.wait_fixed(0.01),\n            stop=tenacity.stop_after_attempt(3),\n            before_sleep=_before_sleep,\n        )\n        def _test_before_sleep() -> None:\n            if _before_sleep.attempt_number < 2:  # type: ignore[attr-defined]\n                raise Exception(\"testing before_sleep_attempts handler\")\n\n        _test_before_sleep()\n        self.assertEqual(_before_sleep.attempt_number, 2)  # type: ignore[attr-defined]\n\n    def _before_sleep_log_raises(\n        self, get_call_fn: typing.Callable[..., typing.Any]\n    ) -> None:\n        thing = NoIOErrorAfterCount(2)\n        logger = logging.getLogger(self.id())\n        logger.propagate = False\n        logger.setLevel(logging.INFO)\n        handler = CapturingHandler()\n        logger.addHandler(handler)\n        try:\n            _before_sleep = tenacity.before_sleep_log(logger, logging.INFO)\n            retrying = Retrying(\n                wait=tenacity.wait_fixed(0.01),\n                stop=tenacity.stop_after_attempt(3),\n                before_sleep=_before_sleep,\n            )\n            get_call_fn(retrying)(thing.go)\n        finally:\n            logger.removeHandler(handler)\n\n        etalon_re = (\n            r\"^Retrying .* in 0\\.01 seconds as it raised \"\n            r\"(IO|OS)Error: Hi there, I'm an IOError\\.$\"\n        )\n        self.assertEqual(len(handler.records), 2)\n        fmt = logging.Formatter().format\n        self.assertRegex(fmt(handler.records[0]), etalon_re)\n        self.assertRegex(fmt(handler.records[1]), etalon_re)\n\n    def test_before_sleep_log_raises(self) -> None:\n        self._before_sleep_log_raises(lambda x: x)\n\n    def test_before_sleep_log_raises_with_exc_info(self) -> None:\n        thing = NoIOErrorAfterCount(2)\n        logger = logging.getLogger(self.id())\n        logger.propagate = False\n        logger.setLevel(logging.INFO)\n        handler = CapturingHandler()\n        logger.addHandler(handler)\n        try:\n            _before_sleep = tenacity.before_sleep_log(\n                logger, logging.INFO, exc_info=True\n            )\n            retrying = Retrying(\n                wait=tenacity.wait_fixed(0.01),\n                stop=tenacity.stop_after_attempt(3),\n                before_sleep=_before_sleep,\n            )\n            retrying(thing.go)\n        finally:\n            logger.removeHandler(handler)\n\n        etalon_re = re.compile(\n            r\"^Retrying .* in 0\\.01 seconds as it raised \"\n            r\"(IO|OS)Error: Hi there, I'm an IOError\\.{0}\"\n            r\"Traceback \\(most recent call last\\):{0}\"\n            r\".*$\".format(\"\\n\"),\n            flags=re.MULTILINE,\n        )\n        self.assertEqual(len(handler.records), 2)\n        fmt = logging.Formatter().format\n        self.assertRegex(fmt(handler.records[0]), etalon_re)\n        self.assertRegex(fmt(handler.records[1]), etalon_re)\n\n    def test_before_sleep_log_returns(self, exc_info: bool = False) -> None:\n        thing = NoneReturnUntilAfterCount(2)\n        logger = logging.getLogger(self.id())\n        logger.propagate = False\n        logger.setLevel(logging.INFO)\n        handler = CapturingHandler()\n        logger.addHandler(handler)\n        try:\n            _before_sleep = tenacity.before_sleep_log(\n                logger, logging.INFO, exc_info=exc_info\n            )\n            _retry = tenacity.retry_if_result(lambda result: result is None)\n            retrying = Retrying(\n                wait=tenacity.wait_fixed(0.01),\n                stop=tenacity.stop_after_attempt(3),\n                retry=_retry,\n                before_sleep=_before_sleep,\n            )\n            retrying(thing.go)\n        finally:\n            logger.removeHandler(handler)\n\n        etalon_re = r\"^Retrying .* in 0\\.01 seconds as it returned None\\.$\"\n        self.assertEqual(len(handler.records), 2)\n        fmt = logging.Formatter().format\n        self.assertRegex(fmt(handler.records[0]), etalon_re)\n        self.assertRegex(fmt(handler.records[1]), etalon_re)\n\n    def test_before_sleep_log_returns_with_exc_info(self) -> None:\n        self.test_before_sleep_log_returns(exc_info=True)\n\n\nclass TestReraiseExceptions(unittest.TestCase):\n    def test_reraise_by_default(self) -> None:\n        calls = []\n\n        @retry(\n            wait=tenacity.wait_fixed(0.1),\n            stop=tenacity.stop_after_attempt(2),\n            reraise=True,\n        )\n        def _reraised_by_default() -> None:\n            calls.append(\"x\")\n            raise KeyError(\"Bad key\")\n\n        self.assertRaises(KeyError, _reraised_by_default)\n        self.assertEqual(2, len(calls))\n\n    def test_reraise_from_retry_error(self) -> None:\n        calls = []\n\n        @retry(wait=tenacity.wait_fixed(0.1), stop=tenacity.stop_after_attempt(2))\n        def _raise_key_error() -> None:\n            calls.append(\"x\")\n            raise KeyError(\"Bad key\")\n\n        def _reraised_key_error() -> None:\n            try:\n                _raise_key_error()\n            except tenacity.RetryError as retry_err:\n                retry_err.reraise()\n\n        self.assertRaises(KeyError, _reraised_key_error)\n        self.assertEqual(2, len(calls))\n\n    def test_reraise_timeout_from_retry_error(self) -> None:\n        calls = []\n\n        @retry(\n            wait=tenacity.wait_fixed(0.1),\n            stop=tenacity.stop_after_attempt(2),\n            retry=lambda retry_state: True,\n        )\n        def _mock_fn() -> None:\n            calls.append(\"x\")\n\n        def _reraised_mock_fn() -> None:\n            try:\n                _mock_fn()\n            except tenacity.RetryError as retry_err:\n                retry_err.reraise()\n\n        self.assertRaises(tenacity.RetryError, _reraised_mock_fn)\n        self.assertEqual(2, len(calls))\n\n    def test_reraise_no_exception(self) -> None:\n        calls = []\n\n        @retry(\n            wait=tenacity.wait_fixed(0.1),\n            stop=tenacity.stop_after_attempt(2),\n            retry=lambda retry_state: True,\n            reraise=True,\n        )\n        def _mock_fn() -> None:\n            calls.append(\"x\")\n\n        self.assertRaises(tenacity.RetryError, _mock_fn)\n        self.assertEqual(2, len(calls))\n\n\nclass TestStatistics(unittest.TestCase):\n    def test_stats(self) -> None:\n        @retry()\n        def _foobar() -> int:\n            return 42\n\n        self.assertEqual({}, _foobar.statistics)\n        _foobar()\n        self.assertEqual(1, _foobar.statistics[\"attempt_number\"])\n\n    def test_stats_failing(self) -> None:\n        @retry(stop=tenacity.stop_after_attempt(2))\n        def _foobar() -> None:\n            raise ValueError(42)\n\n        self.assertEqual({}, _foobar.statistics)\n        with contextlib.suppress(Exception):\n            _foobar()\n        self.assertEqual(2, _foobar.statistics[\"attempt_number\"])\n\n    def test_retry_object_statistics_synced(self) -> None:\n        \"\"\"Test that func.retry.statistics is synced with func.statistics.\"\"\"\n\n        @retry(stop=tenacity.stop_after_attempt(3))\n        def _foobar() -> int:\n            return 42\n\n        _foobar()\n        self.assertEqual(\n            _foobar.retry.statistics[\"attempt_number\"],\n            _foobar.statistics[\"attempt_number\"],\n        )\n\n    def test_retry_object_statistics_during_execution(self) -> None:\n        \"\"\"Test that func.retry.statistics is accessible during execution.\"\"\"\n        attempts: list[int] = []\n\n        @retry(\n            stop=tenacity.stop_after_attempt(3),\n            retry=tenacity.retry_if_exception_type(ValueError),\n            reraise=True,\n        )\n        def _foobar() -> int:\n            attempts.append(_foobar.retry.statistics[\"attempt_number\"])\n            if len(attempts) < 3:\n                raise ValueError(\"retry\")\n            return 42\n\n        _foobar()\n        self.assertEqual(attempts, [1, 2, 3])\n\n\nclass TestRetryErrorCallback(unittest.TestCase):\n    def setUp(self) -> None:\n        self._attempt_number = 0\n        self._callback_called = False\n\n    def _callback(self, fut: tenacity.Future) -> tenacity.Future:\n        self._callback_called = True\n        return fut\n\n    def test_retry_error_callback(self) -> None:\n        num_attempts = 3\n\n        def retry_error_callback(retry_state: RetryCallState) -> typing.Any:\n            retry_error_callback.called_times += 1  # type: ignore[attr-defined]\n            return retry_state.outcome\n\n        retry_error_callback.called_times = 0  # type: ignore[attr-defined]\n\n        @retry(\n            stop=tenacity.stop_after_attempt(num_attempts),\n            retry_error_callback=retry_error_callback,\n        )\n        def _foobar() -> None:\n            self._attempt_number += 1\n            raise Exception(\"This exception should not be raised\")\n\n        result = _foobar()\n\n        self.assertEqual(retry_error_callback.called_times, 1)  # type: ignore[attr-defined]\n        self.assertEqual(num_attempts, self._attempt_number)\n        self.assertIsInstance(result, tenacity.Future)\n\n\nclass TestContextManager(unittest.TestCase):\n    def test_context_manager_retry_one(self) -> None:\n        from tenacity import Retrying\n\n        raise_ = True\n\n        for attempt in Retrying():\n            with attempt:\n                if raise_:\n                    raise_ = False\n                    raise Exception(\"Retry it!\")\n\n    def test_context_manager_on_error(self) -> None:\n        from tenacity import Retrying\n\n        class CustomError(Exception):\n            pass\n\n        retry = Retrying(retry=tenacity.retry_if_exception_type(IOError))\n\n        def test() -> None:\n            for attempt in retry:\n                with attempt:\n                    raise CustomError(\"Don't retry!\")\n\n        self.assertRaises(CustomError, test)\n\n    def test_context_manager_retry_error(self) -> None:\n        from tenacity import Retrying\n\n        retry = Retrying(stop=tenacity.stop_after_attempt(2))\n\n        def test() -> None:\n            for attempt in retry:\n                with attempt:\n                    raise Exception(\"Retry it!\")\n\n        self.assertRaises(RetryError, test)\n\n    def test_context_manager_reraise(self) -> None:\n        from tenacity import Retrying\n\n        class CustomError(Exception):\n            pass\n\n        retry = Retrying(reraise=True, stop=tenacity.stop_after_attempt(2))\n\n        def test() -> None:\n            for attempt in retry:\n                with attempt:\n                    raise CustomError(\"Don't retry!\")\n\n        self.assertRaises(CustomError, test)\n\n\nclass TestInvokeAsCallable:\n    \"\"\"Test direct invocation of Retrying as a callable.\"\"\"\n\n    @staticmethod\n    def invoke(retry: Retrying, f: typing.Callable[..., typing.Any]) -> typing.Any:\n        \"\"\"\n        Invoke Retrying logic.\n\n        Wrapper allows testing different call mechanisms in test sub-classes.\n        \"\"\"\n        return retry(f)\n\n    def test_retry_one(self) -> None:\n        def f() -> typing.Any:\n            f.calls.append(len(f.calls) + 1)  # type: ignore[attr-defined]\n            if len(f.calls) <= 1:  # type: ignore[attr-defined]\n                raise Exception(\"Retry it!\")\n            return 42\n\n        f.calls = []  # type: ignore[attr-defined]\n\n        retry = Retrying()\n        assert self.invoke(retry, f) == 42\n        assert f.calls == [1, 2]  # type: ignore[attr-defined]\n\n    def test_on_error(self) -> None:\n        class CustomError(Exception):\n            pass\n\n        def f() -> typing.Any:\n            f.calls.append(len(f.calls) + 1)  # type: ignore[attr-defined]\n            if len(f.calls) <= 1:  # type: ignore[attr-defined]\n                raise CustomError(\"Don't retry!\")\n            return 42\n\n        f.calls = []  # type: ignore[attr-defined]\n\n        retry = Retrying(retry=tenacity.retry_if_exception_type(IOError))\n        with pytest.raises(CustomError):\n            self.invoke(retry, f)\n        assert f.calls == [1]  # type: ignore[attr-defined]\n\n    def test_retry_error(self) -> None:\n        def f() -> typing.Any:\n            f.calls.append(len(f.calls) + 1)  # type: ignore[attr-defined]\n            raise Exception(\"Retry it!\")\n\n        f.calls = []  # type: ignore[attr-defined]\n\n        retry = Retrying(stop=tenacity.stop_after_attempt(2))\n        with pytest.raises(RetryError):\n            self.invoke(retry, f)\n        assert f.calls == [1, 2]  # type: ignore[attr-defined]\n\n    def test_reraise(self) -> None:\n        class CustomError(Exception):\n            pass\n\n        def f() -> typing.Any:\n            f.calls.append(len(f.calls) + 1)  # type: ignore[attr-defined]\n            raise CustomError(\"Retry it!\")\n\n        f.calls = []  # type: ignore[attr-defined]\n\n        retry = Retrying(reraise=True, stop=tenacity.stop_after_attempt(2))\n        with pytest.raises(CustomError):\n            self.invoke(retry, f)\n        assert f.calls == [1, 2]  # type: ignore[attr-defined]\n\n\nclass TestRetryException(unittest.TestCase):\n    def test_retry_error_is_pickleable(self) -> None:\n        import pickle\n\n        expected = RetryError(last_attempt=123)  # type: ignore[arg-type]\n        pickled = pickle.dumps(expected)\n        actual = pickle.loads(pickled)\n        self.assertEqual(expected.last_attempt, actual.last_attempt)\n\n\nclass TestRetryTyping(unittest.TestCase):\n    def test_retry_type_annotations(self) -> None:\n        \"\"\"The decorator should maintain types of decorated functions.\"\"\"\n\n        def num_to_str(number):\n            # type: (int) -> str\n            return str(number)\n\n        # equivalent to a raw @retry decoration\n        with_raw = retry(num_to_str)\n        with_raw_result = with_raw(1)\n\n        # equivalent to a @retry(...) decoration\n        with_constructor = retry()(num_to_str)\n        with_constructor_result = with_raw(1)\n\n        # These raise TypeError exceptions if they fail\n        check_type(with_raw, typing.Callable[[int], str])\n        check_type(with_raw_result, str)\n        check_type(with_constructor, typing.Callable[[int], str])\n        check_type(with_constructor_result, str)\n\n\nclass TestMockingSleep:\n    RETRY_ARGS = {\n        \"wait\": tenacity.wait_fixed(0.1),\n        \"stop\": tenacity.stop_after_attempt(5),\n    }\n\n    def _fail(self) -> None:\n        raise NotImplementedError\n\n    @retry(**RETRY_ARGS)  # type: ignore[call-overload, untyped-decorator]\n    def _decorated_fail(self) -> None:\n        self._fail()\n\n    @pytest.fixture()\n    def mock_sleep(\n        self, monkeypatch: typing.Any\n    ) -> typing.Generator[typing.Any, None, None]:\n        class MockSleep:\n            call_count = 0\n\n            def __call__(self, seconds: float) -> None:\n                self.call_count += 1\n\n        sleep = MockSleep()\n        monkeypatch.setattr(tenacity.nap.time, \"sleep\", sleep)  # type: ignore[attr-defined]\n        yield sleep\n\n    def test_decorated(self, mock_sleep: typing.Any) -> None:\n        with pytest.raises(RetryError):\n            self._decorated_fail()\n        assert mock_sleep.call_count == 4\n\n    def test_decorated_retry_with(self, mock_sleep: typing.Any) -> None:\n        fail_faster = self._decorated_fail.retry_with(\n            stop=tenacity.stop_after_attempt(2),\n        )\n        with pytest.raises(RetryError):\n            fail_faster()\n        assert mock_sleep.call_count == 1\n\n\nclass TestPickle(unittest.TestCase):\n    def test_retrying_picklable(self) -> None:\n        \"\"\"Retrying objects can be pickled for multiprocessing support.\"\"\"\n        retrying = Retrying(stop=tenacity.stop_after_attempt(3))\n        pickled = pickle.dumps(retrying)\n        restored = pickle.loads(pickled)\n        assert isinstance(restored, Retrying)\n        assert isinstance(restored.stop, tenacity.stop_after_attempt)\n\n    def test_retrying_picklable_after_run(self) -> None:\n        \"\"\"Retrying objects can be pickled even after being used.\"\"\"\n        retrying = Retrying(stop=tenacity.stop_after_attempt(3))\n        # Access statistics to populate _local\n        _ = retrying.statistics\n        pickled = pickle.dumps(retrying)\n        restored = pickle.loads(pickled)\n        assert isinstance(restored, Retrying)\n        # Statistics should be reset on the restored object\n        assert restored.statistics == {}\n\n    def test_retry_strategies_picklable(self) -> None:\n        \"\"\"All built-in retry strategies can be pickled.\"\"\"\n        strategies = [\n            tenacity.retry_if_exception_type(ValueError),\n            tenacity.retry_if_not_exception_type(ValueError),\n            tenacity.retry_if_exception_message(message=\"fail\"),\n            tenacity.retry_if_exception_message(match=\"fail.*\"),\n            tenacity.retry_if_not_exception_message(message=\"fail\"),\n        ]\n        for strategy in strategies:\n            restored = pickle.loads(pickle.dumps(strategy))\n            assert type(restored) is type(strategy)\n\n    def test_retrying_pickle_round_trip_works(self) -> None:\n        \"\"\"A pickled-then-restored Retrying object retries correctly.\"\"\"\n        retrying = Retrying(\n            stop=tenacity.stop_after_attempt(3),\n            retry=tenacity.retry_if_exception_type(ValueError),\n            reraise=True,\n        )\n        restored = pickle.loads(pickle.dumps(retrying))\n\n        calls = 0\n\n        def succeed_on_third() -> str:\n            nonlocal calls\n            calls += 1\n            if calls < 3:\n                raise ValueError(\"not yet\")\n            return \"ok\"\n\n        result = restored(succeed_on_third)\n        assert result == \"ok\"\n        assert calls == 3\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_tornado.py",
    "content": "# Copyright 2017 Elisey Zanko\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport unittest\nfrom collections.abc import Generator\nfrom typing import Any\n\nfrom tornado import gen, testing\n\nfrom tenacity import RetryError, retry, stop_after_attempt, tornadoweb\n\nfrom .test_tenacity import NoIOErrorAfterCount\n\n\n@retry\n@gen.coroutine\ndef _retryable_coroutine(thing: NoIOErrorAfterCount) -> Generator[Any, Any, None]:\n    yield gen.sleep(0.00001)\n    thing.go()\n\n\n@retry(stop=stop_after_attempt(2))\n@gen.coroutine\ndef _retryable_coroutine_with_2_attempts(\n    thing: NoIOErrorAfterCount,\n) -> Generator[Any, Any, None]:\n    yield gen.sleep(0.00001)\n    thing.go()\n\n\nclass TestTornado(testing.AsyncTestCase):\n    @testing.gen_test\n    def test_retry(self) -> Generator[Any, Any, None]:\n        assert gen.is_coroutine_function(_retryable_coroutine)\n        thing = NoIOErrorAfterCount(5)\n        yield _retryable_coroutine(thing)\n        assert thing.counter == thing.count\n\n    @testing.gen_test\n    def test_stop_after_attempt(self) -> Generator[Any, Any, None]:\n        assert gen.is_coroutine_function(_retryable_coroutine)\n        thing = NoIOErrorAfterCount(2)\n        try:\n            yield _retryable_coroutine_with_2_attempts(thing)\n        except RetryError:\n            assert thing.counter == 2\n\n    def test_repr(self) -> None:\n        repr(tornadoweb.TornadoRetrying())\n\n    def test_old_tornado(self) -> None:\n        old_attr = gen.is_coroutine_function\n        try:\n            del gen.is_coroutine_function\n\n            # is_coroutine_function was introduced in tornado 4.5;\n            # verify that we don't *completely* fall over on old versions\n            @retry\n            def retryable(thing: NoIOErrorAfterCount) -> None:\n                pass\n\n        finally:\n            gen.is_coroutine_function = old_attr\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "import functools\n\nfrom tenacity import _utils\n\n\ndef test_is_coroutine_callable() -> None:\n    async def async_func() -> None:\n        pass\n\n    def sync_func() -> None:\n        pass\n\n    class AsyncClass:\n        async def __call__(self) -> None:\n            pass\n\n    class SyncClass:\n        def __call__(self) -> None:\n            pass\n\n    lambda_fn = lambda: None  # noqa: E731\n\n    partial_async_func = functools.partial(async_func)\n    partial_sync_func = functools.partial(sync_func)\n    partial_async_class = functools.partial(AsyncClass().__call__)\n    partial_sync_class = functools.partial(SyncClass().__call__)\n    partial_lambda_fn = functools.partial(lambda_fn)\n\n    assert _utils.is_coroutine_callable(async_func) is True\n    assert _utils.is_coroutine_callable(sync_func) is False\n    assert _utils.is_coroutine_callable(AsyncClass) is False\n    assert _utils.is_coroutine_callable(AsyncClass()) is True\n    assert _utils.is_coroutine_callable(SyncClass) is False\n    assert _utils.is_coroutine_callable(SyncClass()) is False\n    assert _utils.is_coroutine_callable(lambda_fn) is False\n\n    assert _utils.is_coroutine_callable(partial_async_func) is True\n    assert _utils.is_coroutine_callable(partial_sync_func) is False\n    assert _utils.is_coroutine_callable(partial_async_class) is True\n    assert _utils.is_coroutine_callable(partial_sync_class) is False\n    assert _utils.is_coroutine_callable(partial_lambda_fn) is False\n\n\ndef test_find_ordinal() -> None:\n    assert _utils.find_ordinal(1) == \"st\"\n    assert _utils.find_ordinal(2) == \"nd\"\n    assert _utils.find_ordinal(3) == \"rd\"\n    assert _utils.find_ordinal(4) == \"th\"\n    assert _utils.find_ordinal(11) == \"th\"\n    assert _utils.find_ordinal(12) == \"th\"\n    assert _utils.find_ordinal(13) == \"th\"\n    assert _utils.find_ordinal(21) == \"st\"\n    assert _utils.find_ordinal(22) == \"nd\"\n    assert _utils.find_ordinal(23) == \"rd\"\n    assert _utils.find_ordinal(111) == \"th\"\n    assert _utils.find_ordinal(112) == \"th\"\n    assert _utils.find_ordinal(113) == \"th\"\n"
  }
]