pax_global_header00006660000000000000000000000064144652060740014522gustar00rootroot0000000000000052 comment=a48974404c746593f78c116faceb56a0db50309e async-timeout-4.0.3/000077500000000000000000000000001446520607400143275ustar00rootroot00000000000000async-timeout-4.0.3/.codecov.yml000066400000000000000000000000521446520607400165470ustar00rootroot00000000000000codecov: notify: after_n_builds: 10 async-timeout-4.0.3/.github/000077500000000000000000000000001446520607400156675ustar00rootroot00000000000000async-timeout-4.0.3/.github/dependabot.yml000066400000000000000000000001751446520607400205220ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: pip directory: "/" schedule: interval: daily open-pull-requests-limit: 10 async-timeout-4.0.3/.github/workflows/000077500000000000000000000000001446520607400177245ustar00rootroot00000000000000async-timeout-4.0.3/.github/workflows/auto-merge.yml000066400000000000000000000011401446520607400225100ustar00rootroot00000000000000name: Dependabot auto-merge on: pull_request_target permissions: pull-requests: write contents: write jobs: dependabot: runs-on: ubuntu-latest if: ${{ github.actor == 'dependabot[bot]' }} steps: - name: Dependabot metadata id: metadata uses: dependabot/fetch-metadata@v1.3.4 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} async-timeout-4.0.3/.github/workflows/ci.yml000066400000000000000000000060171446520607400210460ustar00rootroot00000000000000name: CI on: push: branches: - master - '[0-9].[0-9]+' # matches to backport branches, e.g. 3.6 tags: [ 'v*' ] pull_request: branches: - master - '[0-9].[0-9]+' schedule: - cron: '0 6 * * *' # Daily 6AM UTC build jobs: lint: name: Linter runs-on: ubuntu-latest timeout-minutes: 30 # pre-commit env update can take time steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Python uses: actions/setup-python@v4 with: python-version: 3.x - name: Cache PyPI uses: actions/cache@v3 with: key: pip-lint-${{ hashFiles('requirements.txt') }} path: ~/.cache/pip restore-keys: | pip-lint- - name: Install dependencies run: | make install - name: Run linter run: | make lint - name: Run twine checker run: | make check test: name: Test needs: lint strategy: matrix: pyver: ["3.7", "3.8", "3.9", "3.10", "3.11"] os: [ubuntu, macos, windows] include: - pyver: pypy-3.9 os: ubuntu runs-on: ${{ matrix.os }}-latest timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Python ${{ matrix.pyver }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.pyver }} - name: Get pip cache dir id: pip-cache run: | echo "::set-output name=dir::$(pip cache dir)" # - name: Cache - name: Cache PyPI uses: actions/cache@v3 with: key: pip-ci-${{ runner.os }}-${{ matrix.pyver }}-${{ hashFiles('requirements/*.txt') }} path: ${{ steps.pip-cache.outputs.dir }} restore-keys: | pip-ci-${{ runner.os }}-${{ matrix.pyver }}- - name: Install dependencies run: | make install - name: Run unittests env: COLOR: 'yes' run: | python -m pytest tests python -m coverage xml - name: Upload coverage uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: unit fail_ci_if_error: false deploy: name: Deploy runs-on: ubuntu-latest needs: test # Run only on pushing a tag if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') steps: - name: Checkout uses: actions/checkout@v3 - name: Setup Python uses: actions/setup-python@v4 with: python-version: 3.x - name: Install dependencies run: | make install - name: Make dists run: python -m build - name: Make Release uses: aio-libs/create-release@v1.6.6 with: changes_file: CHANGES.rst name: async-timeout version_file: async_timeout/__init__.py github_token: ${{ secrets.GITHUB_TOKEN }} pypi_token: ${{ secrets.PYPI_TOKEN }} fix_issue_regex: '\(`#(\\d+) `_\)' fix_issue_repl: "(#\\1)" async-timeout-4.0.3/.github/workflows/codeql.yml000066400000000000000000000053061446520607400217220ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ "master" ] pull_request: # The branches below must be a subset of the branches above branches: [ "master" ] schedule: - cron: '28 19 * * 2' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Checkout repository uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs queries: security-and-quality # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v2 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # If the Autobuild fails above, remove it and uncomment the following three lines. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # - run: | # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 with: category: "/language:${{matrix.language}}" async-timeout-4.0.3/.gitignore000066400000000000000000000020601446520607400163150ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # IPython Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # dotenv .env # virtualenv venv/ ENV/ # Spyder project settings .spyderproject # Rope project settings .ropeproject .mypy_cache .pytest_cache async-timeout-4.0.3/.mypy.ini000066400000000000000000000010751446520607400161070ustar00rootroot00000000000000[mypy] files = async_timeout, tests check_untyped_defs = True follow_imports_for_stubs = True disallow_any_decorated = True disallow_any_generics = True disallow_incomplete_defs = True disallow_subclassing_any = True disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True implicit_reexport = False no_implicit_optional = True show_error_codes = True strict_equality = True warn_incomplete_stub = True warn_redundant_casts = True warn_unreachable = True warn_unused_ignores = True disallow_any_unimported = True warn_return_any = True async-timeout-4.0.3/.pre-commit-config.yaml000066400000000000000000000023441446520607400206130ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: 'v4.4.0' hooks: - id: check-merge-conflict exclude: "rst$" - repo: https://github.com/asottile/yesqa rev: v1.5.0 hooks: - id: yesqa - repo: https://github.com/PyCQA/isort rev: '5.12.0' hooks: - id: isort - repo: https://github.com/psf/black rev: '23.7.0' hooks: - id: black language_version: python3 # Should be a command that runs python3.6+ - repo: https://github.com/pre-commit/pre-commit-hooks rev: 'v4.4.0' hooks: - id: check-case-conflict - id: check-json - id: check-xml - id: check-yaml - id: debug-statements - id: check-added-large-files - id: end-of-file-fixer exclude: "[.]md$" - id: requirements-txt-fixer - id: trailing-whitespace exclude: "[.]md$" - id: check-symlinks - id: debug-statements # Another entry is required to apply file-contents-sorter to another file - repo: https://github.com/pre-commit/pre-commit-hooks rev: 'v4.4.0' hooks: - id: file-contents-sorter files: | .gitignore - repo: https://github.com/asottile/pyupgrade rev: 'v3.10.1' hooks: - id: pyupgrade args: ['--py36-plus'] - repo: https://github.com/PyCQA/flake8 rev: '6.1.0' hooks: - id: flake8 async-timeout-4.0.3/CHANGES.rst000066400000000000000000000053131446520607400161330ustar00rootroot00000000000000======= CHANGES ======= .. towncrier release notes start 4.0.3 (2023-08-10) ================== * Fixed compatibility with asyncio.timeout() on Python 3.11+. * Added support for Python 3.11. * Dropped support for Python 3.6. 4.0.2 (2021-12-20) ================== Misc ---- - `#259 `_, `#274 `_ 4.0.1 (2121-11-10) ================== - Fix regression: 1. Don't raise TimeoutError from timeout object that doesn't enter into async context manager 2. Use call_soon() for raising TimeoutError if deadline is reached on entering into async context manager (#258) - Make ``Timeout`` class available in ``__all__``. 4.0.0 (2021-11-01) ================== * Implemented ``timeout_at(deadline)`` (#117) * Supported ``timeout.deadline`` and ``timeout.expired`` properties. * Dropped ``timeout.remaining`` property: it can be calculated as ``timeout.deadline - loop.time()`` * Dropped ``timeout.timeout`` property that returns a relative timeout based on the timeout object creation time; the absolute ``timeout.deadline`` should be used instead. * Added the deadline modification methods: ``timeout.reject()``, ``timeout.shift(delay)``, ``timeout.update(deadline)``. * Deprecated synchronous context manager usage 3.0.1 (2018-10-09) ================== * More aggressive typing (#48) 3.0.0 (2018-05-05) ================== * Drop Python 3.4, the minimal supported version is Python 3.5.3 * Provide type annotations 2.0.1 (2018-03-13) ================== * Fix ``PendingDeprecationWarning`` on Python 3.7 (#33) 2.0.0 (2017-10-09) ================== * Changed ``timeout <= 0`` behaviour * Backward incompatibility change, prior this version ``0`` was shortcut for ``None`` * when timeout <= 0 ``TimeoutError`` raised faster 1.4.0 (2017-09-09) ================== * Implement ``remaining`` property (#20) * If timeout is not started yet or started unconstrained: ``remaining`` is ``None`` * If timeout is expired: ``remaining`` is ``0.0`` * All others: roughly amount of time before ``TimeoutError`` is triggered 1.3.0 (2017-08-23) ================== * Don't suppress nested exception on timeout. Exception context points on cancelled line with suspended ``await`` (#13) * Introduce ``.timeout`` property (#16) * Add methods for using as async context manager (#9) 1.2.1 (2017-05-02) ================== * Support unpublished event loop's "current_task" api. 1.2.0 (2017-03-11) ================== * Extra check on context manager exit * 0 is no-op timeout 1.1.0 (2016-10-20) ================== * Rename to ``async-timeout`` 1.0.0 (2016-09-09) ================== * The first release. async-timeout-4.0.3/CHANGES/000077500000000000000000000000001446520607400153775ustar00rootroot00000000000000async-timeout-4.0.3/CHANGES/README.rst000066400000000000000000000066361446520607400171010ustar00rootroot00000000000000.. _Adding change notes with your PRs: Adding change notes with your PRs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It is very important to maintain a log for news of how updating to the new version of the software will affect end-users. This is why we enforce collection of the change fragment files in pull requests as per `Towncrier philosophy`_. The idea is that when somebody makes a change, they must record the bits that would affect end-users only including information that would be useful to them. Then, when the maintainers publish a new release, they'll automatically use these records to compose a change log for the respective version. It is important to understand that including unnecessary low-level implementation related details generates noise that is not particularly useful to the end-users most of the time. And so such details should be recorded in the Git history rather than a changelog. Alright! So how to add a news fragment? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``async-timeout`` uses `towncrier `_ for changelog management. To submit a change note about your PR, add a text file into the ``CHANGES/`` folder. It should contain an explanation of what applying this PR will change in the way end-users interact with the project. One sentence is usually enough but feel free to add as many details as you feel necessary for the users to understand what it means. **Use the past tense** for the text in your fragment because, combined with others, it will be a part of the "news digest" telling the readers **what changed** in a specific version of the library *since the previous version*. You should also use reStructuredText syntax for highlighting code (inline or block), linking parts of the docs or external sites. Finally, name your file following the convention that Towncrier understands: it should start with the number of an issue or a PR followed by a dot, then add a patch type, like ``feature``, ``doc``, ``misc`` etc., and add ``.rst`` as a suffix. If you need to add more than one fragment, you may add an optional sequence number (delimited with another period) between the type and the suffix. In general the name will follow ``..rst`` pattern, where the categories are: - ``feature``: Any new feature - ``bugfix``: A bug fix - ``doc``: A change to the documentation - ``misc``: Changes internal to the repo like CI, test and build changes - ``removal``: For deprecations and removals of an existing feature or behavior A pull request may have more than one of these components, for example a code change may introduce a new feature that deprecates an old feature, in which case two fragments should be added. It is not necessary to make a separate documentation fragment for documentation changes accompanying the relevant code changes. Examples for adding changelog entries to your Pull Requests ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File :file:`CHANGES/603.removal.1.rst`: .. code-block:: rst Dropped Python 3.5 support; Python 3.6 is the minimal supported Python version. File :file:`CHANGES/550.bugfix.rst`: .. code-block:: rst Started shipping Windows wheels for the x86 architecture. File :file:`CHANGES/553.feature.rst`: .. code-block:: rst Added support for ``GenericAliases`` (``MultiDict[str]``) under Python 3.9 and higher. .. _Towncrier philosophy: https://towncrier.readthedocs.io/en/actual-freaking-docs/#philosophy async-timeout-4.0.3/LICENSE000066400000000000000000000010701446520607400153320ustar00rootroot00000000000000Copyright 2016-2020 aio-libs collaboration. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. async-timeout-4.0.3/MANIFEST.in000066400000000000000000000002031446520607400160600ustar00rootroot00000000000000include LICENSE include CHANGES.rst include README.rst graft async_timeout graft tests global-exclude *.pyc global-exclude *.cache async-timeout-4.0.3/Makefile000066400000000000000000000005241446520607400157700ustar00rootroot00000000000000test: lint python -m pytest tests lint: fmt python -m mypy fmt: ifdef CI python -m pre_commit run --all-files --show-diff-on-failure else python -m pre_commit run --all-files endif check: python -m build python -m twine check dist/* install: python -m pip install --user -U pip python -m pip install --user -r requirements.txt async-timeout-4.0.3/README.rst000066400000000000000000000053201446520607400160160ustar00rootroot00000000000000async-timeout ============= .. image:: https://travis-ci.com/aio-libs/async-timeout.svg?branch=master :target: https://travis-ci.com/aio-libs/async-timeout .. image:: https://codecov.io/gh/aio-libs/async-timeout/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/async-timeout .. image:: https://img.shields.io/pypi/v/async-timeout.svg :target: https://pypi.python.org/pypi/async-timeout .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter asyncio-compatible timeout context manager. Usage example ------------- The context manager is useful in cases when you want to apply timeout logic around block of code or in cases when ``asyncio.wait_for()`` is not suitable. Also it's much faster than ``asyncio.wait_for()`` because ``timeout`` doesn't create a new task. The ``timeout(delay, *, loop=None)`` call returns a context manager that cancels a block on *timeout* expiring:: from async_timeout import timeout async with timeout(1.5): await inner() 1. If ``inner()`` is executed faster than in ``1.5`` seconds nothing happens. 2. Otherwise ``inner()`` is cancelled internally by sending ``asyncio.CancelledError`` into but ``asyncio.TimeoutError`` is raised outside of context manager scope. *timeout* parameter could be ``None`` for skipping timeout functionality. Alternatively, ``timeout_at(when)`` can be used for scheduling at the absolute time:: loop = asyncio.get_event_loop() now = loop.time() async with timeout_at(now + 1.5): await inner() Please note: it is not POSIX time but a time with undefined starting base, e.g. the time of the system power on. Context manager has ``.expired`` property for check if timeout happens exactly in context manager:: async with timeout(1.5) as cm: await inner() print(cm.expired) The property is ``True`` if ``inner()`` execution is cancelled by timeout context manager. If ``inner()`` call explicitly raises ``TimeoutError`` ``cm.expired`` is ``False``. The scheduled deadline time is available as ``.deadline`` property:: async with timeout(1.5) as cm: cm.deadline Not finished yet timeout can be rescheduled by ``shift_by()`` or ``shift_to()`` methods:: async with timeout(1.5) as cm: cm.shift(1) # add another second on waiting cm.update(loop.time() + 5) # reschedule to now+5 seconds Rescheduling is forbidden if the timeout is expired or after exit from ``async with`` code block. Installation ------------ :: $ pip install async-timeout The library is Python 3 only! Authors and License ------------------- The module is written by Andrew Svetlov. It's *Apache 2* licensed and freely available. async-timeout-4.0.3/async_timeout/000077500000000000000000000000001446520607400172125ustar00rootroot00000000000000async-timeout-4.0.3/async_timeout/__init__.py000066400000000000000000000161121446520607400213240ustar00rootroot00000000000000import asyncio import enum import sys import warnings from types import TracebackType from typing import Optional, Type if sys.version_info >= (3, 8): from typing import final else: from typing_extensions import final if sys.version_info >= (3, 11): def _uncancel_task(task: "asyncio.Task[object]") -> None: task.uncancel() else: def _uncancel_task(task: "asyncio.Task[object]") -> None: pass __version__ = "4.0.3" __all__ = ("timeout", "timeout_at", "Timeout") def timeout(delay: Optional[float]) -> "Timeout": """timeout context manager. Useful in cases when you want to apply timeout logic around block of code or in cases when asyncio.wait_for is not suitable. For example: >>> async with timeout(0.001): ... async with aiohttp.get('https://github.com') as r: ... await r.text() delay - value in seconds or None to disable timeout logic """ loop = asyncio.get_running_loop() if delay is not None: deadline = loop.time() + delay # type: Optional[float] else: deadline = None return Timeout(deadline, loop) def timeout_at(deadline: Optional[float]) -> "Timeout": """Schedule the timeout at absolute time. deadline argument points on the time in the same clock system as loop.time(). Please note: it is not POSIX time but a time with undefined starting base, e.g. the time of the system power on. >>> async with timeout_at(loop.time() + 10): ... async with aiohttp.get('https://github.com') as r: ... await r.text() """ loop = asyncio.get_running_loop() return Timeout(deadline, loop) class _State(enum.Enum): INIT = "INIT" ENTER = "ENTER" TIMEOUT = "TIMEOUT" EXIT = "EXIT" @final class Timeout: # Internal class, please don't instantiate it directly # Use timeout() and timeout_at() public factories instead. # # Implementation note: `async with timeout()` is preferred # over `with timeout()`. # While technically the Timeout class implementation # doesn't need to be async at all, # the `async with` statement explicitly points that # the context manager should be used from async function context. # # This design allows to avoid many silly misusages. # # TimeoutError is raised immediately when scheduled # if the deadline is passed. # The purpose is to time out as soon as possible # without waiting for the next await expression. __slots__ = ("_deadline", "_loop", "_state", "_timeout_handler", "_task") def __init__( self, deadline: Optional[float], loop: asyncio.AbstractEventLoop ) -> None: self._loop = loop self._state = _State.INIT self._task: Optional["asyncio.Task[object]"] = None self._timeout_handler = None # type: Optional[asyncio.Handle] if deadline is None: self._deadline = None # type: Optional[float] else: self.update(deadline) def __enter__(self) -> "Timeout": warnings.warn( "with timeout() is deprecated, use async with timeout() instead", DeprecationWarning, stacklevel=2, ) self._do_enter() return self def __exit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Optional[bool]: self._do_exit(exc_type) return None async def __aenter__(self) -> "Timeout": self._do_enter() return self async def __aexit__( self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> Optional[bool]: self._do_exit(exc_type) return None @property def expired(self) -> bool: """Is timeout expired during execution?""" return self._state == _State.TIMEOUT @property def deadline(self) -> Optional[float]: return self._deadline def reject(self) -> None: """Reject scheduled timeout if any.""" # cancel is maybe better name but # task.cancel() raises CancelledError in asyncio world. if self._state not in (_State.INIT, _State.ENTER): raise RuntimeError(f"invalid state {self._state.value}") self._reject() def _reject(self) -> None: self._task = None if self._timeout_handler is not None: self._timeout_handler.cancel() self._timeout_handler = None def shift(self, delay: float) -> None: """Advance timeout on delay seconds. The delay can be negative. Raise RuntimeError if shift is called when deadline is not scheduled """ deadline = self._deadline if deadline is None: raise RuntimeError("cannot shift timeout if deadline is not scheduled") self.update(deadline + delay) def update(self, deadline: float) -> None: """Set deadline to absolute value. deadline argument points on the time in the same clock system as loop.time(). If new deadline is in the past the timeout is raised immediately. Please note: it is not POSIX time but a time with undefined starting base, e.g. the time of the system power on. """ if self._state == _State.EXIT: raise RuntimeError("cannot reschedule after exit from context manager") if self._state == _State.TIMEOUT: raise RuntimeError("cannot reschedule expired timeout") if self._timeout_handler is not None: self._timeout_handler.cancel() self._deadline = deadline if self._state != _State.INIT: self._reschedule() def _reschedule(self) -> None: assert self._state == _State.ENTER deadline = self._deadline if deadline is None: return now = self._loop.time() if self._timeout_handler is not None: self._timeout_handler.cancel() self._task = asyncio.current_task() if deadline <= now: self._timeout_handler = self._loop.call_soon(self._on_timeout) else: self._timeout_handler = self._loop.call_at(deadline, self._on_timeout) def _do_enter(self) -> None: if self._state != _State.INIT: raise RuntimeError(f"invalid state {self._state.value}") self._state = _State.ENTER self._reschedule() def _do_exit(self, exc_type: Optional[Type[BaseException]]) -> None: if exc_type is asyncio.CancelledError and self._state == _State.TIMEOUT: assert self._task is not None _uncancel_task(self._task) self._timeout_handler = None self._task = None raise asyncio.TimeoutError # timeout has not expired self._state = _State.EXIT self._reject() return None def _on_timeout(self) -> None: assert self._task is not None self._task.cancel() self._state = _State.TIMEOUT # drop the reference early self._timeout_handler = None async-timeout-4.0.3/async_timeout/py.typed000066400000000000000000000000141446520607400207040ustar00rootroot00000000000000Placeholder async-timeout-4.0.3/pyproject.toml000066400000000000000000000005111446520607400172400ustar00rootroot00000000000000[build-system] requires = [ "setuptools>=45", "wheel", ] build-backend = "setuptools.build_meta" [tool.towncrier] package = "async_timeout" filename = "CHANGES.rst" directory = "CHANGES/" title_format = "{version} ({project_date})" issue_format = "`#{issue} `_" async-timeout-4.0.3/requirements.txt000066400000000000000000000002471446520607400176160ustar00rootroot00000000000000-e . build==0.10.0 docutils==0.20.1 mypy==1.4.1; implementation_name=="cpython" pre-commit==2.21.0 pytest==7.4.0 pytest-asyncio==0.21.1 pytest-cov==4.1.0 twine==4.0.2 async-timeout-4.0.3/setup.cfg000066400000000000000000000034771446520607400161630ustar00rootroot00000000000000[metadata] name = async-timeout version = attr: async_timeout.__version__ url = https://github.com/aio-libs/async-timeout project_urls = Chat: Gitter = https://gitter.im/aio-libs/Lobby CI: GitHub Actions = https://github.com/aio-libs/async-timeout/actions Coverage: codecov = https://codecov.io/github/aio-libs/async-timeout GitHub: issues = https://github.com/aio-libs/async-timeout/issues GitHub: repo = https://github.com/aio-libs/async-timeout description = Timeout context manager for asyncio programs long_description = file: README.rst long_description_content_type = text/x-rst author = Andrew Svetlov author_email = andrew.svetlov@gmail.com license = Apache 2 license_files = LICENSE classifiers = Development Status :: 5 - Production/Stable Topic :: Software Development :: Libraries Framework :: AsyncIO Intended Audience :: Developers License :: OSI Approved :: Apache Software License Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 [options] python_requires = >=3.7 packages = async_timeout zip_safe = True include_package_data = True install_requires = typing_extensions>=3.6.5; python_version < "3.8" [flake8] exclude = .git,.env,__pycache__,.eggs max-line-length = 88 ignore = N801,N802,N803,E252,W503,E133,E203 [isort] line_length=88 include_trailing_comma=True multi_line_output=3 force_grid_wrap=0 combine_as_imports=True lines_after_imports=2 [tool:pytest] addopts= --cov=async_timeout --cov-report=term --cov-report=html --cov-branch asyncio_mode = strict [mypy-pytest] ignore_missing_imports = true async-timeout-4.0.3/setup.py000066400000000000000000000000471446520607400160420ustar00rootroot00000000000000from setuptools import setup setup() async-timeout-4.0.3/tests/000077500000000000000000000000001446520607400154715ustar00rootroot00000000000000async-timeout-4.0.3/tests/test_timeout.py000066400000000000000000000230101446520607400205640ustar00rootroot00000000000000import asyncio import sys import time from functools import wraps from typing import Any, Callable, List, TypeVar import pytest from async_timeout import timeout, timeout_at _Func = TypeVar("_Func", bound=Callable[..., Any]) def log_func(func: _Func, msg: str, call_order: List[str]) -> _Func: """Simple wrapper to add a log to call_order when the function is called.""" @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> Any: # type: ignore[misc] call_order.append(msg) return func(*args, **kwargs) return wrapper # type: ignore[return-value] @pytest.mark.asyncio async def test_timeout() -> None: canceled_raised = False async def long_running_task() -> None: try: await asyncio.sleep(10) except asyncio.CancelledError: nonlocal canceled_raised canceled_raised = True raise with pytest.raises(asyncio.TimeoutError): async with timeout(0.01) as t: await long_running_task() assert t._loop is asyncio.get_event_loop() assert canceled_raised, "CancelledError was not raised" if sys.version_info >= (3, 11): task = asyncio.current_task() assert task is not None assert not task.cancelling() @pytest.mark.asyncio async def test_timeout_finish_in_time() -> None: async def long_running_task() -> str: await asyncio.sleep(0.01) return "done" # timeout should be long enough to work even on slow bisy test boxes async with timeout(0.5): resp = await long_running_task() assert resp == "done" @pytest.mark.asyncio async def test_timeout_disable() -> None: async def long_running_task() -> str: await asyncio.sleep(0.1) return "done" loop = asyncio.get_event_loop() t0 = loop.time() async with timeout(None): resp = await long_running_task() assert resp == "done" dt = loop.time() - t0 assert 0.09 < dt < 0.3, dt @pytest.mark.asyncio async def test_timeout_is_none_no_schedule() -> None: async with timeout(None) as cm: assert cm._timeout_handler is None assert cm.deadline is None def test_timeout_no_loop() -> None: with pytest.raises(RuntimeError, match="no running event loop"): timeout(None) @pytest.mark.asyncio async def test_timeout_zero() -> None: with pytest.raises(asyncio.TimeoutError): async with timeout(0): await asyncio.sleep(10) @pytest.mark.asyncio async def test_timeout_not_relevant_exception() -> None: await asyncio.sleep(0) with pytest.raises(KeyError): async with timeout(0.1): raise KeyError @pytest.mark.asyncio async def test_timeout_canceled_error_is_not_converted_to_timeout() -> None: await asyncio.sleep(0) with pytest.raises(asyncio.CancelledError): async with timeout(0.001): raise asyncio.CancelledError @pytest.mark.asyncio async def test_timeout_blocking_loop() -> None: async def long_running_task() -> str: time.sleep(0.1) return "done" async with timeout(0.01): result = await long_running_task() assert result == "done" @pytest.mark.asyncio async def test_for_race_conditions() -> None: loop = asyncio.get_event_loop() fut = loop.create_future() loop.call_later(0.1, fut.set_result, "done") async with timeout(0.5): resp = await fut assert resp == "done" @pytest.mark.asyncio async def test_timeout_time() -> None: foo_running = None loop = asyncio.get_event_loop() start = loop.time() with pytest.raises(asyncio.TimeoutError): async with timeout(0.1): foo_running = True try: await asyncio.sleep(0.2) finally: foo_running = False dt = loop.time() - start assert 0.09 < dt < 0.3 assert not foo_running @pytest.mark.asyncio async def test_outer_coro_is_not_cancelled() -> None: has_timeout = False async def outer() -> None: nonlocal has_timeout try: async with timeout(0.001): await asyncio.sleep(1) except asyncio.TimeoutError: has_timeout = True task = asyncio.ensure_future(outer()) await task assert has_timeout assert not task.cancelled() if sys.version_info >= (3, 11): assert not task.cancelling() assert task.done() @pytest.mark.asyncio async def test_cancel_outer_coro() -> None: loop = asyncio.get_event_loop() fut = loop.create_future() async def outer() -> None: fut.set_result(None) await asyncio.sleep(1) task = asyncio.ensure_future(outer()) await fut task.cancel() with pytest.raises(asyncio.CancelledError): await task assert task.cancelled() assert task.done() @pytest.mark.asyncio async def test_timeout_suppress_exception_chain() -> None: with pytest.raises(asyncio.TimeoutError) as ctx: async with timeout(0.01): await asyncio.sleep(10) assert not ctx.value.__suppress_context__ @pytest.mark.asyncio async def test_timeout_expired() -> None: with pytest.raises(asyncio.TimeoutError): async with timeout(0.01) as cm: await asyncio.sleep(10) assert cm.expired @pytest.mark.asyncio async def test_timeout_inner_timeout_error() -> None: with pytest.raises(asyncio.TimeoutError): async with timeout(0.01) as cm: raise asyncio.TimeoutError assert not cm.expired @pytest.mark.asyncio async def test_timeout_inner_other_error() -> None: class MyError(RuntimeError): pass with pytest.raises(MyError): async with timeout(0.01) as cm: raise MyError assert not cm.expired @pytest.mark.asyncio async def test_timeout_at() -> None: loop = asyncio.get_event_loop() with pytest.raises(asyncio.TimeoutError): now = loop.time() async with timeout_at(now + 0.01) as cm: await asyncio.sleep(10) assert cm.expired @pytest.mark.asyncio async def test_timeout_at_not_fired() -> None: loop = asyncio.get_event_loop() now = loop.time() async with timeout_at(now + 1) as cm: await asyncio.sleep(0) assert not cm.expired @pytest.mark.asyncio async def test_expired_after_rejecting() -> None: t = timeout(10) assert not t.expired t.reject() assert not t.expired @pytest.mark.asyncio async def test_reject_finished() -> None: async with timeout(10) as t: await asyncio.sleep(0) assert not t.expired with pytest.raises(RuntimeError, match="invalid state EXIT"): t.reject() @pytest.mark.asyncio async def test_expired_after_timeout() -> None: with pytest.raises(asyncio.TimeoutError): async with timeout(0.01) as t: assert not t.expired await asyncio.sleep(10) assert t.expired @pytest.mark.asyncio async def test_deadline() -> None: loop = asyncio.get_event_loop() t0 = loop.time() async with timeout(1) as cm: t1 = loop.time() assert cm.deadline is not None assert t0 + 1 <= cm.deadline <= t1 + 1 @pytest.mark.asyncio async def test_async_timeout() -> None: with pytest.raises(asyncio.TimeoutError): async with timeout(0.01) as cm: await asyncio.sleep(10) assert cm.expired @pytest.mark.asyncio async def test_async_no_timeout() -> None: async with timeout(1) as cm: await asyncio.sleep(0) assert not cm.expired @pytest.mark.asyncio async def test_shift() -> None: loop = asyncio.get_event_loop() t0 = loop.time() async with timeout(1) as cm: t1 = loop.time() assert cm.deadline is not None assert t0 + 1 <= cm.deadline <= t1 + 1 cm.shift(1) assert t0 + 2 <= cm.deadline <= t0 + 2.1 @pytest.mark.asyncio async def test_shift_nonscheduled() -> None: async with timeout(None) as cm: with pytest.raises( RuntimeError, match="cannot shift timeout if deadline is not scheduled", ): cm.shift(1) @pytest.mark.asyncio async def test_shift_negative_expired() -> None: async with timeout(1) as cm: with pytest.raises(asyncio.CancelledError): cm.shift(-1) await asyncio.sleep(10) @pytest.mark.asyncio async def test_shift_by_expired() -> None: async with timeout(0.001) as cm: with pytest.raises(asyncio.CancelledError): await asyncio.sleep(10) with pytest.raises(RuntimeError, match="cannot reschedule expired timeout"): cm.shift(10) @pytest.mark.asyncio async def test_shift_to_expired() -> None: loop = asyncio.get_event_loop() t0 = loop.time() async with timeout_at(t0 + 0.001) as cm: with pytest.raises(asyncio.CancelledError): await asyncio.sleep(10) with pytest.raises(RuntimeError, match="cannot reschedule expired timeout"): cm.update(t0 + 10) @pytest.mark.asyncio async def test_shift_by_after_cm_exit() -> None: async with timeout(1) as cm: await asyncio.sleep(0) with pytest.raises( RuntimeError, match="cannot reschedule after exit from context manager" ): cm.shift(1) @pytest.mark.asyncio async def test_enter_twice() -> None: async with timeout(10) as t: await asyncio.sleep(0) with pytest.raises(RuntimeError, match="invalid state EXIT"): async with t: await asyncio.sleep(0) @pytest.mark.asyncio async def test_deprecated_with() -> None: with pytest.warns(DeprecationWarning): with timeout(1): await asyncio.sleep(0)