mirror of
https://github.com/jazzband/contextlib2.git
synced 2026-03-22 16:40:25 +00:00
Compare commits
No commits in common. "master" and "21.6.0" have entirely different histories.
36 changed files with 748 additions and 2495 deletions
19
.github/workflows/test.yml
vendored
19
.github/workflows/test.yml
vendored
|
|
@ -1,12 +1,6 @@
|
||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on:
|
on: [push, pull_request]
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- "**"
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -14,16 +8,13 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
max-parallel: 5
|
max-parallel: 5
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.8, 3.9, '3.10', 3.11, 3.12, 'pypy-3.10']
|
python-version: [3.6, 3.7, 3.8, 3.9, '3.10.0-beta - 3.10', 'pypy3']
|
||||||
|
|
||||||
# Check https://github.com/actions/action-versions/tree/main/config/actions
|
|
||||||
# for latest versions if the standard actions start emitting warnings
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
|
@ -33,7 +24,7 @@ jobs:
|
||||||
echo "::set-output name=dir::$(pip cache dir)"
|
echo "::set-output name=dir::$(pip cache dir)"
|
||||||
|
|
||||||
- name: Cache
|
- name: Cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pip-cache.outputs.dir }}
|
path: ${{ steps.pip-cache.outputs.dir }}
|
||||||
key:
|
key:
|
||||||
|
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -11,7 +11,3 @@ MANIFEST
|
||||||
.coverage
|
.coverage
|
||||||
coverage.xml
|
coverage.xml
|
||||||
htmlcov/
|
htmlcov/
|
||||||
|
|
||||||
# Patching output files
|
|
||||||
*.orig
|
|
||||||
*.rej
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
repos:
|
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
|
||||||
rev: v1.10.0
|
|
||||||
hooks:
|
|
||||||
- id: python-check-blanket-noqa
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v6.0.0
|
|
||||||
hooks:
|
|
||||||
- id: check-merge-conflict
|
|
||||||
- id: check-yaml
|
|
||||||
|
|
||||||
ci:
|
|
||||||
autoupdate_schedule: quarterly
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# Read the Docs configuration file for Sphinx projects
|
|
||||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
||||||
|
|
||||||
# Required
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
# Set the OS, Python version and other tools you might need
|
|
||||||
build:
|
|
||||||
os: ubuntu-22.04
|
|
||||||
tools:
|
|
||||||
python: "3.12"
|
|
||||||
# You can also specify other tool versions:
|
|
||||||
# nodejs: "20"
|
|
||||||
# rust: "1.70"
|
|
||||||
# golang: "1.20"
|
|
||||||
|
|
||||||
# Build documentation in the "docs/" directory with Sphinx
|
|
||||||
sphinx:
|
|
||||||
configuration: docs/conf.py
|
|
||||||
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
|
|
||||||
# builder: "dirhtml"
|
|
||||||
# Fail on all warnings to avoid broken references
|
|
||||||
# fail_on_warning: true
|
|
||||||
|
|
||||||
# Optionally build your docs in additional formats such as PDF and ePub
|
|
||||||
# formats:
|
|
||||||
# - pdf
|
|
||||||
# - epub
|
|
||||||
|
|
||||||
# Optional but recommended, declare the Python requirements required
|
|
||||||
# to build your documentation
|
|
||||||
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
|
||||||
python:
|
|
||||||
install:
|
|
||||||
- requirements: docs/requirements.txt
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
# Code of Conduct
|
|
||||||
|
|
||||||
As contributors and maintainers of the Jazzband projects, and in the interest of
|
|
||||||
fostering an open and welcoming community, we pledge to respect all people who
|
|
||||||
contribute through reporting issues, posting feature requests, updating documentation,
|
|
||||||
submitting pull requests or patches, and other activities.
|
|
||||||
|
|
||||||
We are committed to making participation in the Jazzband a harassment-free experience
|
|
||||||
for everyone, regardless of the level of experience, gender, gender identity and
|
|
||||||
expression, sexual orientation, disability, personal appearance, body size, race,
|
|
||||||
ethnicity, age, religion, or nationality.
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
|
||||||
|
|
||||||
- The use of sexualized language or imagery
|
|
||||||
- Personal attacks
|
|
||||||
- Trolling or insulting/derogatory comments
|
|
||||||
- Public or private harassment
|
|
||||||
- Publishing other's private information, such as physical or electronic addresses,
|
|
||||||
without explicit permission
|
|
||||||
- Other unethical or unprofessional conduct
|
|
||||||
|
|
||||||
The Jazzband roadies have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are not
|
|
||||||
aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
|
|
||||||
for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
By adopting this Code of Conduct, the roadies commit themselves to fairly and
|
|
||||||
consistently applying these principles to every aspect of managing the jazzband
|
|
||||||
projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
|
|
||||||
removed from the Jazzband roadies.
|
|
||||||
|
|
||||||
This code of conduct applies both within project spaces and in public spaces when an
|
|
||||||
individual is representing the project or its community.
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
|
||||||
contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
|
|
||||||
investigated and will result in a response that is deemed necessary and appropriate to
|
|
||||||
the circumstances. Roadies are obligated to maintain confidentiality with regard to the
|
|
||||||
reporter of an incident.
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
|
|
||||||
1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
|
|
||||||
|
|
||||||
[homepage]: https://contributor-covenant.org
|
|
||||||
[version]: https://contributor-covenant.org/version/1/3/0/
|
|
||||||
|
|
@ -2,4 +2,4 @@ include *.py *.cfg *.txt *.rst *.md *.ini MANIFEST.in
|
||||||
recursive-include contextlib2 *.py *.pyi py.typed
|
recursive-include contextlib2 *.py *.pyi py.typed
|
||||||
recursive-include docs *.rst *.py make.bat Makefile
|
recursive-include docs *.rst *.py make.bat Makefile
|
||||||
recursive-include test *.py
|
recursive-include test *.py
|
||||||
recursive-include dev *.patch *.allowlist *.sh
|
recursive-include dev *.patch
|
||||||
|
|
|
||||||
36
NEWS.rst
36
NEWS.rst
|
|
@ -1,40 +1,6 @@
|
||||||
Release History
|
Release History
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
24.6.0 (2024-06-??)
|
|
||||||
^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
* To allow the use of positional-only argument syntax, the minimum supported
|
|
||||||
Python version is now Python 3.8.
|
|
||||||
* Synchronised with the Python 3.12.3 (and 3.13.0) version of contextlib
|
|
||||||
(`#12 <https://github.com/jazzband/contextlib2/issues/12>`__), making the
|
|
||||||
following new features available on Python 3.8+:
|
|
||||||
|
|
||||||
* :class:`chdir` (added in Python 3.11)
|
|
||||||
* :func:`suppress` filters the contents of ``BaseExceptionGroup`` (Python 3.12)
|
|
||||||
* improved handling of :class:`StopIteration` subclasses (Python 3.11)
|
|
||||||
* The exception thrown by :meth:`ExitStack.enter_context` and
|
|
||||||
:meth:`AsyncExitStack.enter_async_context` when the given object does not
|
|
||||||
implement the relevant context management protocol is now version-dependent
|
|
||||||
(:class:`TypeError` on 3.11+, :class:`AttributeError` on earlier versions).
|
|
||||||
This provides consistency with the ``with`` and ``async with`` behaviour on
|
|
||||||
the corresponding versions.
|
|
||||||
* No longer needed object references are now released more promptly
|
|
||||||
* Update ``mypy stubtest`` to work with recent mypy versions (mypy 1.8.0 tested)
|
|
||||||
(`#54 <https://github.com/jazzband/contextlib2/issues/54>`__)
|
|
||||||
* The ``dev/mypy.allowlist`` file needed for the ``mypy stubtest`` step in the
|
|
||||||
``tox`` test configuration is now included in the published sdist
|
|
||||||
(`#53 <https://github.com/jazzband/contextlib2/issues/53>`__)
|
|
||||||
* Type hints have been updated to include ``nullcontext`` (3.10 API added in
|
|
||||||
21.6.0) (`#41 <https://github.com/jazzband/contextlib2/issues/41>`__)
|
|
||||||
* Test suite updated to pass on Python 3.11 and 3.12 (21.6.0 works on these
|
|
||||||
versions, the test suite just failed due to no longer valid assumptions)
|
|
||||||
(`#51 <https://github.com/jazzband/contextlib2/issues/51>`__)
|
|
||||||
* Updates to the default compatibility testing matrix:
|
|
||||||
|
|
||||||
* Added: CPython 3.11, CPython 3.12
|
|
||||||
* Dropped: CPython 3.6, CPython 3.7
|
|
||||||
|
|
||||||
21.6.0 (2021-06-27)
|
21.6.0 (2021-06-27)
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
@ -98,7 +64,7 @@ Release History
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
* Thanks to the welcome efforts of Jannis Leidel, contextlib2 is now a
|
* Thanks to the welcome efforts of Jannis Leidel, contextlib2 is now a
|
||||||
[Jazzband](https://jazzband.co/) project! This means that I (Alyssa Coghlan)
|
[Jazzband](https://jazzband.co/) project! This means that I (Nick Coghlan)
|
||||||
am no longer a single point of failure for backports of future contextlib
|
am no longer a single point of failure for backports of future contextlib
|
||||||
updates to earlier Python versions.
|
updates to earlier Python versions.
|
||||||
|
|
||||||
|
|
|
||||||
40
README.rst
40
README.rst
|
|
@ -34,7 +34,7 @@ The one exception is the included type hints file, which comes from the
|
||||||
Development
|
Development
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
``contextlib2`` has no runtime dependencies, but requires ``setuptools`` and
|
contextlib2 has no runtime dependencies, but requires ``setuptools`` and
|
||||||
``wheel`` at build time to generate universal wheel archives.
|
``wheel`` at build time to generate universal wheel archives.
|
||||||
|
|
||||||
Local testing is a matter of running::
|
Local testing is a matter of running::
|
||||||
|
|
@ -49,20 +49,20 @@ You can test against multiple versions of Python with
|
||||||
|
|
||||||
Versions currently tested in both tox and GitHub Actions are:
|
Versions currently tested in both tox and GitHub Actions are:
|
||||||
|
|
||||||
|
* CPython 3.6
|
||||||
|
* CPython 3.7
|
||||||
* CPython 3.8
|
* CPython 3.8
|
||||||
* CPython 3.9
|
* CPython 3.9
|
||||||
* CPython 3.10
|
* CPython 3.10
|
||||||
* CPython 3.11
|
* PyPy3
|
||||||
* CPython 3.12
|
|
||||||
* PyPy3 (specifically 3.10 in GitHub Actions)
|
|
||||||
|
|
||||||
Updating to a new stdlib reference version
|
Updating to a new stdlib reference version
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
As of Python 3.12.3, 4 files needed to be copied from the CPython reference
|
As of Python 3.10, 4 files needed to be copied from the CPython reference
|
||||||
implementation to contextlib2:
|
implementation to contextlib2:
|
||||||
|
|
||||||
* ``Doc/library/contextlib.rst`` -> ``docs/contextlib2.rst``
|
* ``Doc/contextlib.rst`` -> ``docs/contextlib2.rst``
|
||||||
* ``Lib/contextlib.py`` -> ``contextlib2/__init__.py``
|
* ``Lib/contextlib.py`` -> ``contextlib2/__init__.py``
|
||||||
* ``Lib/test/test_contextlib.py`` -> ``test/test_contextlib.py``
|
* ``Lib/test/test_contextlib.py`` -> ``test/test_contextlib.py``
|
||||||
* ``Lib/test/test_contextlib_async.py`` -> ``test/test_contextlib_async.py``
|
* ``Lib/test/test_contextlib_async.py`` -> ``test/test_contextlib_async.py``
|
||||||
|
|
@ -72,31 +72,19 @@ retrieved from the ``typeshed`` project::
|
||||||
|
|
||||||
wget https://raw.githubusercontent.com/python/typeshed/master/stdlib/contextlib.pyi
|
wget https://raw.githubusercontent.com/python/typeshed/master/stdlib/contextlib.pyi
|
||||||
|
|
||||||
|
For the 3.10 sync, the only changes needed to the test files were to import from
|
||||||
|
``contextlib2`` rather than ``contextlib``. The test directory is laid out so
|
||||||
|
that the test suite's imports from ``test.support`` work the same way they do in
|
||||||
|
the main CPython test suite.
|
||||||
|
|
||||||
The following patch files are saved in the ``dev`` directory:
|
The following patch files are saved in the ``dev`` directory:
|
||||||
|
|
||||||
* changes to ``contextlib2/__init__.py`` to get it to run on the older
|
* changes made to ``contextlib2/__init__.py`` to get it to run on the older
|
||||||
versions (and to add back in the deprecated APIs that never graduated to
|
versions (and to add back in the deprecated APIs that never graduated to
|
||||||
the standard library version)
|
the standard library version)
|
||||||
* changes to ``test/test_contextlib.py`` and ``test/test_contextlib_async.py``
|
* changes made to ``contextlib2/__init__.pyi`` to make the Python version
|
||||||
to get them to run on the older versions
|
|
||||||
* changes to ``contextlib2/__init__.pyi`` to make the Python version
|
|
||||||
guards unconditional (since the ``contextlib2`` API is the same on all
|
guards unconditional (since the ``contextlib2`` API is the same on all
|
||||||
supported versions)
|
supported versions)
|
||||||
* changes to ``docs/contextlib2.rst`` to use ``contextlib2`` version
|
* changes made to ``docs/contextlib2.rst`` to use ``contextlib2`` version
|
||||||
numbers in the version added/changed notes and to integrate the module
|
numbers in the version added/changed notes and to integrate the module
|
||||||
documentation with the rest of the project documentation
|
documentation with the rest of the project documentation
|
||||||
|
|
||||||
When the upstream changes between releases are minor, these patch files may be
|
|
||||||
used directly to reapply the ``contextlib2`` specific changes after syncing a
|
|
||||||
new version. Even when the patches do not apply cleanly, they're still a useful
|
|
||||||
guide as to the changes that are needed to restore compatibility with older
|
|
||||||
Python versions and make any other ``contextlib2`` specific updates.
|
|
||||||
|
|
||||||
The test directory is laid out so that the test suite's imports from
|
|
||||||
``test.support`` work the same way as they do in the main CPython test suite.
|
|
||||||
These files are selective copies rather than complete ones as the ``contextlib``
|
|
||||||
tests only need a tiny fraction of the features available in the real
|
|
||||||
``test.support`` module.
|
|
||||||
|
|
||||||
The ``dev/sync_from_cpython.sh`` and ``dev/save_diff_snapshot.sh`` scripts
|
|
||||||
automate some of the steps in the sync process.
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
24.6.0rc1
|
21.6.0
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,32 @@
|
||||||
"""Utilities for with-statement contexts. See PEP 343."""
|
"""contextlib2 - backports and enhancements to the contextlib module"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
import _collections_abc
|
import _collections_abc
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from types import MethodType
|
from types import MethodType
|
||||||
|
|
||||||
# Python 3.8 compatibility: GenericAlias may not be defined
|
# Python 3.6/3.7/3.8 compatibility: GenericAlias may not be defined
|
||||||
try:
|
try:
|
||||||
from types import GenericAlias
|
from types import GenericAlias
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# If the real GenericAlias type doesn't exist, __class_getitem__ won't be used,
|
# If the real GenericAlias type doesn't exist, __class_getitem__ won't be used,
|
||||||
# so the fallback placeholder doesn't need to provide any meaningful behaviour
|
# so the fallback placeholder doesn't need to provide any meaningful behaviour
|
||||||
# (typecheckers may still be unhappy, but for that problem the answer is
|
|
||||||
# "use a newer Python version with better typechecking support")
|
|
||||||
class GenericAlias:
|
class GenericAlias:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Python 3.10 and earlier compatibility: BaseExceptionGroup may not be defined
|
|
||||||
try:
|
|
||||||
BaseExceptionGroup
|
|
||||||
except NameError:
|
|
||||||
# If the real BaseExceptionGroup type doesn't exist, it will never actually
|
|
||||||
# be raised. This means the fallback placeholder doesn't need to provide
|
|
||||||
# any meaningful behaviour, it just needs to be compatible with 'issubclass'
|
|
||||||
class BaseExceptionGroup(BaseException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Python 3.9 and earlier compatibility: anext may not be defined
|
|
||||||
try:
|
|
||||||
anext
|
|
||||||
except NameError:
|
|
||||||
def anext(obj, /):
|
|
||||||
return obj.__anext__()
|
|
||||||
|
|
||||||
# Python 3.11+ behaviour consistency: replace AttributeError with TypeError
|
|
||||||
if sys.version_info >= (3, 11):
|
|
||||||
# enter_context() and enter_async_context() follow the change in the
|
|
||||||
# exception type raised by with statements and async with statements
|
|
||||||
_CL2_ERROR_TO_CONVERT = AttributeError
|
|
||||||
else:
|
|
||||||
# On older versions, raise AttributeError without any changes
|
|
||||||
class _CL2_ERROR_TO_CONVERT(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
|
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
|
||||||
"AbstractContextManager", "AbstractAsyncContextManager",
|
"AbstractContextManager", "AbstractAsyncContextManager",
|
||||||
"AsyncExitStack", "ContextDecorator", "ExitStack",
|
"AsyncExitStack", "ContextDecorator", "ExitStack",
|
||||||
"redirect_stdout", "redirect_stderr", "suppress", "aclosing",
|
"redirect_stdout", "redirect_stderr", "suppress", "aclosing"]
|
||||||
"chdir"]
|
|
||||||
|
|
||||||
|
# Backwards compatibility
|
||||||
|
__all__ += ["ContextStack"]
|
||||||
|
|
||||||
class AbstractContextManager(abc.ABC):
|
class AbstractContextManager(abc.ABC):
|
||||||
|
|
||||||
"""An abstract base class for context managers."""
|
"""An abstract base class for context managers."""
|
||||||
|
|
||||||
__class_getitem__ = classmethod(GenericAlias)
|
__class_getitem__ = classmethod(GenericAlias)
|
||||||
|
|
@ -114,7 +86,6 @@ class ContextDecorator(object):
|
||||||
DEPRECATED: refresh_cm was never added to the standard library's
|
DEPRECATED: refresh_cm was never added to the standard library's
|
||||||
ContextDecorator API
|
ContextDecorator API
|
||||||
"""
|
"""
|
||||||
import warnings # Only import if needed for the deprecation warning
|
|
||||||
warnings.warn("refresh_cm was never added to the standard library",
|
warnings.warn("refresh_cm was never added to the standard library",
|
||||||
DeprecationWarning)
|
DeprecationWarning)
|
||||||
return self._recreate_cm()
|
return self._recreate_cm()
|
||||||
|
|
@ -172,20 +143,18 @@ class _GeneratorContextManagerBase:
|
||||||
# for the class instead.
|
# for the class instead.
|
||||||
# See http://bugs.python.org/issue19404 for more details.
|
# See http://bugs.python.org/issue19404 for more details.
|
||||||
|
|
||||||
|
|
||||||
|
class _GeneratorContextManager(_GeneratorContextManagerBase,
|
||||||
|
AbstractContextManager,
|
||||||
|
ContextDecorator):
|
||||||
|
"""Helper for @contextmanager decorator."""
|
||||||
|
|
||||||
def _recreate_cm(self):
|
def _recreate_cm(self):
|
||||||
# _GCMB instances are one-shot context managers, so the
|
# _GCM instances are one-shot context managers, so the
|
||||||
# CM must be recreated each time a decorated function is
|
# CM must be recreated each time a decorated function is
|
||||||
# called
|
# called
|
||||||
return self.__class__(self.func, self.args, self.kwds)
|
return self.__class__(self.func, self.args, self.kwds)
|
||||||
|
|
||||||
|
|
||||||
class _GeneratorContextManager(
|
|
||||||
_GeneratorContextManagerBase,
|
|
||||||
AbstractContextManager,
|
|
||||||
ContextDecorator,
|
|
||||||
):
|
|
||||||
"""Helper for @contextmanager decorator."""
|
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
# do not keep args and kwds alive unnecessarily
|
# do not keep args and kwds alive unnecessarily
|
||||||
# they are only needed for recreation, which is not possible anymore
|
# they are only needed for recreation, which is not possible anymore
|
||||||
|
|
@ -195,24 +164,21 @@ class _GeneratorContextManager(
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise RuntimeError("generator didn't yield") from None
|
raise RuntimeError("generator didn't yield") from None
|
||||||
|
|
||||||
def __exit__(self, typ, value, traceback):
|
def __exit__(self, type, value, traceback):
|
||||||
if typ is None:
|
if type is None:
|
||||||
try:
|
try:
|
||||||
next(self.gen)
|
next(self.gen)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
try:
|
raise RuntimeError("generator didn't stop")
|
||||||
raise RuntimeError("generator didn't stop")
|
|
||||||
finally:
|
|
||||||
self.gen.close()
|
|
||||||
else:
|
else:
|
||||||
if value is None:
|
if value is None:
|
||||||
# Need to force instantiation so we can reliably
|
# Need to force instantiation so we can reliably
|
||||||
# tell if we get the same exception back
|
# tell if we get the same exception back
|
||||||
value = typ()
|
value = type()
|
||||||
try:
|
try:
|
||||||
self.gen.throw(value)
|
self.gen.throw(type, value, traceback)
|
||||||
except StopIteration as exc:
|
except StopIteration as exc:
|
||||||
# Suppress StopIteration *unless* it's the same exception that
|
# Suppress StopIteration *unless* it's the same exception that
|
||||||
# was passed to throw(). This prevents a StopIteration
|
# was passed to throw(). This prevents a StopIteration
|
||||||
|
|
@ -221,7 +187,68 @@ class _GeneratorContextManager(
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
# Don't re-raise the passed in exception. (issue27122)
|
# Don't re-raise the passed in exception. (issue27122)
|
||||||
if exc is value:
|
if exc is value:
|
||||||
exc.__traceback__ = traceback
|
return False
|
||||||
|
# Likewise, avoid suppressing if a StopIteration exception
|
||||||
|
# was passed to throw() and later wrapped into a RuntimeError
|
||||||
|
# (see PEP 479).
|
||||||
|
if type is StopIteration and exc.__cause__ is value:
|
||||||
|
return False
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
# only re-raise if it's *not* the exception that was
|
||||||
|
# passed to throw(), because __exit__() must not raise
|
||||||
|
# an exception unless __exit__() itself failed. But throw()
|
||||||
|
# has to raise the exception to signal propagation, so this
|
||||||
|
# fixes the impedance mismatch between the throw() protocol
|
||||||
|
# and the __exit__() protocol.
|
||||||
|
#
|
||||||
|
# This cannot use 'except BaseException as exc' (as in the
|
||||||
|
# async implementation) to maintain compatibility with
|
||||||
|
# Python 2, where old-style class exceptions are not caught
|
||||||
|
# by 'except BaseException'.
|
||||||
|
if sys.exc_info()[1] is value:
|
||||||
|
return False
|
||||||
|
raise
|
||||||
|
raise RuntimeError("generator didn't stop after throw()")
|
||||||
|
|
||||||
|
|
||||||
|
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
|
||||||
|
AbstractAsyncContextManager,
|
||||||
|
AsyncContextDecorator):
|
||||||
|
"""Helper for @asynccontextmanager."""
|
||||||
|
|
||||||
|
def _recreate_cm(self):
|
||||||
|
# _AGCM instances are one-shot context managers, so the
|
||||||
|
# ACM must be recreated each time a decorated function is
|
||||||
|
# called
|
||||||
|
return self.__class__(self.func, self.args, self.kwds)
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
try:
|
||||||
|
return await self.gen.__anext__()
|
||||||
|
except StopAsyncIteration:
|
||||||
|
raise RuntimeError("generator didn't yield") from None
|
||||||
|
|
||||||
|
async def __aexit__(self, typ, value, traceback):
|
||||||
|
if typ is None:
|
||||||
|
try:
|
||||||
|
await self.gen.__anext__()
|
||||||
|
except StopAsyncIteration:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise RuntimeError("generator didn't stop")
|
||||||
|
else:
|
||||||
|
if value is None:
|
||||||
|
value = typ()
|
||||||
|
# See _GeneratorContextManager.__exit__ for comments on subtleties
|
||||||
|
# in this implementation
|
||||||
|
try:
|
||||||
|
await self.gen.athrow(typ, value, traceback)
|
||||||
|
raise RuntimeError("generator didn't stop after athrow()")
|
||||||
|
except StopAsyncIteration as exc:
|
||||||
|
return exc is not value
|
||||||
|
except RuntimeError as exc:
|
||||||
|
if exc is value:
|
||||||
return False
|
return False
|
||||||
# Avoid suppressing if a StopIteration exception
|
# Avoid suppressing if a StopIteration exception
|
||||||
# was passed to throw() and later wrapped into a RuntimeError
|
# was passed to throw() and later wrapped into a RuntimeError
|
||||||
|
|
@ -229,101 +256,13 @@ class _GeneratorContextManager(
|
||||||
# have this behavior). But do this only if the exception wrapped
|
# have this behavior). But do this only if the exception wrapped
|
||||||
# by the RuntimeError is actually Stop(Async)Iteration (see
|
# by the RuntimeError is actually Stop(Async)Iteration (see
|
||||||
# issue29692).
|
# issue29692).
|
||||||
if (
|
if isinstance(value, (StopIteration, StopAsyncIteration)):
|
||||||
isinstance(value, StopIteration)
|
if exc.__cause__ is value:
|
||||||
and exc.__cause__ is value
|
return False
|
||||||
):
|
|
||||||
value.__traceback__ = traceback
|
|
||||||
return False
|
|
||||||
raise
|
raise
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
# only re-raise if it's *not* the exception that was
|
|
||||||
# passed to throw(), because __exit__() must not raise
|
|
||||||
# an exception unless __exit__() itself failed. But throw()
|
|
||||||
# has to raise the exception to signal propagation, so this
|
|
||||||
# fixes the impedance mismatch between the throw() protocol
|
|
||||||
# and the __exit__() protocol.
|
|
||||||
if exc is not value:
|
if exc is not value:
|
||||||
raise
|
raise
|
||||||
exc.__traceback__ = traceback
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
raise RuntimeError("generator didn't stop after throw()")
|
|
||||||
finally:
|
|
||||||
self.gen.close()
|
|
||||||
|
|
||||||
class _AsyncGeneratorContextManager(
|
|
||||||
_GeneratorContextManagerBase,
|
|
||||||
AbstractAsyncContextManager,
|
|
||||||
AsyncContextDecorator,
|
|
||||||
):
|
|
||||||
"""Helper for @asynccontextmanager decorator."""
|
|
||||||
|
|
||||||
async def __aenter__(self):
|
|
||||||
# do not keep args and kwds alive unnecessarily
|
|
||||||
# they are only needed for recreation, which is not possible anymore
|
|
||||||
del self.args, self.kwds, self.func
|
|
||||||
try:
|
|
||||||
return await anext(self.gen)
|
|
||||||
except StopAsyncIteration:
|
|
||||||
raise RuntimeError("generator didn't yield") from None
|
|
||||||
|
|
||||||
async def __aexit__(self, typ, value, traceback):
|
|
||||||
if typ is None:
|
|
||||||
try:
|
|
||||||
await anext(self.gen)
|
|
||||||
except StopAsyncIteration:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
raise RuntimeError("generator didn't stop")
|
|
||||||
finally:
|
|
||||||
await self.gen.aclose()
|
|
||||||
else:
|
|
||||||
if value is None:
|
|
||||||
# Need to force instantiation so we can reliably
|
|
||||||
# tell if we get the same exception back
|
|
||||||
value = typ()
|
|
||||||
try:
|
|
||||||
await self.gen.athrow(value)
|
|
||||||
except StopAsyncIteration as exc:
|
|
||||||
# Suppress StopIteration *unless* it's the same exception that
|
|
||||||
# was passed to throw(). This prevents a StopIteration
|
|
||||||
# raised inside the "with" statement from being suppressed.
|
|
||||||
return exc is not value
|
|
||||||
except RuntimeError as exc:
|
|
||||||
# Don't re-raise the passed in exception. (issue27122)
|
|
||||||
if exc is value:
|
|
||||||
exc.__traceback__ = traceback
|
|
||||||
return False
|
|
||||||
# Avoid suppressing if a Stop(Async)Iteration exception
|
|
||||||
# was passed to athrow() and later wrapped into a RuntimeError
|
|
||||||
# (see PEP 479 for sync generators; async generators also
|
|
||||||
# have this behavior). But do this only if the exception wrapped
|
|
||||||
# by the RuntimeError is actually Stop(Async)Iteration (see
|
|
||||||
# issue29692).
|
|
||||||
if (
|
|
||||||
isinstance(value, (StopIteration, StopAsyncIteration))
|
|
||||||
and exc.__cause__ is value
|
|
||||||
):
|
|
||||||
value.__traceback__ = traceback
|
|
||||||
return False
|
|
||||||
raise
|
|
||||||
except BaseException as exc:
|
|
||||||
# only re-raise if it's *not* the exception that was
|
|
||||||
# passed to throw(), because __exit__() must not raise
|
|
||||||
# an exception unless __exit__() itself failed. But throw()
|
|
||||||
# has to raise the exception to signal propagation, so this
|
|
||||||
# fixes the impedance mismatch between the throw() protocol
|
|
||||||
# and the __exit__() protocol.
|
|
||||||
if exc is not value:
|
|
||||||
raise
|
|
||||||
exc.__traceback__ = traceback
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
raise RuntimeError("generator didn't stop after athrow()")
|
|
||||||
finally:
|
|
||||||
await self.gen.aclose()
|
|
||||||
|
|
||||||
|
|
||||||
def contextmanager(func):
|
def contextmanager(func):
|
||||||
|
|
@ -510,16 +449,7 @@ class suppress(AbstractContextManager):
|
||||||
# exactly reproduce the limitations of the CPython interpreter.
|
# exactly reproduce the limitations of the CPython interpreter.
|
||||||
#
|
#
|
||||||
# See http://bugs.python.org/issue12029 for more details
|
# See http://bugs.python.org/issue12029 for more details
|
||||||
if exctype is None:
|
return exctype is not None and issubclass(exctype, self._exceptions)
|
||||||
return
|
|
||||||
if issubclass(exctype, self._exceptions):
|
|
||||||
return True
|
|
||||||
if issubclass(exctype, BaseExceptionGroup):
|
|
||||||
match, rest = excinst.split(self._exceptions)
|
|
||||||
if rest is None:
|
|
||||||
return True
|
|
||||||
raise rest
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class _BaseExitStack:
|
class _BaseExitStack:
|
||||||
|
|
@ -530,7 +460,9 @@ class _BaseExitStack:
|
||||||
return MethodType(cm_exit, cm)
|
return MethodType(cm_exit, cm)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_cb_wrapper(callback, /, *args, **kwds):
|
def _create_cb_wrapper(*args, **kwds):
|
||||||
|
# Python 3.6/3.7 compatibility: no native positional-only args syntax
|
||||||
|
callback, *args = args
|
||||||
def _exit_wrapper(exc_type, exc, tb):
|
def _exit_wrapper(exc_type, exc, tb):
|
||||||
callback(*args, **kwds)
|
callback(*args, **kwds)
|
||||||
return _exit_wrapper
|
return _exit_wrapper
|
||||||
|
|
@ -573,22 +505,24 @@ class _BaseExitStack:
|
||||||
"""
|
"""
|
||||||
# We look up the special methods on the type to match the with
|
# We look up the special methods on the type to match the with
|
||||||
# statement.
|
# statement.
|
||||||
cls = type(cm)
|
_cm_type = type(cm)
|
||||||
try:
|
_exit = _cm_type.__exit__
|
||||||
_enter = cls.__enter__
|
result = _cm_type.__enter__(cm)
|
||||||
_exit = cls.__exit__
|
|
||||||
except _CL2_ERROR_TO_CONVERT:
|
|
||||||
raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does "
|
|
||||||
f"not support the context manager protocol") from None
|
|
||||||
result = _enter(cm)
|
|
||||||
self._push_cm_exit(cm, _exit)
|
self._push_cm_exit(cm, _exit)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def callback(self, callback, /, *args, **kwds):
|
def callback(*args, **kwds):
|
||||||
"""Registers an arbitrary callback and arguments.
|
"""Registers an arbitrary callback and arguments.
|
||||||
|
|
||||||
Cannot suppress exceptions.
|
Cannot suppress exceptions.
|
||||||
"""
|
"""
|
||||||
|
# Python 3.6/3.7 compatibility: no native positional-only args syntax
|
||||||
|
try:
|
||||||
|
self, callback, *args = args
|
||||||
|
except ValueError as exc:
|
||||||
|
exc_details = str(exc).partition("(")[2]
|
||||||
|
msg = "Not enough positional arguments {}".format(exc_details)
|
||||||
|
raise TypeError(msg) from None
|
||||||
_exit_wrapper = self._create_cb_wrapper(callback, *args, **kwds)
|
_exit_wrapper = self._create_cb_wrapper(callback, *args, **kwds)
|
||||||
|
|
||||||
# We changed the signature, so using @wraps is not appropriate, but
|
# We changed the signature, so using @wraps is not appropriate, but
|
||||||
|
|
@ -631,10 +565,10 @@ class ExitStack(_BaseExitStack, AbstractContextManager):
|
||||||
# Context may not be correct, so find the end of the chain
|
# Context may not be correct, so find the end of the chain
|
||||||
while 1:
|
while 1:
|
||||||
exc_context = new_exc.__context__
|
exc_context = new_exc.__context__
|
||||||
if exc_context is None or exc_context is old_exc:
|
if exc_context is old_exc:
|
||||||
# Context is already set correctly (see issue 20317)
|
# Context is already set correctly (see issue 20317)
|
||||||
return
|
return
|
||||||
if exc_context is frame_exc:
|
if exc_context is None or exc_context is frame_exc:
|
||||||
break
|
break
|
||||||
new_exc = exc_context
|
new_exc = exc_context
|
||||||
# Change the end of the chain to point to the exception
|
# Change the end of the chain to point to the exception
|
||||||
|
|
@ -694,7 +628,9 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
||||||
return MethodType(cm_exit, cm)
|
return MethodType(cm_exit, cm)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_async_cb_wrapper(callback, /, *args, **kwds):
|
def _create_async_cb_wrapper(*args, **kwds):
|
||||||
|
# Python 3.6/3.7 compatibility: no native positional-only args syntax
|
||||||
|
callback, *args = args
|
||||||
async def _exit_wrapper(exc_type, exc, tb):
|
async def _exit_wrapper(exc_type, exc, tb):
|
||||||
await callback(*args, **kwds)
|
await callback(*args, **kwds)
|
||||||
return _exit_wrapper
|
return _exit_wrapper
|
||||||
|
|
@ -705,15 +641,9 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
||||||
If successful, also pushes its __aexit__ method as a callback and
|
If successful, also pushes its __aexit__ method as a callback and
|
||||||
returns the result of the __aenter__ method.
|
returns the result of the __aenter__ method.
|
||||||
"""
|
"""
|
||||||
cls = type(cm)
|
_cm_type = type(cm)
|
||||||
try:
|
_exit = _cm_type.__aexit__
|
||||||
_enter = cls.__aenter__
|
result = await _cm_type.__aenter__(cm)
|
||||||
_exit = cls.__aexit__
|
|
||||||
except _CL2_ERROR_TO_CONVERT:
|
|
||||||
raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does "
|
|
||||||
f"not support the asynchronous context manager protocol"
|
|
||||||
) from None
|
|
||||||
result = await _enter(cm)
|
|
||||||
self._push_async_cm_exit(cm, _exit)
|
self._push_async_cm_exit(cm, _exit)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
@ -735,11 +665,18 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
||||||
self._push_async_cm_exit(exit, exit_method)
|
self._push_async_cm_exit(exit, exit_method)
|
||||||
return exit # Allow use as a decorator
|
return exit # Allow use as a decorator
|
||||||
|
|
||||||
def push_async_callback(self, callback, /, *args, **kwds):
|
def push_async_callback(*args, **kwds):
|
||||||
"""Registers an arbitrary coroutine function and arguments.
|
"""Registers an arbitrary coroutine function and arguments.
|
||||||
|
|
||||||
Cannot suppress exceptions.
|
Cannot suppress exceptions.
|
||||||
"""
|
"""
|
||||||
|
# Python 3.6/3.7 compatibility: no native positional-only args syntax
|
||||||
|
try:
|
||||||
|
self, callback, *args = args
|
||||||
|
except ValueError as exc:
|
||||||
|
exc_details = str(exc).partition("(")[2]
|
||||||
|
msg = "Not enough positional arguments {}".format(exc_details)
|
||||||
|
raise TypeError(msg) from None
|
||||||
_exit_wrapper = self._create_async_cb_wrapper(callback, *args, **kwds)
|
_exit_wrapper = self._create_async_cb_wrapper(callback, *args, **kwds)
|
||||||
|
|
||||||
# We changed the signature, so using @wraps is not appropriate, but
|
# We changed the signature, so using @wraps is not appropriate, but
|
||||||
|
|
@ -771,10 +708,10 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
||||||
# Context may not be correct, so find the end of the chain
|
# Context may not be correct, so find the end of the chain
|
||||||
while 1:
|
while 1:
|
||||||
exc_context = new_exc.__context__
|
exc_context = new_exc.__context__
|
||||||
if exc_context is None or exc_context is old_exc:
|
if exc_context is old_exc:
|
||||||
# Context is already set correctly (see issue 20317)
|
# Context is already set correctly (see issue 20317)
|
||||||
return
|
return
|
||||||
if exc_context is frame_exc:
|
if exc_context is None or exc_context is frame_exc:
|
||||||
break
|
break
|
||||||
new_exc = exc_context
|
new_exc = exc_context
|
||||||
# Change the end of the chain to point to the exception
|
# Change the end of the chain to point to the exception
|
||||||
|
|
@ -842,26 +779,11 @@ class nullcontext(AbstractContextManager, AbstractAsyncContextManager):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class chdir(AbstractContextManager):
|
|
||||||
"""Non thread-safe context manager to change the current working directory."""
|
|
||||||
|
|
||||||
def __init__(self, path):
|
|
||||||
self.path = path
|
|
||||||
self._old_cwd = []
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self._old_cwd.append(os.getcwd())
|
|
||||||
os.chdir(self.path)
|
|
||||||
|
|
||||||
def __exit__(self, *excinfo):
|
|
||||||
os.chdir(self._old_cwd.pop())
|
|
||||||
|
|
||||||
# Preserve backwards compatibility
|
# Preserve backwards compatibility
|
||||||
class ContextStack(ExitStack):
|
class ContextStack(ExitStack):
|
||||||
"""(DEPRECATED) Backwards compatibility alias for ExitStack"""
|
"""Backwards compatibility alias for ExitStack"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
import warnings # Only import if needed for the deprecation warning
|
|
||||||
warnings.warn("ContextStack has been renamed to ExitStack",
|
warnings.warn("ContextStack has been renamed to ExitStack",
|
||||||
DeprecationWarning)
|
DeprecationWarning)
|
||||||
super(ContextStack, self).__init__()
|
super(ContextStack, self).__init__()
|
||||||
|
|
|
||||||
|
|
@ -1,201 +1,132 @@
|
||||||
# Type hints copied from the typeshed project under the Apache License 2.0
|
# Type hints copied from the typeshed project under the Apache License 2.0
|
||||||
# https://github.com/python/typeshed/blob/64c85cdd449ccaff90b546676220c9ecfa6e697f/LICENSE
|
# https://github.com/python/typeshed/blob/64c85cdd449ccaff90b546676220c9ecfa6e697f/LICENSE
|
||||||
|
|
||||||
# For updates: https://github.com/python/typeshed/blob/main/stdlib/contextlib.pyi
|
import sys
|
||||||
|
from types import TracebackType
|
||||||
# Last updated: 2024-05-22
|
from typing import (
|
||||||
# Updated from: https://github.com/python/typeshed/blob/aa2d33df211e1e4f70883388febf750ac524d2bb/stdlib/contextlib.pyi
|
IO,
|
||||||
# Saved to: dev/typeshed_contextlib.pyi
|
Any,
|
||||||
|
AsyncContextManager,
|
||||||
|
AsyncIterator,
|
||||||
|
Awaitable,
|
||||||
|
Callable,
|
||||||
|
ContextManager,
|
||||||
|
Iterator,
|
||||||
|
Optional,
|
||||||
|
Type,
|
||||||
|
TypeVar,
|
||||||
|
overload,
|
||||||
|
)
|
||||||
|
from typing_extensions import ParamSpec, Protocol
|
||||||
|
|
||||||
# contextlib2 API adaptation notes:
|
# contextlib2 API adaptation notes:
|
||||||
# * the various 'if True:' guards replace sys.version checks in the original
|
# * the various 'if True:' guards replace sys.version checks in the original
|
||||||
# typeshed file (those APIs are available on all supported versions)
|
# typeshed file (those APIs are available on all supported versions)
|
||||||
# * any commented out 'if True:' guards replace sys.version checks in the original
|
|
||||||
# typeshed file where the affected APIs haven't been backported yet
|
|
||||||
# * deliberately omitted APIs are listed in `dev/mypy.allowlist`
|
# * deliberately omitted APIs are listed in `dev/mypy.allowlist`
|
||||||
# (e.g. deprecated experimental APIs that never graduated to the stdlib)
|
# (e.g. deprecated experimental APIs that never graduated to the stdlib)
|
||||||
|
|
||||||
import abc
|
AbstractContextManager = ContextManager
|
||||||
import sys
|
|
||||||
from _typeshed import FileDescriptorOrPath, Unused
|
|
||||||
from abc import abstractmethod
|
|
||||||
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Generator, Iterator
|
|
||||||
from types import TracebackType
|
|
||||||
from typing import IO, Any, Generic, Protocol, TypeVar, overload, runtime_checkable
|
|
||||||
from typing_extensions import ParamSpec, Self, TypeAlias
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"contextmanager",
|
|
||||||
"closing",
|
|
||||||
"AbstractContextManager",
|
|
||||||
"ContextDecorator",
|
|
||||||
"ExitStack",
|
|
||||||
"redirect_stdout",
|
|
||||||
"redirect_stderr",
|
|
||||||
"suppress",
|
|
||||||
"AbstractAsyncContextManager",
|
|
||||||
"AsyncExitStack",
|
|
||||||
"asynccontextmanager",
|
|
||||||
"nullcontext",
|
|
||||||
]
|
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
__all__ += ["aclosing"]
|
AbstractAsyncContextManager = AsyncContextManager
|
||||||
|
|
||||||
if True:
|
|
||||||
__all__ += ["chdir"]
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
_T_co = TypeVar("_T_co", covariant=True)
|
_T_co = TypeVar("_T_co", covariant=True)
|
||||||
_T_io = TypeVar("_T_io", bound=IO[str] | None)
|
_T_io = TypeVar("_T_io", bound=Optional[IO[str]])
|
||||||
_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound=bool | None, default=bool | None)
|
|
||||||
_F = TypeVar("_F", bound=Callable[..., Any])
|
_F = TypeVar("_F", bound=Callable[..., Any])
|
||||||
_P = ParamSpec("_P")
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
_ExitFunc: TypeAlias = Callable[[type[BaseException] | None, BaseException | None, TracebackType | None], bool | None]
|
_ExitFunc = Callable[[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]], bool]
|
||||||
_CM_EF = TypeVar("_CM_EF", bound=AbstractContextManager[Any, Any] | _ExitFunc)
|
_CM_EF = TypeVar("_CM_EF", ContextManager[Any], _ExitFunc)
|
||||||
|
|
||||||
@runtime_checkable
|
class _GeneratorContextManager(ContextManager[_T_co]):
|
||||||
class AbstractContextManager(Protocol[_T_co, _ExitT_co]):
|
|
||||||
def __enter__(self) -> _T_co: ...
|
|
||||||
@abstractmethod
|
|
||||||
def __exit__(
|
|
||||||
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
|
||||||
) -> _ExitT_co: ...
|
|
||||||
|
|
||||||
@runtime_checkable
|
|
||||||
class AbstractAsyncContextManager(Protocol[_T_co, _ExitT_co]):
|
|
||||||
async def __aenter__(self) -> _T_co: ...
|
|
||||||
@abstractmethod
|
|
||||||
async def __aexit__(
|
|
||||||
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
|
||||||
) -> _ExitT_co: ...
|
|
||||||
|
|
||||||
class ContextDecorator:
|
|
||||||
def __call__(self, func: _F) -> _F: ...
|
def __call__(self, func: _F) -> _F: ...
|
||||||
|
|
||||||
class _GeneratorContextManager(AbstractContextManager[_T_co, bool | None], ContextDecorator):
|
# type ignore to deal with incomplete ParamSpec support in mypy
|
||||||
# __init__ and all instance attributes are actually inherited from _GeneratorContextManagerBase
|
def contextmanager(func: Callable[_P, Iterator[_T]]) -> Callable[_P, _GeneratorContextManager[_T]]: ... # type: ignore
|
||||||
# _GeneratorContextManagerBase is more trouble than it's worth to include in the stub; see #6676
|
|
||||||
def __init__(self, func: Callable[..., Iterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
|
|
||||||
gen: Generator[_T_co, Any, Any]
|
|
||||||
func: Callable[..., Generator[_T_co, Any, Any]]
|
|
||||||
args: tuple[Any, ...]
|
|
||||||
kwds: dict[str, Any]
|
|
||||||
if True:
|
|
||||||
def __exit__(
|
|
||||||
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
) -> bool | None: ...
|
|
||||||
|
|
||||||
def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co]]: ...
|
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
_AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]])
|
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T]]) -> Callable[_P, AsyncContextManager[_T]]: ... # type: ignore
|
||||||
|
|
||||||
class AsyncContextDecorator:
|
|
||||||
def __call__(self, func: _AF) -> _AF: ...
|
|
||||||
|
|
||||||
class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co, bool | None], AsyncContextDecorator):
|
|
||||||
# __init__ and these attributes are actually defined in the base class _GeneratorContextManagerBase,
|
|
||||||
# which is more trouble than it's worth to include in the stub (see #6676)
|
|
||||||
def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
|
|
||||||
gen: AsyncGenerator[_T_co, Any]
|
|
||||||
func: Callable[..., AsyncGenerator[_T_co, Any]]
|
|
||||||
args: tuple[Any, ...]
|
|
||||||
kwds: dict[str, Any]
|
|
||||||
async def __aexit__(
|
|
||||||
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
) -> bool | None: ...
|
|
||||||
|
|
||||||
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T_co]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]: ...
|
|
||||||
|
|
||||||
class _SupportsClose(Protocol):
|
class _SupportsClose(Protocol):
|
||||||
def close(self) -> object: ...
|
def close(self) -> object: ...
|
||||||
|
|
||||||
_SupportsCloseT = TypeVar("_SupportsCloseT", bound=_SupportsClose)
|
_SupportsCloseT = TypeVar("_SupportsCloseT", bound=_SupportsClose)
|
||||||
|
|
||||||
class closing(AbstractContextManager[_SupportsCloseT, None]):
|
class closing(ContextManager[_SupportsCloseT]):
|
||||||
def __init__(self, thing: _SupportsCloseT) -> None: ...
|
def __init__(self, thing: _SupportsCloseT) -> None: ...
|
||||||
def __exit__(self, *exc_info: Unused) -> None: ...
|
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
class _SupportsAclose(Protocol):
|
class _SupportsAclose(Protocol):
|
||||||
def aclose(self) -> Awaitable[object]: ...
|
async def aclose(self) -> object: ...
|
||||||
|
|
||||||
_SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose)
|
_SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose)
|
||||||
|
class aclosing(AsyncContextManager[_SupportsAcloseT]):
|
||||||
class aclosing(AbstractAsyncContextManager[_SupportsAcloseT, None]):
|
|
||||||
def __init__(self, thing: _SupportsAcloseT) -> None: ...
|
def __init__(self, thing: _SupportsAcloseT) -> None: ...
|
||||||
async def __aexit__(self, *exc_info: Unused) -> None: ...
|
_AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]])
|
||||||
|
class AsyncContextDecorator:
|
||||||
|
def __call__(self, func: _AF) -> _AF: ...
|
||||||
|
|
||||||
class suppress(AbstractContextManager[None, bool]):
|
class suppress(ContextManager[None]):
|
||||||
def __init__(self, *exceptions: type[BaseException]) -> None: ...
|
def __init__(self, *exceptions: Type[BaseException]) -> None: ...
|
||||||
def __exit__(
|
def __exit__(
|
||||||
self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None
|
self, exctype: Optional[Type[BaseException]], excinst: Optional[BaseException], exctb: Optional[TracebackType]
|
||||||
) -> bool: ...
|
) -> bool: ...
|
||||||
|
|
||||||
class _RedirectStream(AbstractContextManager[_T_io, None]):
|
class redirect_stdout(ContextManager[_T_io]):
|
||||||
def __init__(self, new_target: _T_io) -> None: ...
|
def __init__(self, new_target: _T_io) -> None: ...
|
||||||
def __exit__(
|
|
||||||
self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None
|
|
||||||
) -> None: ...
|
|
||||||
|
|
||||||
class redirect_stdout(_RedirectStream[_T_io]): ...
|
class redirect_stderr(ContextManager[_T_io]):
|
||||||
class redirect_stderr(_RedirectStream[_T_io]): ...
|
def __init__(self, new_target: _T_io) -> None: ...
|
||||||
|
|
||||||
# In reality this is a subclass of `AbstractContextManager`;
|
class ContextDecorator:
|
||||||
# see #7961 for why we don't do that in the stub
|
def __call__(self, func: _F) -> _F: ...
|
||||||
class ExitStack(Generic[_ExitT_co], metaclass=abc.ABCMeta):
|
|
||||||
def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ...
|
_U = TypeVar("_U", bound=ExitStack)
|
||||||
|
|
||||||
|
class ExitStack(ContextManager[ExitStack]):
|
||||||
|
def __init__(self) -> None: ...
|
||||||
|
def enter_context(self, cm: ContextManager[_T]) -> _T: ...
|
||||||
def push(self, exit: _CM_EF) -> _CM_EF: ...
|
def push(self, exit: _CM_EF) -> _CM_EF: ...
|
||||||
def callback(self, callback: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ...
|
def callback(self, callback: Callable[..., Any], *args: Any, **kwds: Any) -> Callable[..., Any]: ...
|
||||||
def pop_all(self) -> Self: ...
|
def pop_all(self: _U) -> _U: ...
|
||||||
def close(self) -> None: ...
|
def close(self) -> None: ...
|
||||||
def __enter__(self) -> Self: ...
|
def __enter__(self: _U) -> _U: ...
|
||||||
def __exit__(
|
def __exit__(
|
||||||
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
self,
|
||||||
) -> _ExitT_co: ...
|
__exc_type: Optional[Type[BaseException]],
|
||||||
|
__exc_value: Optional[BaseException],
|
||||||
_ExitCoroFunc: TypeAlias = Callable[
|
__traceback: Optional[TracebackType],
|
||||||
[type[BaseException] | None, BaseException | None, TracebackType | None], Awaitable[bool | None]
|
|
||||||
]
|
|
||||||
_ACM_EF = TypeVar("_ACM_EF", bound=AbstractAsyncContextManager[Any, Any] | _ExitCoroFunc)
|
|
||||||
|
|
||||||
# In reality this is a subclass of `AbstractAsyncContextManager`;
|
|
||||||
# see #7961 for why we don't do that in the stub
|
|
||||||
class AsyncExitStack(Generic[_ExitT_co], metaclass=abc.ABCMeta):
|
|
||||||
def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ...
|
|
||||||
async def enter_async_context(self, cm: AbstractAsyncContextManager[_T, _ExitT_co]) -> _T: ...
|
|
||||||
def push(self, exit: _CM_EF) -> _CM_EF: ...
|
|
||||||
def push_async_exit(self, exit: _ACM_EF) -> _ACM_EF: ...
|
|
||||||
def callback(self, callback: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ...
|
|
||||||
def push_async_callback(
|
|
||||||
self, callback: Callable[_P, Awaitable[_T]], /, *args: _P.args, **kwds: _P.kwargs
|
|
||||||
) -> Callable[_P, Awaitable[_T]]: ...
|
|
||||||
def pop_all(self) -> Self: ...
|
|
||||||
async def aclose(self) -> None: ...
|
|
||||||
async def __aenter__(self) -> Self: ...
|
|
||||||
async def __aexit__(
|
|
||||||
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
|
||||||
) -> bool: ...
|
) -> bool: ...
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
class nullcontext(AbstractContextManager[_T, None], AbstractAsyncContextManager[_T, None]):
|
_S = TypeVar("_S", bound=AsyncExitStack)
|
||||||
|
|
||||||
|
_ExitCoroFunc = Callable[[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]], Awaitable[bool]]
|
||||||
|
_CallbackCoroFunc = Callable[..., Awaitable[Any]]
|
||||||
|
_ACM_EF = TypeVar("_ACM_EF", AsyncContextManager[Any], _ExitCoroFunc)
|
||||||
|
class AsyncExitStack(AsyncContextManager[AsyncExitStack]):
|
||||||
|
def __init__(self) -> None: ...
|
||||||
|
def enter_context(self, cm: ContextManager[_T]) -> _T: ...
|
||||||
|
def enter_async_context(self, cm: AsyncContextManager[_T]) -> Awaitable[_T]: ...
|
||||||
|
def push(self, exit: _CM_EF) -> _CM_EF: ...
|
||||||
|
def push_async_exit(self, exit: _ACM_EF) -> _ACM_EF: ...
|
||||||
|
def callback(self, callback: Callable[..., Any], *args: Any, **kwds: Any) -> Callable[..., Any]: ...
|
||||||
|
def push_async_callback(self, callback: _CallbackCoroFunc, *args: Any, **kwds: Any) -> _CallbackCoroFunc: ...
|
||||||
|
def pop_all(self: _S) -> _S: ...
|
||||||
|
def aclose(self) -> Awaitable[None]: ...
|
||||||
|
def __aenter__(self: _S) -> Awaitable[_S]: ...
|
||||||
|
def __aexit__(
|
||||||
|
self,
|
||||||
|
__exc_type: Optional[Type[BaseException]],
|
||||||
|
__exc_value: Optional[BaseException],
|
||||||
|
__traceback: Optional[TracebackType],
|
||||||
|
) -> Awaitable[bool]: ...
|
||||||
|
|
||||||
|
if True:
|
||||||
|
class nullcontext(AbstractContextManager[_T]):
|
||||||
enter_result: _T
|
enter_result: _T
|
||||||
@overload
|
@overload
|
||||||
def __init__(self: nullcontext[None], enter_result: None = None) -> None: ...
|
def __init__(self: nullcontext[None], enter_result: None = ...) -> None: ...
|
||||||
@overload
|
@overload
|
||||||
def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780
|
def __init__(self: nullcontext[_T], enter_result: _T) -> None: ...
|
||||||
def __enter__(self) -> _T: ...
|
def __enter__(self) -> _T: ...
|
||||||
def __exit__(self, *exctype: Unused) -> None: ...
|
def __exit__(self, *exctype: Any) -> bool: ...
|
||||||
async def __aenter__(self) -> _T: ...
|
|
||||||
async def __aexit__(self, *exctype: Unused) -> None: ...
|
|
||||||
|
|
||||||
if True:
|
|
||||||
_T_fd_or_any_path = TypeVar("_T_fd_or_any_path", bound=FileDescriptorOrPath)
|
|
||||||
|
|
||||||
class chdir(AbstractContextManager[None, None], Generic[_T_fd_or_any_path]):
|
|
||||||
path: _T_fd_or_any_path
|
|
||||||
def __init__(self, path: _T_fd_or_any_path) -> None: ...
|
|
||||||
def __enter__(self) -> None: ...
|
|
||||||
def __exit__(self, *excinfo: Unused) -> None: ...
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,3 @@
|
||||||
# Deprecated APIs that never graduated to the standard library
|
# Deprecated APIs that never graduated to the standard library
|
||||||
contextlib2.ContextDecorator.refresh_cm
|
contextlib2.ContextDecorator.refresh_cm
|
||||||
|
contextlib2.ContextStack
|
||||||
# stubcheck no longer complains about this one for some reason
|
|
||||||
# (but it does complain about the unused allowlist entry)
|
|
||||||
# contextlib2.ContextStack
|
|
||||||
|
|
||||||
# mypy seems to be confused by the GenericAlias compatibility hack
|
|
||||||
contextlib2.AbstractAsyncContextManager.__class_getitem__
|
|
||||||
contextlib2.AbstractContextManager.__class_getitem__
|
|
||||||
|
|
|
||||||
61
dev/py3_10_contextlib_pyi_to_contextlib2_pyi.patch
Normal file
61
dev/py3_10_contextlib_pyi_to_contextlib2_pyi.patch
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
--- ../contextlib.pyi 2021-06-27 16:02:28.004872421 +1000
|
||||||
|
+++ contextlib2/__init__.pyi 2021-06-27 16:00:25.431733524 +1000
|
||||||
|
@@ -1,3 +1,6 @@
|
||||||
|
+# Type hints copied from the typeshed project under the Apache License 2.0
|
||||||
|
+# https://github.com/python/typeshed/blob/64c85cdd449ccaff90b546676220c9ecfa6e697f/LICENSE
|
||||||
|
+
|
||||||
|
import sys
|
||||||
|
from types import TracebackType
|
||||||
|
from typing import (
|
||||||
|
@@ -16,8 +19,14 @@
|
||||||
|
)
|
||||||
|
from typing_extensions import ParamSpec, Protocol
|
||||||
|
|
||||||
|
+# contextlib2 API adaptation notes:
|
||||||
|
+# * the various 'if True:' guards replace sys.version checks in the original
|
||||||
|
+# typeshed file (those APIs are available on all supported versions)
|
||||||
|
+# * deliberately omitted APIs are listed in `dev/mypy.allowlist`
|
||||||
|
+# (e.g. deprecated experimental APIs that never graduated to the stdlib)
|
||||||
|
+
|
||||||
|
AbstractContextManager = ContextManager
|
||||||
|
-if sys.version_info >= (3, 7):
|
||||||
|
+if True:
|
||||||
|
AbstractAsyncContextManager = AsyncContextManager
|
||||||
|
|
||||||
|
_T = TypeVar("_T")
|
||||||
|
@@ -35,7 +44,7 @@
|
||||||
|
# type ignore to deal with incomplete ParamSpec support in mypy
|
||||||
|
def contextmanager(func: Callable[_P, Iterator[_T]]) -> Callable[_P, _GeneratorContextManager[_T]]: ... # type: ignore
|
||||||
|
|
||||||
|
-if sys.version_info >= (3, 7):
|
||||||
|
+if True:
|
||||||
|
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T]]) -> Callable[_P, AsyncContextManager[_T]]: ... # type: ignore
|
||||||
|
|
||||||
|
class _SupportsClose(Protocol):
|
||||||
|
@@ -46,7 +55,7 @@
|
||||||
|
class closing(ContextManager[_SupportsCloseT]):
|
||||||
|
def __init__(self, thing: _SupportsCloseT) -> None: ...
|
||||||
|
|
||||||
|
-if sys.version_info >= (3, 10):
|
||||||
|
+if True:
|
||||||
|
class _SupportsAclose(Protocol):
|
||||||
|
async def aclose(self) -> object: ...
|
||||||
|
_SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose)
|
||||||
|
@@ -88,7 +97,7 @@
|
||||||
|
__traceback: Optional[TracebackType],
|
||||||
|
) -> bool: ...
|
||||||
|
|
||||||
|
-if sys.version_info >= (3, 7):
|
||||||
|
+if True:
|
||||||
|
_S = TypeVar("_S", bound=AsyncExitStack)
|
||||||
|
|
||||||
|
_ExitCoroFunc = Callable[[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]], Awaitable[bool]]
|
||||||
|
@@ -112,7 +121,7 @@
|
||||||
|
__traceback: Optional[TracebackType],
|
||||||
|
) -> Awaitable[bool]: ...
|
||||||
|
|
||||||
|
-if sys.version_info >= (3, 7):
|
||||||
|
+if True:
|
||||||
|
class nullcontext(AbstractContextManager[_T]):
|
||||||
|
enter_result: _T
|
||||||
|
@overload
|
||||||
194
dev/py3_10_contextlib_rst_to_contextlib2_rst.patch
Normal file
194
dev/py3_10_contextlib_rst_to_contextlib2_rst.patch
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
--- ../cpython/Doc/library/contextlib.rst 2021-06-26 18:31:45.179532455 +1000
|
||||||
|
+++ docs/contextlib2.rst 2021-06-26 21:19:00.172517765 +1000
|
||||||
|
@@ -1,20 +1,5 @@
|
||||||
|
-:mod:`!contextlib` --- Utilities for :keyword:`!with`\ -statement contexts
|
||||||
|
-==========================================================================
|
||||||
|
-
|
||||||
|
-.. module:: contextlib
|
||||||
|
- :synopsis: Utilities for with-statement contexts.
|
||||||
|
-
|
||||||
|
-**Source code:** :source:`Lib/contextlib.py`
|
||||||
|
-
|
||||||
|
---------------
|
||||||
|
-
|
||||||
|
-This module provides utilities for common tasks involving the :keyword:`with`
|
||||||
|
-statement. For more information see also :ref:`typecontextmanager` and
|
||||||
|
-:ref:`context-managers`.
|
||||||
|
-
|
||||||
|
-
|
||||||
|
-Utilities
|
||||||
|
----------
|
||||||
|
+API Reference
|
||||||
|
+-------------
|
||||||
|
|
||||||
|
Functions and classes provided:
|
||||||
|
|
||||||
|
@@ -26,8 +11,8 @@
|
||||||
|
``self`` while :meth:`object.__exit__` is an abstract method which by default
|
||||||
|
returns ``None``. See also the definition of :ref:`typecontextmanager`.
|
||||||
|
|
||||||
|
- .. versionadded:: 3.6
|
||||||
|
-
|
||||||
|
+ .. versionadded:: 0.6.0
|
||||||
|
+ Part of the standard library in Python 3.6 and later
|
||||||
|
|
||||||
|
.. class:: AbstractAsyncContextManager
|
||||||
|
|
||||||
|
@@ -38,8 +23,8 @@
|
||||||
|
returns ``None``. See also the definition of
|
||||||
|
:ref:`async-context-managers`.
|
||||||
|
|
||||||
|
- .. versionadded:: 3.7
|
||||||
|
-
|
||||||
|
+ .. versionadded:: 21.6.0
|
||||||
|
+ Part of the standard library in Python 3.7 and later
|
||||||
|
|
||||||
|
.. decorator:: contextmanager
|
||||||
|
|
||||||
|
@@ -93,9 +78,6 @@
|
||||||
|
created by :func:`contextmanager` to meet the requirement that context
|
||||||
|
managers support multiple invocations in order to be used as decorators).
|
||||||
|
|
||||||
|
- .. versionchanged:: 3.2
|
||||||
|
- Use of :class:`ContextDecorator`.
|
||||||
|
-
|
||||||
|
|
||||||
|
.. decorator:: asynccontextmanager
|
||||||
|
|
||||||
|
@@ -124,7 +106,10 @@
|
||||||
|
async with get_connection() as conn:
|
||||||
|
return conn.query('SELECT ...')
|
||||||
|
|
||||||
|
- .. versionadded:: 3.7
|
||||||
|
+ .. versionadded:: 21.6.0
|
||||||
|
+ Part of the standard library in Python 3.7 and later, enhanced in
|
||||||
|
+ Python 3.10 and later to allow created async context managers to be used
|
||||||
|
+ as async function decorators.
|
||||||
|
|
||||||
|
Context managers defined with :func:`asynccontextmanager` can be used
|
||||||
|
either as decorators or with :keyword:`async with` statements::
|
||||||
|
@@ -147,10 +132,6 @@
|
||||||
|
created by :func:`asynccontextmanager` to meet the requirement that context
|
||||||
|
managers support multiple invocations in order to be used as decorators.
|
||||||
|
|
||||||
|
- .. versionchanged:: 3.10
|
||||||
|
- Async context managers created with :func:`asynccontextmanager` can
|
||||||
|
- be used as decorators.
|
||||||
|
-
|
||||||
|
|
||||||
|
.. function:: closing(thing)
|
||||||
|
|
||||||
|
@@ -209,7 +190,8 @@
|
||||||
|
variables work as expected, and the exit code isn't run after the
|
||||||
|
lifetime of some task it depends on).
|
||||||
|
|
||||||
|
- .. versionadded:: 3.10
|
||||||
|
+ .. versionadded:: 21.6.0
|
||||||
|
+ Part of the standard library in Python 3.10 and later
|
||||||
|
|
||||||
|
|
||||||
|
.. _simplifying-support-for-single-optional-context-managers:
|
||||||
|
@@ -257,11 +239,11 @@
|
||||||
|
async with cm as session:
|
||||||
|
# Send http requests with session
|
||||||
|
|
||||||
|
- .. versionadded:: 3.7
|
||||||
|
-
|
||||||
|
- .. versionchanged:: 3.10
|
||||||
|
- :term:`asynchronous context manager` support was added.
|
||||||
|
+ .. versionadded:: 0.6.0
|
||||||
|
+ Part of the standard library in Python 3.7 and later
|
||||||
|
|
||||||
|
+ .. versionchanged:: 21.6.0
|
||||||
|
+ Updated to Python 3.10 version with :term:`asynchronous context manager` support
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: suppress(*exceptions)
|
||||||
|
@@ -300,7 +282,8 @@
|
||||||
|
|
||||||
|
This context manager is :ref:`reentrant <reentrant-cms>`.
|
||||||
|
|
||||||
|
- .. versionadded:: 3.4
|
||||||
|
+ .. versionadded:: 0.5
|
||||||
|
+ Part of the standard library in Python 3.4 and later
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: redirect_stdout(new_target)
|
||||||
|
@@ -340,7 +323,8 @@
|
||||||
|
|
||||||
|
This context manager is :ref:`reentrant <reentrant-cms>`.
|
||||||
|
|
||||||
|
- .. versionadded:: 3.4
|
||||||
|
+ .. versionadded:: 0.5
|
||||||
|
+ Part of the standard library in Python 3.4 and later
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: redirect_stderr(new_target)
|
||||||
|
@@ -350,7 +334,8 @@
|
||||||
|
|
||||||
|
This context manager is :ref:`reentrant <reentrant-cms>`.
|
||||||
|
|
||||||
|
- .. versionadded:: 3.5
|
||||||
|
+ .. versionadded:: 0.5
|
||||||
|
+ Part of the standard library in Python 3.5 and later
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: ContextDecorator()
|
||||||
|
@@ -426,8 +411,6 @@
|
||||||
|
statements. If this is not the case, then the original construct with the
|
||||||
|
explicit :keyword:`!with` statement inside the function should be used.
|
||||||
|
|
||||||
|
- .. versionadded:: 3.2
|
||||||
|
-
|
||||||
|
|
||||||
|
.. class:: AsyncContextDecorator
|
||||||
|
|
||||||
|
@@ -465,7 +448,8 @@
|
||||||
|
The bit in the middle
|
||||||
|
Finishing
|
||||||
|
|
||||||
|
- .. versionadded:: 3.10
|
||||||
|
+ .. versionadded:: 21.6.0
|
||||||
|
+ Part of the standard library in Python 3.10 and later
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: ExitStack()
|
||||||
|
@@ -504,7 +488,8 @@
|
||||||
|
foundation for higher level context managers that manipulate the exit
|
||||||
|
stack in application specific ways.
|
||||||
|
|
||||||
|
- .. versionadded:: 3.3
|
||||||
|
+ .. versionadded:: 0.4
|
||||||
|
+ Part of the standard library in Python 3.3 and later
|
||||||
|
|
||||||
|
.. method:: enter_context(cm)
|
||||||
|
|
||||||
|
@@ -580,7 +565,7 @@
|
||||||
|
The :meth:`close` method is not implemented, :meth:`aclose` must be used
|
||||||
|
instead.
|
||||||
|
|
||||||
|
- .. coroutinemethod:: enter_async_context(cm)
|
||||||
|
+ .. method:: enter_async_context(cm)
|
||||||
|
|
||||||
|
Similar to :meth:`enter_context` but expects an asynchronous context
|
||||||
|
manager.
|
||||||
|
@@ -594,7 +579,7 @@
|
||||||
|
|
||||||
|
Similar to :meth:`callback` but expects a coroutine function.
|
||||||
|
|
||||||
|
- .. coroutinemethod:: aclose()
|
||||||
|
+ .. method:: aclose()
|
||||||
|
|
||||||
|
Similar to :meth:`close` but properly handles awaitables.
|
||||||
|
|
||||||
|
@@ -607,7 +592,9 @@
|
||||||
|
# the async with statement, even if attempts to open a connection
|
||||||
|
# later in the list raise an exception.
|
||||||
|
|
||||||
|
- .. versionadded:: 3.7
|
||||||
|
+ .. versionadded:: 21.6.0
|
||||||
|
+ Part of the standard library in Python 3.7 and later
|
||||||
|
+
|
||||||
|
|
||||||
|
Examples and Recipes
|
||||||
|
--------------------
|
||||||
147
dev/py3_10_contextlib_to_contextlib2.patch
Normal file
147
dev/py3_10_contextlib_to_contextlib2.patch
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
--- ../cpython/Lib/contextlib.py 2021-06-26 16:28:03.835372955 +1000
|
||||||
|
+++ contextlib2.py 2021-06-26 17:40:30.047079570 +1000
|
||||||
|
@@ -1,19 +1,32 @@
|
||||||
|
-"""Utilities for with-statement contexts. See PEP 343."""
|
||||||
|
+"""contextlib2 - backports and enhancements to the contextlib module"""
|
||||||
|
+
|
||||||
|
import abc
|
||||||
|
import sys
|
||||||
|
+import warnings
|
||||||
|
import _collections_abc
|
||||||
|
from collections import deque
|
||||||
|
from functools import wraps
|
||||||
|
-from types import MethodType, GenericAlias
|
||||||
|
+from types import MethodType
|
||||||
|
+
|
||||||
|
+# Python 3.6/3.7/3.8 compatibility: GenericAlias may not be defined
|
||||||
|
+try:
|
||||||
|
+ from types import GenericAlias
|
||||||
|
+except ImportError:
|
||||||
|
+ # If the real GenericAlias type doesn't exist, __class_getitem__ won't be used,
|
||||||
|
+ # so the fallback placeholder doesn't need to provide any meaningful behaviour
|
||||||
|
+ class GenericAlias:
|
||||||
|
+ pass
|
||||||
|
+
|
||||||
|
|
||||||
|
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
|
||||||
|
"AbstractContextManager", "AbstractAsyncContextManager",
|
||||||
|
"AsyncExitStack", "ContextDecorator", "ExitStack",
|
||||||
|
"redirect_stdout", "redirect_stderr", "suppress", "aclosing"]
|
||||||
|
|
||||||
|
+# Backwards compatibility
|
||||||
|
+__all__ += ["ContextStack"]
|
||||||
|
|
||||||
|
class AbstractContextManager(abc.ABC):
|
||||||
|
-
|
||||||
|
"""An abstract base class for context managers."""
|
||||||
|
|
||||||
|
__class_getitem__ = classmethod(GenericAlias)
|
||||||
|
@@ -60,6 +73,23 @@
|
||||||
|
class ContextDecorator(object):
|
||||||
|
"A base class or mixin that enables context managers to work as decorators."
|
||||||
|
|
||||||
|
+ def refresh_cm(self):
|
||||||
|
+ """Returns the context manager used to actually wrap the call to the
|
||||||
|
+ decorated function.
|
||||||
|
+
|
||||||
|
+ The default implementation just returns *self*.
|
||||||
|
+
|
||||||
|
+ Overriding this method allows otherwise one-shot context managers
|
||||||
|
+ like _GeneratorContextManager to support use as decorators via
|
||||||
|
+ implicit recreation.
|
||||||
|
+
|
||||||
|
+ DEPRECATED: refresh_cm was never added to the standard library's
|
||||||
|
+ ContextDecorator API
|
||||||
|
+ """
|
||||||
|
+ warnings.warn("refresh_cm was never added to the standard library",
|
||||||
|
+ DeprecationWarning)
|
||||||
|
+ return self._recreate_cm()
|
||||||
|
+
|
||||||
|
def _recreate_cm(self):
|
||||||
|
"""Return a recreated instance of self.
|
||||||
|
|
||||||
|
@@ -430,7 +460,9 @@
|
||||||
|
return MethodType(cm_exit, cm)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
- def _create_cb_wrapper(callback, /, *args, **kwds):
|
||||||
|
+ def _create_cb_wrapper(*args, **kwds):
|
||||||
|
+ # Python 3.6/3.7 compatibility: no native positional-only args syntax
|
||||||
|
+ callback, *args = args
|
||||||
|
def _exit_wrapper(exc_type, exc, tb):
|
||||||
|
callback(*args, **kwds)
|
||||||
|
return _exit_wrapper
|
||||||
|
@@ -479,11 +511,18 @@
|
||||||
|
self._push_cm_exit(cm, _exit)
|
||||||
|
return result
|
||||||
|
|
||||||
|
- def callback(self, callback, /, *args, **kwds):
|
||||||
|
+ def callback(*args, **kwds):
|
||||||
|
"""Registers an arbitrary callback and arguments.
|
||||||
|
|
||||||
|
Cannot suppress exceptions.
|
||||||
|
"""
|
||||||
|
+ # Python 3.6/3.7 compatibility: no native positional-only args syntax
|
||||||
|
+ try:
|
||||||
|
+ self, callback, *args = args
|
||||||
|
+ except ValueError as exc:
|
||||||
|
+ exc_details = str(exc).partition("(")[2]
|
||||||
|
+ msg = "Not enough positional arguments {}".format(exc_details)
|
||||||
|
+ raise TypeError(msg) from None
|
||||||
|
_exit_wrapper = self._create_cb_wrapper(callback, *args, **kwds)
|
||||||
|
|
||||||
|
# We changed the signature, so using @wraps is not appropriate, but
|
||||||
|
@@ -589,7 +628,9 @@
|
||||||
|
return MethodType(cm_exit, cm)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
- def _create_async_cb_wrapper(callback, /, *args, **kwds):
|
||||||
|
+ def _create_async_cb_wrapper(*args, **kwds):
|
||||||
|
+ # Python 3.6/3.7 compatibility: no native positional-only args syntax
|
||||||
|
+ callback, *args = args
|
||||||
|
async def _exit_wrapper(exc_type, exc, tb):
|
||||||
|
await callback(*args, **kwds)
|
||||||
|
return _exit_wrapper
|
||||||
|
@@ -624,11 +665,18 @@
|
||||||
|
self._push_async_cm_exit(exit, exit_method)
|
||||||
|
return exit # Allow use as a decorator
|
||||||
|
|
||||||
|
- def push_async_callback(self, callback, /, *args, **kwds):
|
||||||
|
+ def push_async_callback(*args, **kwds):
|
||||||
|
"""Registers an arbitrary coroutine function and arguments.
|
||||||
|
|
||||||
|
Cannot suppress exceptions.
|
||||||
|
"""
|
||||||
|
+ # Python 3.6/3.7 compatibility: no native positional-only args syntax
|
||||||
|
+ try:
|
||||||
|
+ self, callback, *args = args
|
||||||
|
+ except ValueError as exc:
|
||||||
|
+ exc_details = str(exc).partition("(")[2]
|
||||||
|
+ msg = "Not enough positional arguments {}".format(exc_details)
|
||||||
|
+ raise TypeError(msg) from None
|
||||||
|
_exit_wrapper = self._create_async_cb_wrapper(callback, *args, **kwds)
|
||||||
|
|
||||||
|
# We changed the signature, so using @wraps is not appropriate, but
|
||||||
|
@@ -729,3 +777,22 @@
|
||||||
|
|
||||||
|
async def __aexit__(self, *excinfo):
|
||||||
|
pass
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+# Preserve backwards compatibility
|
||||||
|
+class ContextStack(ExitStack):
|
||||||
|
+ """Backwards compatibility alias for ExitStack"""
|
||||||
|
+
|
||||||
|
+ def __init__(self):
|
||||||
|
+ warnings.warn("ContextStack has been renamed to ExitStack",
|
||||||
|
+ DeprecationWarning)
|
||||||
|
+ super(ContextStack, self).__init__()
|
||||||
|
+
|
||||||
|
+ def register_exit(self, callback):
|
||||||
|
+ return self.push(callback)
|
||||||
|
+
|
||||||
|
+ def register(self, callback, *args, **kwds):
|
||||||
|
+ return self.callback(callback, *args, **kwds)
|
||||||
|
+
|
||||||
|
+ def preserve(self):
|
||||||
|
+ return self.pop_all()
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
--- /home/ncoghlan/devel/contextlib2/../cpython/Lib/contextlib.py 2024-05-23 11:57:09.210023505 +1000
|
|
||||||
+++ /home/ncoghlan/devel/contextlib2/contextlib2/__init__.py 2024-05-23 17:05:06.549142813 +1000
|
|
||||||
@@ -5,7 +5,46 @@
|
|
||||||
import _collections_abc
|
|
||||||
from collections import deque
|
|
||||||
from functools import wraps
|
|
||||||
-from types import MethodType, GenericAlias
|
|
||||||
+from types import MethodType
|
|
||||||
+
|
|
||||||
+# Python 3.8 compatibility: GenericAlias may not be defined
|
|
||||||
+try:
|
|
||||||
+ from types import GenericAlias
|
|
||||||
+except ImportError:
|
|
||||||
+ # If the real GenericAlias type doesn't exist, __class_getitem__ won't be used,
|
|
||||||
+ # so the fallback placeholder doesn't need to provide any meaningful behaviour
|
|
||||||
+ # (typecheckers may still be unhappy, but for that problem the answer is
|
|
||||||
+ # "use a newer Python version with better typechecking support")
|
|
||||||
+ class GenericAlias:
|
|
||||||
+ pass
|
|
||||||
+
|
|
||||||
+# Python 3.10 and earlier compatibility: BaseExceptionGroup may not be defined
|
|
||||||
+try:
|
|
||||||
+ BaseExceptionGroup
|
|
||||||
+except NameError:
|
|
||||||
+ # If the real BaseExceptionGroup type doesn't exist, it will never actually
|
|
||||||
+ # be raised. This means the fallback placeholder doesn't need to provide
|
|
||||||
+ # any meaningful behaviour, it just needs to be compatible with 'issubclass'
|
|
||||||
+ class BaseExceptionGroup(BaseException):
|
|
||||||
+ pass
|
|
||||||
+
|
|
||||||
+# Python 3.9 and earlier compatibility: anext may not be defined
|
|
||||||
+try:
|
|
||||||
+ anext
|
|
||||||
+except NameError:
|
|
||||||
+ def anext(obj, /):
|
|
||||||
+ return obj.__anext__()
|
|
||||||
+
|
|
||||||
+# Python 3.11+ behaviour consistency: replace AttributeError with TypeError
|
|
||||||
+if sys.version_info >= (3, 11):
|
|
||||||
+ # enter_context() and enter_async_context() follow the change in the
|
|
||||||
+ # exception type raised by with statements and async with statements
|
|
||||||
+ _CL2_ERROR_TO_CONVERT = AttributeError
|
|
||||||
+else:
|
|
||||||
+ # On older versions, raise AttributeError without any changes
|
|
||||||
+ class _CL2_ERROR_TO_CONVERT(Exception):
|
|
||||||
+ pass
|
|
||||||
+
|
|
||||||
|
|
||||||
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
|
|
||||||
"AbstractContextManager", "AbstractAsyncContextManager",
|
|
||||||
@@ -62,6 +101,24 @@
|
|
||||||
class ContextDecorator(object):
|
|
||||||
"A base class or mixin that enables context managers to work as decorators."
|
|
||||||
|
|
||||||
+ def refresh_cm(self):
|
|
||||||
+ """Returns the context manager used to actually wrap the call to the
|
|
||||||
+ decorated function.
|
|
||||||
+
|
|
||||||
+ The default implementation just returns *self*.
|
|
||||||
+
|
|
||||||
+ Overriding this method allows otherwise one-shot context managers
|
|
||||||
+ like _GeneratorContextManager to support use as decorators via
|
|
||||||
+ implicit recreation.
|
|
||||||
+
|
|
||||||
+ DEPRECATED: refresh_cm was never added to the standard library's
|
|
||||||
+ ContextDecorator API
|
|
||||||
+ """
|
|
||||||
+ import warnings # Only import if needed for the deprecation warning
|
|
||||||
+ warnings.warn("refresh_cm was never added to the standard library",
|
|
||||||
+ DeprecationWarning)
|
|
||||||
+ return self._recreate_cm()
|
|
||||||
+
|
|
||||||
def _recreate_cm(self):
|
|
||||||
"""Return a recreated instance of self.
|
|
||||||
|
|
||||||
@@ -520,7 +577,7 @@
|
|
||||||
try:
|
|
||||||
_enter = cls.__enter__
|
|
||||||
_exit = cls.__exit__
|
|
||||||
- except AttributeError:
|
|
||||||
+ except _CL2_ERROR_TO_CONVERT:
|
|
||||||
raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does "
|
|
||||||
f"not support the context manager protocol") from None
|
|
||||||
result = _enter(cm)
|
|
||||||
@@ -652,7 +709,7 @@
|
|
||||||
try:
|
|
||||||
_enter = cls.__aenter__
|
|
||||||
_exit = cls.__aexit__
|
|
||||||
- except AttributeError:
|
|
||||||
+ except _CL2_ERROR_TO_CONVERT:
|
|
||||||
raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does "
|
|
||||||
f"not support the asynchronous context manager protocol"
|
|
||||||
) from None
|
|
||||||
@@ -798,3 +855,22 @@
|
|
||||||
|
|
||||||
def __exit__(self, *excinfo):
|
|
||||||
os.chdir(self._old_cwd.pop())
|
|
||||||
+
|
|
||||||
+# Preserve backwards compatibility
|
|
||||||
+class ContextStack(ExitStack):
|
|
||||||
+ """(DEPRECATED) Backwards compatibility alias for ExitStack"""
|
|
||||||
+
|
|
||||||
+ def __init__(self):
|
|
||||||
+ import warnings # Only import if needed for the deprecation warning
|
|
||||||
+ warnings.warn("ContextStack has been renamed to ExitStack",
|
|
||||||
+ DeprecationWarning)
|
|
||||||
+ super(ContextStack, self).__init__()
|
|
||||||
+
|
|
||||||
+ def register_exit(self, callback):
|
|
||||||
+ return self.push(callback)
|
|
||||||
+
|
|
||||||
+ def register(self, callback, *args, **kwds):
|
|
||||||
+ return self.callback(callback, *args, **kwds)
|
|
||||||
+
|
|
||||||
+ def preserve(self):
|
|
||||||
+ return self.pop_all()
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
--- /home/ncoghlan/devel/contextlib2/dev/typeshed_contextlib.pyi 2024-05-23 12:40:10.170754997 +1000
|
|
||||||
+++ /home/ncoghlan/devel/contextlib2/contextlib2/__init__.pyi 2024-05-23 16:47:15.874656809 +1000
|
|
||||||
@@ -1,3 +1,20 @@
|
|
||||||
+# Type hints copied from the typeshed project under the Apache License 2.0
|
|
||||||
+# https://github.com/python/typeshed/blob/64c85cdd449ccaff90b546676220c9ecfa6e697f/LICENSE
|
|
||||||
+
|
|
||||||
+# For updates: https://github.com/python/typeshed/blob/main/stdlib/contextlib.pyi
|
|
||||||
+
|
|
||||||
+# Last updated: 2024-05-22
|
|
||||||
+# Updated from: https://github.com/python/typeshed/blob/aa2d33df211e1e4f70883388febf750ac524d2bb/stdlib/contextlib.pyi
|
|
||||||
+# Saved to: dev/typeshed_contextlib.pyi
|
|
||||||
+
|
|
||||||
+# contextlib2 API adaptation notes:
|
|
||||||
+# * the various 'if True:' guards replace sys.version checks in the original
|
|
||||||
+# typeshed file (those APIs are available on all supported versions)
|
|
||||||
+# * any commented out 'if True:' guards replace sys.version checks in the original
|
|
||||||
+# typeshed file where the affected APIs haven't been backported yet
|
|
||||||
+# * deliberately omitted APIs are listed in `dev/mypy.allowlist`
|
|
||||||
+# (e.g. deprecated experimental APIs that never graduated to the stdlib)
|
|
||||||
+
|
|
||||||
import abc
|
|
||||||
import sys
|
|
||||||
from _typeshed import FileDescriptorOrPath, Unused
|
|
||||||
@@ -22,10 +39,10 @@
|
|
||||||
"nullcontext",
|
|
||||||
]
|
|
||||||
|
|
||||||
-if sys.version_info >= (3, 10):
|
|
||||||
+if True:
|
|
||||||
__all__ += ["aclosing"]
|
|
||||||
|
|
||||||
-if sys.version_info >= (3, 11):
|
|
||||||
+if True:
|
|
||||||
__all__ += ["chdir"]
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
|
||||||
@@ -65,18 +82,14 @@
|
|
||||||
func: Callable[..., Generator[_T_co, Any, Any]]
|
|
||||||
args: tuple[Any, ...]
|
|
||||||
kwds: dict[str, Any]
|
|
||||||
- if sys.version_info >= (3, 9):
|
|
||||||
+ if True:
|
|
||||||
def __exit__(
|
|
||||||
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
) -> bool | None: ...
|
|
||||||
- else:
|
|
||||||
- def __exit__(
|
|
||||||
- self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
- ) -> bool | None: ...
|
|
||||||
|
|
||||||
def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co]]: ...
|
|
||||||
|
|
||||||
-if sys.version_info >= (3, 10):
|
|
||||||
+if True:
|
|
||||||
_AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]])
|
|
||||||
|
|
||||||
class AsyncContextDecorator:
|
|
||||||
@@ -94,17 +107,6 @@
|
|
||||||
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
) -> bool | None: ...
|
|
||||||
|
|
||||||
-else:
|
|
||||||
- class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co, bool | None]):
|
|
||||||
- def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
|
|
||||||
- gen: AsyncGenerator[_T_co, Any]
|
|
||||||
- func: Callable[..., AsyncGenerator[_T_co, Any]]
|
|
||||||
- args: tuple[Any, ...]
|
|
||||||
- kwds: dict[str, Any]
|
|
||||||
- async def __aexit__(
|
|
||||||
- self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
- ) -> bool | None: ...
|
|
||||||
-
|
|
||||||
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T_co]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]: ...
|
|
||||||
|
|
||||||
class _SupportsClose(Protocol):
|
|
||||||
@@ -116,7 +118,7 @@
|
|
||||||
def __init__(self, thing: _SupportsCloseT) -> None: ...
|
|
||||||
def __exit__(self, *exc_info: Unused) -> None: ...
|
|
||||||
|
|
||||||
-if sys.version_info >= (3, 10):
|
|
||||||
+if True:
|
|
||||||
class _SupportsAclose(Protocol):
|
|
||||||
def aclose(self) -> Awaitable[object]: ...
|
|
||||||
|
|
||||||
@@ -177,7 +179,7 @@
|
|
||||||
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
|
||||||
) -> bool: ...
|
|
||||||
|
|
||||||
-if sys.version_info >= (3, 10):
|
|
||||||
+if True:
|
|
||||||
class nullcontext(AbstractContextManager[_T, None], AbstractAsyncContextManager[_T, None]):
|
|
||||||
enter_result: _T
|
|
||||||
@overload
|
|
||||||
@@ -189,17 +191,7 @@
|
|
||||||
async def __aenter__(self) -> _T: ...
|
|
||||||
async def __aexit__(self, *exctype: Unused) -> None: ...
|
|
||||||
|
|
||||||
-else:
|
|
||||||
- class nullcontext(AbstractContextManager[_T, None]):
|
|
||||||
- enter_result: _T
|
|
||||||
- @overload
|
|
||||||
- def __init__(self: nullcontext[None], enter_result: None = None) -> None: ...
|
|
||||||
- @overload
|
|
||||||
- def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780
|
|
||||||
- def __enter__(self) -> _T: ...
|
|
||||||
- def __exit__(self, *exctype: Unused) -> None: ...
|
|
||||||
-
|
|
||||||
-if sys.version_info >= (3, 11):
|
|
||||||
+if True:
|
|
||||||
_T_fd_or_any_path = TypeVar("_T_fd_or_any_path", bound=FileDescriptorOrPath)
|
|
||||||
|
|
||||||
class chdir(AbstractContextManager[None, None], Generic[_T_fd_or_any_path]):
|
|
||||||
|
|
@ -1,453 +0,0 @@
|
||||||
--- /home/ncoghlan/devel/contextlib2/../cpython/Doc/library/contextlib.rst 2024-05-20 12:53:59.936907756 +1000
|
|
||||||
+++ /home/ncoghlan/devel/contextlib2/docs/contextlib2.rst 2024-05-23 17:39:52.671083724 +1000
|
|
||||||
@@ -1,20 +1,5 @@
|
|
||||||
-:mod:`!contextlib` --- Utilities for :keyword:`!with`\ -statement contexts
|
|
||||||
-==========================================================================
|
|
||||||
-
|
|
||||||
-.. module:: contextlib
|
|
||||||
- :synopsis: Utilities for with-statement contexts.
|
|
||||||
-
|
|
||||||
-**Source code:** :source:`Lib/contextlib.py`
|
|
||||||
-
|
|
||||||
---------------
|
|
||||||
-
|
|
||||||
-This module provides utilities for common tasks involving the :keyword:`with`
|
|
||||||
-statement. For more information see also :ref:`typecontextmanager` and
|
|
||||||
-:ref:`context-managers`.
|
|
||||||
-
|
|
||||||
-
|
|
||||||
-Utilities
|
|
||||||
----------
|
|
||||||
+API Reference
|
|
||||||
+-------------
|
|
||||||
|
|
||||||
Functions and classes provided:
|
|
||||||
|
|
||||||
@@ -26,8 +11,8 @@
|
|
||||||
``self`` while :meth:`object.__exit__` is an abstract method which by default
|
|
||||||
returns ``None``. See also the definition of :ref:`typecontextmanager`.
|
|
||||||
|
|
||||||
- .. versionadded:: 3.6
|
|
||||||
-
|
|
||||||
+ .. versionadded:: 0.6.0
|
|
||||||
+ Part of the standard library in Python 3.6 and later
|
|
||||||
|
|
||||||
.. class:: AbstractAsyncContextManager
|
|
||||||
|
|
||||||
@@ -38,8 +23,8 @@
|
|
||||||
returns ``None``. See also the definition of
|
|
||||||
:ref:`async-context-managers`.
|
|
||||||
|
|
||||||
- .. versionadded:: 3.7
|
|
||||||
-
|
|
||||||
+ .. versionadded:: 21.6.0
|
|
||||||
+ Part of the standard library in Python 3.7 and later
|
|
||||||
|
|
||||||
.. decorator:: contextmanager
|
|
||||||
|
|
||||||
@@ -49,12 +34,12 @@
|
|
||||||
|
|
||||||
While many objects natively support use in with statements, sometimes a
|
|
||||||
resource needs to be managed that isn't a context manager in its own right,
|
|
||||||
- and doesn't implement a ``close()`` method for use with ``contextlib.closing``
|
|
||||||
+ and doesn't implement a ``close()`` method for use with ``contextlib2.closing``
|
|
||||||
|
|
||||||
An abstract example would be the following to ensure correct resource
|
|
||||||
management::
|
|
||||||
|
|
||||||
- from contextlib import contextmanager
|
|
||||||
+ from contextlib2 import contextmanager
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def managed_resource(*args, **kwds):
|
|
||||||
@@ -95,13 +80,10 @@
|
|
||||||
created by :func:`contextmanager` to meet the requirement that context
|
|
||||||
managers support multiple invocations in order to be used as decorators).
|
|
||||||
|
|
||||||
- .. versionchanged:: 3.2
|
|
||||||
- Use of :class:`ContextDecorator`.
|
|
||||||
-
|
|
||||||
|
|
||||||
.. decorator:: asynccontextmanager
|
|
||||||
|
|
||||||
- Similar to :func:`~contextlib.contextmanager`, but creates an
|
|
||||||
+ Similar to :func:`~contextlib2.contextmanager`, but creates an
|
|
||||||
:ref:`asynchronous context manager <async-context-managers>`.
|
|
||||||
|
|
||||||
This function is a :term:`decorator` that can be used to define a factory
|
|
||||||
@@ -112,7 +94,7 @@
|
|
||||||
|
|
||||||
A simple example::
|
|
||||||
|
|
||||||
- from contextlib import asynccontextmanager
|
|
||||||
+ from contextlib2 import asynccontextmanager
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def get_connection():
|
|
||||||
@@ -126,13 +108,16 @@
|
|
||||||
async with get_connection() as conn:
|
|
||||||
return conn.query('SELECT ...')
|
|
||||||
|
|
||||||
- .. versionadded:: 3.7
|
|
||||||
+ .. versionadded:: 21.6.0
|
|
||||||
+ Part of the standard library in Python 3.7 and later, enhanced in
|
|
||||||
+ Python 3.10 and later to allow created async context managers to be used
|
|
||||||
+ as async function decorators.
|
|
||||||
|
|
||||||
Context managers defined with :func:`asynccontextmanager` can be used
|
|
||||||
either as decorators or with :keyword:`async with` statements::
|
|
||||||
|
|
||||||
import time
|
|
||||||
- from contextlib import asynccontextmanager
|
|
||||||
+ from contextlib2 import asynccontextmanager
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def timeit():
|
|
||||||
@@ -151,17 +136,13 @@
|
|
||||||
created by :func:`asynccontextmanager` to meet the requirement that context
|
|
||||||
managers support multiple invocations in order to be used as decorators.
|
|
||||||
|
|
||||||
- .. versionchanged:: 3.10
|
|
||||||
- Async context managers created with :func:`asynccontextmanager` can
|
|
||||||
- be used as decorators.
|
|
||||||
-
|
|
||||||
|
|
||||||
.. function:: closing(thing)
|
|
||||||
|
|
||||||
Return a context manager that closes *thing* upon completion of the block. This
|
|
||||||
is basically equivalent to::
|
|
||||||
|
|
||||||
- from contextlib import contextmanager
|
|
||||||
+ from contextlib2 import contextmanager
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def closing(thing):
|
|
||||||
@@ -172,7 +153,7 @@
|
|
||||||
|
|
||||||
And lets you write code like this::
|
|
||||||
|
|
||||||
- from contextlib import closing
|
|
||||||
+ from contextlib2 import closing
|
|
||||||
from urllib.request import urlopen
|
|
||||||
|
|
||||||
with closing(urlopen('https://www.python.org')) as page:
|
|
||||||
@@ -196,7 +177,7 @@
|
|
||||||
Return an async context manager that calls the ``aclose()`` method of *thing*
|
|
||||||
upon completion of the block. This is basically equivalent to::
|
|
||||||
|
|
||||||
- from contextlib import asynccontextmanager
|
|
||||||
+ from contextlib2 import asynccontextmanager
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def aclosing(thing):
|
|
||||||
@@ -209,7 +190,7 @@
|
|
||||||
generators when they happen to exit early by :keyword:`break` or an
|
|
||||||
exception. For example::
|
|
||||||
|
|
||||||
- from contextlib import aclosing
|
|
||||||
+ from contextlib2 import aclosing
|
|
||||||
|
|
||||||
async with aclosing(my_generator()) as values:
|
|
||||||
async for value in values:
|
|
||||||
@@ -221,7 +202,8 @@
|
|
||||||
variables work as expected, and the exit code isn't run after the
|
|
||||||
lifetime of some task it depends on).
|
|
||||||
|
|
||||||
- .. versionadded:: 3.10
|
|
||||||
+ .. versionadded:: 21.6.0
|
|
||||||
+ Part of the standard library in Python 3.10 and later
|
|
||||||
|
|
||||||
|
|
||||||
.. _simplifying-support-for-single-optional-context-managers:
|
|
||||||
@@ -235,10 +217,10 @@
|
|
||||||
def myfunction(arg, ignore_exceptions=False):
|
|
||||||
if ignore_exceptions:
|
|
||||||
# Use suppress to ignore all exceptions.
|
|
||||||
- cm = contextlib.suppress(Exception)
|
|
||||||
+ cm = contextlib2.suppress(Exception)
|
|
||||||
else:
|
|
||||||
# Do not ignore any exceptions, cm has no effect.
|
|
||||||
- cm = contextlib.nullcontext()
|
|
||||||
+ cm = contextlib2.nullcontext()
|
|
||||||
with cm:
|
|
||||||
# Do something
|
|
||||||
|
|
||||||
@@ -269,11 +251,11 @@
|
|
||||||
async with cm as session:
|
|
||||||
# Send http requests with session
|
|
||||||
|
|
||||||
- .. versionadded:: 3.7
|
|
||||||
-
|
|
||||||
- .. versionchanged:: 3.10
|
|
||||||
- :term:`asynchronous context manager` support was added.
|
|
||||||
+ .. versionadded:: 0.6.0
|
|
||||||
+ Part of the standard library in Python 3.7 and later
|
|
||||||
|
|
||||||
+ .. versionchanged:: 21.6.0
|
|
||||||
+ Updated to Python 3.10 version with :term:`asynchronous context manager` support
|
|
||||||
|
|
||||||
|
|
||||||
.. function:: suppress(*exceptions)
|
|
||||||
@@ -290,7 +272,7 @@
|
|
||||||
|
|
||||||
For example::
|
|
||||||
|
|
||||||
- from contextlib import suppress
|
|
||||||
+ from contextlib2 import suppress
|
|
||||||
|
|
||||||
with suppress(FileNotFoundError):
|
|
||||||
os.remove('somefile.tmp')
|
|
||||||
@@ -314,13 +296,15 @@
|
|
||||||
|
|
||||||
If the code within the :keyword:`!with` block raises a
|
|
||||||
:exc:`BaseExceptionGroup`, suppressed exceptions are removed from the
|
|
||||||
- group. If any exceptions in the group are not suppressed, a group containing them is re-raised.
|
|
||||||
+ group. If any exceptions in the group are not suppressed, a group containing
|
|
||||||
+ them is re-raised.
|
|
||||||
|
|
||||||
- .. versionadded:: 3.4
|
|
||||||
+ .. versionadded:: 0.5
|
|
||||||
+ Part of the standard library in Python 3.4 and later
|
|
||||||
|
|
||||||
- .. versionchanged:: 3.12
|
|
||||||
- ``suppress`` now supports suppressing exceptions raised as
|
|
||||||
- part of an :exc:`BaseExceptionGroup`.
|
|
||||||
+ .. versionchanged:: 24.6.0
|
|
||||||
+ Updated to Python 3.12 version that supports suppressing exceptions raised
|
|
||||||
+ as part of a :exc:`BaseExceptionGroup`.
|
|
||||||
|
|
||||||
.. function:: redirect_stdout(new_target)
|
|
||||||
|
|
||||||
@@ -359,17 +343,19 @@
|
|
||||||
|
|
||||||
This context manager is :ref:`reentrant <reentrant-cms>`.
|
|
||||||
|
|
||||||
- .. versionadded:: 3.4
|
|
||||||
+ .. versionadded:: 0.5
|
|
||||||
+ Part of the standard library in Python 3.4 and later
|
|
||||||
|
|
||||||
|
|
||||||
.. function:: redirect_stderr(new_target)
|
|
||||||
|
|
||||||
- Similar to :func:`~contextlib.redirect_stdout` but redirecting
|
|
||||||
+ Similar to :func:`~contextlib2.redirect_stdout` but redirecting
|
|
||||||
:data:`sys.stderr` to another file or file-like object.
|
|
||||||
|
|
||||||
This context manager is :ref:`reentrant <reentrant-cms>`.
|
|
||||||
|
|
||||||
- .. versionadded:: 3.5
|
|
||||||
+ .. versionadded:: 0.5
|
|
||||||
+ Part of the standard library in Python 3.5 and later
|
|
||||||
|
|
||||||
|
|
||||||
.. function:: chdir(path)
|
|
||||||
@@ -386,7 +372,8 @@
|
|
||||||
|
|
||||||
This context manager is :ref:`reentrant <reentrant-cms>`.
|
|
||||||
|
|
||||||
- .. versionadded:: 3.11
|
|
||||||
+ .. versionadded:: 24.6.0
|
|
||||||
+ Part of the standard library in Python 3.11 and later
|
|
||||||
|
|
||||||
|
|
||||||
.. class:: ContextDecorator()
|
|
||||||
@@ -402,7 +389,7 @@
|
|
||||||
|
|
||||||
Example of ``ContextDecorator``::
|
|
||||||
|
|
||||||
- from contextlib import ContextDecorator
|
|
||||||
+ from contextlib2 import ContextDecorator
|
|
||||||
|
|
||||||
class mycontext(ContextDecorator):
|
|
||||||
def __enter__(self):
|
|
||||||
@@ -449,7 +436,7 @@
|
|
||||||
Existing context managers that already have a base class can be extended by
|
|
||||||
using ``ContextDecorator`` as a mixin class::
|
|
||||||
|
|
||||||
- from contextlib import ContextDecorator
|
|
||||||
+ from contextlib2 import ContextDecorator
|
|
||||||
|
|
||||||
class mycontext(ContextBaseClass, ContextDecorator):
|
|
||||||
def __enter__(self):
|
|
||||||
@@ -464,8 +451,6 @@
|
|
||||||
statements. If this is not the case, then the original construct with the
|
|
||||||
explicit :keyword:`!with` statement inside the function should be used.
|
|
||||||
|
|
||||||
- .. versionadded:: 3.2
|
|
||||||
-
|
|
||||||
|
|
||||||
.. class:: AsyncContextDecorator
|
|
||||||
|
|
||||||
@@ -474,7 +459,7 @@
|
|
||||||
Example of ``AsyncContextDecorator``::
|
|
||||||
|
|
||||||
from asyncio import run
|
|
||||||
- from contextlib import AsyncContextDecorator
|
|
||||||
+ from contextlib2 import AsyncContextDecorator
|
|
||||||
|
|
||||||
class mycontext(AsyncContextDecorator):
|
|
||||||
async def __aenter__(self):
|
|
||||||
@@ -505,7 +490,8 @@
|
|
||||||
The bit in the middle
|
|
||||||
Finishing
|
|
||||||
|
|
||||||
- .. versionadded:: 3.10
|
|
||||||
+ .. versionadded:: 21.6.0
|
|
||||||
+ Part of the standard library in Python 3.10 and later
|
|
||||||
|
|
||||||
|
|
||||||
.. class:: ExitStack()
|
|
||||||
@@ -547,7 +533,8 @@
|
|
||||||
foundation for higher level context managers that manipulate the exit
|
|
||||||
stack in application specific ways.
|
|
||||||
|
|
||||||
- .. versionadded:: 3.3
|
|
||||||
+ .. versionadded:: 0.4
|
|
||||||
+ Part of the standard library in Python 3.3 and later
|
|
||||||
|
|
||||||
.. method:: enter_context(cm)
|
|
||||||
|
|
||||||
@@ -558,9 +545,10 @@
|
|
||||||
These context managers may suppress exceptions just as they normally
|
|
||||||
would if used directly as part of a :keyword:`with` statement.
|
|
||||||
|
|
||||||
- .. versionchanged:: 3.11
|
|
||||||
- Raises :exc:`TypeError` instead of :exc:`AttributeError` if *cm*
|
|
||||||
- is not a context manager.
|
|
||||||
+ .. versionchanged:: 24.6.0
|
|
||||||
+ When running on Python 3.11 or later, raises :exc:`TypeError` instead
|
|
||||||
+ of :exc:`AttributeError` if *cm* is not a context manager. This aligns
|
|
||||||
+ with the behaviour of :keyword:`with` statements in Python 3.11+.
|
|
||||||
|
|
||||||
.. method:: push(exit)
|
|
||||||
|
|
||||||
@@ -627,14 +615,16 @@
|
|
||||||
The :meth:`~ExitStack.close` method is not implemented; :meth:`aclose` must be used
|
|
||||||
instead.
|
|
||||||
|
|
||||||
- .. coroutinemethod:: enter_async_context(cm)
|
|
||||||
+ .. method:: enter_async_context(cm)
|
|
||||||
+ :async:
|
|
||||||
|
|
||||||
Similar to :meth:`ExitStack.enter_context` but expects an asynchronous context
|
|
||||||
manager.
|
|
||||||
|
|
||||||
- .. versionchanged:: 3.11
|
|
||||||
- Raises :exc:`TypeError` instead of :exc:`AttributeError` if *cm*
|
|
||||||
- is not an asynchronous context manager.
|
|
||||||
+ .. versionchanged:: 24.6.0
|
|
||||||
+ When running on Python 3.11 or later, raises :exc:`TypeError` instead
|
|
||||||
+ of :exc:`AttributeError` if *cm* is not an asynchronous context manager.
|
|
||||||
+ This aligns with the behaviour of ``async with`` statements in Python 3.11+.
|
|
||||||
|
|
||||||
.. method:: push_async_exit(exit)
|
|
||||||
|
|
||||||
@@ -645,7 +635,8 @@
|
|
||||||
|
|
||||||
Similar to :meth:`ExitStack.callback` but expects a coroutine function.
|
|
||||||
|
|
||||||
- .. coroutinemethod:: aclose()
|
|
||||||
+ .. method:: aclose()
|
|
||||||
+ :async:
|
|
||||||
|
|
||||||
Similar to :meth:`ExitStack.close` but properly handles awaitables.
|
|
||||||
|
|
||||||
@@ -658,13 +649,15 @@
|
|
||||||
# the async with statement, even if attempts to open a connection
|
|
||||||
# later in the list raise an exception.
|
|
||||||
|
|
||||||
- .. versionadded:: 3.7
|
|
||||||
+ .. versionadded:: 21.6.0
|
|
||||||
+ Part of the standard library in Python 3.7 and later
|
|
||||||
+
|
|
||||||
|
|
||||||
Examples and Recipes
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
This section describes some examples and recipes for making effective use of
|
|
||||||
-the tools provided by :mod:`contextlib`.
|
|
||||||
+the tools provided by :mod:`contextlib2`.
|
|
||||||
|
|
||||||
|
|
||||||
Supporting a variable number of context managers
|
|
||||||
@@ -728,7 +721,7 @@
|
|
||||||
acquisition and release functions, along with an optional validation function,
|
|
||||||
and maps them to the context management protocol::
|
|
||||||
|
|
||||||
- from contextlib import contextmanager, AbstractContextManager, ExitStack
|
|
||||||
+ from contextlib2 import contextmanager, AbstractContextManager, ExitStack
|
|
||||||
|
|
||||||
class ResourceManager(AbstractContextManager):
|
|
||||||
|
|
||||||
@@ -788,7 +781,7 @@
|
|
||||||
execution at the end of a ``with`` statement, and then later decide to skip
|
|
||||||
executing that callback::
|
|
||||||
|
|
||||||
- from contextlib import ExitStack
|
|
||||||
+ from contextlib2 import ExitStack
|
|
||||||
|
|
||||||
with ExitStack() as stack:
|
|
||||||
stack.callback(cleanup_resources)
|
|
||||||
@@ -802,7 +795,7 @@
|
|
||||||
If a particular application uses this pattern a lot, it can be simplified
|
|
||||||
even further by means of a small helper class::
|
|
||||||
|
|
||||||
- from contextlib import ExitStack
|
|
||||||
+ from contextlib2 import ExitStack
|
|
||||||
|
|
||||||
class Callback(ExitStack):
|
|
||||||
def __init__(self, callback, /, *args, **kwds):
|
|
||||||
@@ -822,7 +815,7 @@
|
|
||||||
:meth:`ExitStack.callback` to declare the resource cleanup in
|
|
||||||
advance::
|
|
||||||
|
|
||||||
- from contextlib import ExitStack
|
|
||||||
+ from contextlib2 import ExitStack
|
|
||||||
|
|
||||||
with ExitStack() as stack:
|
|
||||||
@stack.callback
|
|
||||||
@@ -849,7 +842,7 @@
|
|
||||||
inheriting from :class:`ContextDecorator` provides both capabilities in a
|
|
||||||
single definition::
|
|
||||||
|
|
||||||
- from contextlib import ContextDecorator
|
|
||||||
+ from contextlib2 import ContextDecorator
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
@@ -911,7 +904,7 @@
|
|
||||||
context managers, and will complain about the underlying generator failing
|
|
||||||
to yield if an attempt is made to use them a second time::
|
|
||||||
|
|
||||||
- >>> from contextlib import contextmanager
|
|
||||||
+ >>> from contextlib2 import contextmanager
|
|
||||||
>>> @contextmanager
|
|
||||||
... def singleuse():
|
|
||||||
... print("Before")
|
|
||||||
@@ -946,7 +939,7 @@
|
|
||||||
:func:`suppress`, :func:`redirect_stdout`, and :func:`chdir`. Here's a very
|
|
||||||
simple example of reentrant use::
|
|
||||||
|
|
||||||
- >>> from contextlib import redirect_stdout
|
|
||||||
+ >>> from contextlib2 import redirect_stdout
|
|
||||||
>>> from io import StringIO
|
|
||||||
>>> stream = StringIO()
|
|
||||||
>>> write_to_stream = redirect_stdout(stream)
|
|
||||||
@@ -992,7 +985,7 @@
|
|
||||||
when leaving any with statement, regardless of where those callbacks
|
|
||||||
were added::
|
|
||||||
|
|
||||||
- >>> from contextlib import ExitStack
|
|
||||||
+ >>> from contextlib2 import ExitStack
|
|
||||||
>>> stack = ExitStack()
|
|
||||||
>>> with stack:
|
|
||||||
... stack.callback(print, "Callback: from first context")
|
|
||||||
@@ -1026,7 +1019,7 @@
|
|
||||||
Using separate :class:`ExitStack` instances instead of reusing a single
|
|
||||||
instance avoids that problem::
|
|
||||||
|
|
||||||
- >>> from contextlib import ExitStack
|
|
||||||
+ >>> from contextlib2 import ExitStack
|
|
||||||
>>> with ExitStack() as outer_stack:
|
|
||||||
... outer_stack.callback(print, "Callback: from outer context")
|
|
||||||
... with ExitStack() as inner_stack:
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
--- /home/ncoghlan/devel/contextlib2/../cpython/Lib/test/test_contextlib_async.py 2024-05-23 11:57:09.276022441 +1000
|
|
||||||
+++ /home/ncoghlan/devel/contextlib2/test/test_contextlib_async.py 2024-05-23 17:39:05.799797895 +1000
|
|
||||||
@@ -1,5 +1,7 @@
|
|
||||||
+"""Unit tests for asynchronous features of contextlib2.py"""
|
|
||||||
+
|
|
||||||
import asyncio
|
|
||||||
-from contextlib import (
|
|
||||||
+from contextlib2 import (
|
|
||||||
asynccontextmanager, AbstractAsyncContextManager,
|
|
||||||
AsyncExitStack, nullcontext, aclosing, contextmanager)
|
|
||||||
import functools
|
|
||||||
@@ -7,7 +9,7 @@
|
|
||||||
import unittest
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
-from test.test_contextlib import TestBaseExitStack
|
|
||||||
+from .test_contextlib import TestBaseExitStack
|
|
||||||
|
|
||||||
support.requires_working_socket(module=True)
|
|
||||||
|
|
||||||
@@ -202,7 +204,8 @@
|
|
||||||
await ctx.__aexit__(TypeError, TypeError('foo'), None)
|
|
||||||
if support.check_impl_detail(cpython=True):
|
|
||||||
# The "gen" attribute is an implementation detail.
|
|
||||||
- self.assertFalse(ctx.gen.ag_suspended)
|
|
||||||
+ if support.cl2_async_gens_have_ag_suspended:
|
|
||||||
+ self.assertFalse(ctx.gen.ag_suspended)
|
|
||||||
|
|
||||||
@_async_test
|
|
||||||
async def test_contextmanager_trap_no_yield(self):
|
|
||||||
@@ -226,7 +229,8 @@
|
|
||||||
await ctx.__aexit__(None, None, None)
|
|
||||||
if support.check_impl_detail(cpython=True):
|
|
||||||
# The "gen" attribute is an implementation detail.
|
|
||||||
- self.assertFalse(ctx.gen.ag_suspended)
|
|
||||||
+ if support.cl2_async_gens_have_ag_suspended:
|
|
||||||
+ self.assertFalse(ctx.gen.ag_suspended)
|
|
||||||
|
|
||||||
@_async_test
|
|
||||||
async def test_contextmanager_non_normalised(self):
|
|
||||||
@@ -669,12 +673,13 @@
|
|
||||||
async def __aenter__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
+ expected_error, expected_text = support.cl2_cm_api_exc_info_async()
|
|
||||||
async with self.exit_stack() as stack:
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
await stack.enter_async_context(LacksEnterAndExit())
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
await stack.enter_async_context(LacksEnter())
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
await stack.enter_async_context(LacksExit())
|
|
||||||
self.assertFalse(stack._exit_callbacks)
|
|
||||||
|
|
||||||
@@ -752,7 +757,8 @@
|
|
||||||
cm.__aenter__ = object()
|
|
||||||
cm.__aexit__ = object()
|
|
||||||
stack = self.exit_stack()
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
|
|
||||||
+ expected_error, expected_text = support.cl2_cm_api_exc_info_async()
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
await stack.enter_async_context(cm)
|
|
||||||
stack.push_async_exit(cm)
|
|
||||||
self.assertIs(stack._exit_callbacks[-1][1], cm)
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
--- /home/ncoghlan/devel/contextlib2/../cpython/Lib/test/test_contextlib.py 2024-05-23 11:57:09.276022441 +1000
|
|
||||||
+++ /home/ncoghlan/devel/contextlib2/test/test_contextlib.py 2024-05-23 17:38:37.295232213 +1000
|
|
||||||
@@ -1,4 +1,4 @@
|
|
||||||
-"""Unit tests for contextlib.py, and other context managers."""
|
|
||||||
+"""Unit tests for synchronous features of contextlib2.py"""
|
|
||||||
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
@@ -7,7 +7,7 @@
|
|
||||||
import threading
|
|
||||||
import traceback
|
|
||||||
import unittest
|
|
||||||
-from contextlib import * # Tests __all__
|
|
||||||
+from contextlib2 import * # Tests __all__
|
|
||||||
from test import support
|
|
||||||
from test.support import os_helper
|
|
||||||
from test.support.testcase import ExceptionIsLikeMixin
|
|
||||||
@@ -161,7 +161,8 @@
|
|
||||||
ctx.__exit__(TypeError, TypeError("foo"), None)
|
|
||||||
if support.check_impl_detail(cpython=True):
|
|
||||||
# The "gen" attribute is an implementation detail.
|
|
||||||
- self.assertFalse(ctx.gen.gi_suspended)
|
|
||||||
+ if support.cl2_gens_have_gi_suspended:
|
|
||||||
+ self.assertFalse(ctx.gen.gi_suspended)
|
|
||||||
|
|
||||||
def test_contextmanager_trap_no_yield(self):
|
|
||||||
@contextmanager
|
|
||||||
@@ -183,7 +184,8 @@
|
|
||||||
ctx.__exit__(None, None, None)
|
|
||||||
if support.check_impl_detail(cpython=True):
|
|
||||||
# The "gen" attribute is an implementation detail.
|
|
||||||
- self.assertFalse(ctx.gen.gi_suspended)
|
|
||||||
+ if support.cl2_gens_have_gi_suspended:
|
|
||||||
+ self.assertFalse(ctx.gen.gi_suspended)
|
|
||||||
|
|
||||||
def test_contextmanager_non_normalised(self):
|
|
||||||
@contextmanager
|
|
||||||
@@ -610,7 +612,8 @@
|
|
||||||
def __exit__(self, *exc):
|
|
||||||
pass
|
|
||||||
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'the context manager'):
|
|
||||||
+ expected_error, expected_text = support.cl2_cm_api_exc_info_sync("__enter__")
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
with mycontext():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@@ -622,7 +625,8 @@
|
|
||||||
def __uxit__(self, *exc):
|
|
||||||
pass
|
|
||||||
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'the context manager.*__exit__'):
|
|
||||||
+ expected_error, expected_text = support.cl2_cm_api_exc_info_sync("__exit__")
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
with mycontext():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@@ -790,12 +794,13 @@
|
|
||||||
def __enter__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
+ expected_error, expected_text = support.cl2_cm_api_exc_info_sync()
|
|
||||||
with self.exit_stack() as stack:
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'the context manager'):
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
stack.enter_context(LacksEnterAndExit())
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'the context manager'):
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
stack.enter_context(LacksEnter())
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'the context manager'):
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
stack.enter_context(LacksExit())
|
|
||||||
self.assertFalse(stack._exit_callbacks)
|
|
||||||
|
|
||||||
@@ -858,8 +863,11 @@
|
|
||||||
[('_exit_wrapper', 'callback(*args, **kwds)'),
|
|
||||||
('raise_exc', 'raise exc')]
|
|
||||||
|
|
||||||
- self.assertEqual(
|
|
||||||
- [(f.name, f.line) for f in ve_frames], expected)
|
|
||||||
+ # This check fails on PyPy 3.10
|
|
||||||
+ # It also fails on CPython 3.9 and earlier versions
|
|
||||||
+ if support.check_impl_detail(cpython=True) and support.cl2_check_traceback_details:
|
|
||||||
+ self.assertEqual(
|
|
||||||
+ [(f.name, f.line) for f in ve_frames], expected)
|
|
||||||
|
|
||||||
self.assertIsInstance(exc.__context__, ZeroDivisionError)
|
|
||||||
zde_frames = traceback.extract_tb(exc.__context__.__traceback__)
|
|
||||||
@@ -1093,7 +1101,8 @@
|
|
||||||
cm.__enter__ = object()
|
|
||||||
cm.__exit__ = object()
|
|
||||||
stack = self.exit_stack()
|
|
||||||
- with self.assertRaisesRegex(TypeError, 'the context manager'):
|
|
||||||
+ expected_error, expected_text = support.cl2_cm_api_exc_info_sync()
|
|
||||||
+ with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
stack.enter_context(cm)
|
|
||||||
stack.push(cm)
|
|
||||||
self.assertIs(stack._exit_callbacks[-1][1], cm)
|
|
||||||
@@ -1264,6 +1273,7 @@
|
|
||||||
1/0
|
|
||||||
self.assertTrue(outer_continued)
|
|
||||||
|
|
||||||
+ @support.cl2_requires_exception_groups
|
|
||||||
def test_exception_groups(self):
|
|
||||||
eg_ve = lambda: ExceptionGroup(
|
|
||||||
"EG with ValueErrors only",
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
git_root="$(git rev-parse --show-toplevel)"
|
|
||||||
|
|
||||||
cpython_dir="${1:-$git_root/../cpython}"
|
|
||||||
|
|
||||||
diff_prefix="py3_12" # Update based on the version being synced
|
|
||||||
|
|
||||||
function diff_file()
|
|
||||||
{
|
|
||||||
diff -ud "$2" "$git_root/$3" > "$git_root/dev/${diff_prefix}_$1.patch"
|
|
||||||
}
|
|
||||||
|
|
||||||
diff_file rst_to_contextlib2 \
|
|
||||||
"$cpython_dir/Doc/library/contextlib.rst" "docs/contextlib2.rst"
|
|
||||||
|
|
||||||
diff_file py_to_contextlib2 \
|
|
||||||
"$cpython_dir/Lib/contextlib.py" "contextlib2/__init__.py"
|
|
||||||
|
|
||||||
diff_file pyi_to_contextlib2 \
|
|
||||||
"$git_root/dev/typeshed_contextlib.pyi" "contextlib2/__init__.pyi"
|
|
||||||
|
|
||||||
diff_file test_to_contextlib2 \
|
|
||||||
"$cpython_dir/Lib/test/test_contextlib.py" "test/test_contextlib.py"
|
|
||||||
|
|
||||||
diff_file test_async_to_contextlib2 \
|
|
||||||
"$cpython_dir/Lib/test/test_contextlib_async.py" "test/test_contextlib_async.py"
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
git_root="$(git rev-parse --show-toplevel)"
|
|
||||||
|
|
||||||
cpython_dir="${1:-$git_root/../cpython}" # Folder with relevant CPython version
|
|
||||||
|
|
||||||
function sync_file()
|
|
||||||
{
|
|
||||||
cp -fv "$cpython_dir/$1" "$git_root/$2"
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_file "Doc/library/contextlib.rst" "docs/contextlib2.rst"
|
|
||||||
sync_file "Lib/contextlib.py" "contextlib2/__init__.py"
|
|
||||||
sync_file "Lib/test/test_contextlib.py" "test/test_contextlib.py"
|
|
||||||
sync_file "Lib/test/test_contextlib_async.py" "test/test_contextlib_async.py"
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "Note: Update the 'contextlib2/__init__.pyi' stub as described in the file"
|
|
||||||
echo
|
|
||||||
|
|
@ -1,209 +0,0 @@
|
||||||
import abc
|
|
||||||
import sys
|
|
||||||
from _typeshed import FileDescriptorOrPath, Unused
|
|
||||||
from abc import abstractmethod
|
|
||||||
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Generator, Iterator
|
|
||||||
from types import TracebackType
|
|
||||||
from typing import IO, Any, Generic, Protocol, TypeVar, overload, runtime_checkable
|
|
||||||
from typing_extensions import ParamSpec, Self, TypeAlias
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"contextmanager",
|
|
||||||
"closing",
|
|
||||||
"AbstractContextManager",
|
|
||||||
"ContextDecorator",
|
|
||||||
"ExitStack",
|
|
||||||
"redirect_stdout",
|
|
||||||
"redirect_stderr",
|
|
||||||
"suppress",
|
|
||||||
"AbstractAsyncContextManager",
|
|
||||||
"AsyncExitStack",
|
|
||||||
"asynccontextmanager",
|
|
||||||
"nullcontext",
|
|
||||||
]
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
|
||||||
__all__ += ["aclosing"]
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
|
||||||
__all__ += ["chdir"]
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
|
||||||
_T_co = TypeVar("_T_co", covariant=True)
|
|
||||||
_T_io = TypeVar("_T_io", bound=IO[str] | None)
|
|
||||||
_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound=bool | None, default=bool | None)
|
|
||||||
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
||||||
_P = ParamSpec("_P")
|
|
||||||
|
|
||||||
_ExitFunc: TypeAlias = Callable[[type[BaseException] | None, BaseException | None, TracebackType | None], bool | None]
|
|
||||||
_CM_EF = TypeVar("_CM_EF", bound=AbstractContextManager[Any, Any] | _ExitFunc)
|
|
||||||
|
|
||||||
@runtime_checkable
|
|
||||||
class AbstractContextManager(Protocol[_T_co, _ExitT_co]):
|
|
||||||
def __enter__(self) -> _T_co: ...
|
|
||||||
@abstractmethod
|
|
||||||
def __exit__(
|
|
||||||
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
|
||||||
) -> _ExitT_co: ...
|
|
||||||
|
|
||||||
@runtime_checkable
|
|
||||||
class AbstractAsyncContextManager(Protocol[_T_co, _ExitT_co]):
|
|
||||||
async def __aenter__(self) -> _T_co: ...
|
|
||||||
@abstractmethod
|
|
||||||
async def __aexit__(
|
|
||||||
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
|
||||||
) -> _ExitT_co: ...
|
|
||||||
|
|
||||||
class ContextDecorator:
|
|
||||||
def __call__(self, func: _F) -> _F: ...
|
|
||||||
|
|
||||||
class _GeneratorContextManager(AbstractContextManager[_T_co, bool | None], ContextDecorator):
|
|
||||||
# __init__ and all instance attributes are actually inherited from _GeneratorContextManagerBase
|
|
||||||
# _GeneratorContextManagerBase is more trouble than it's worth to include in the stub; see #6676
|
|
||||||
def __init__(self, func: Callable[..., Iterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
|
|
||||||
gen: Generator[_T_co, Any, Any]
|
|
||||||
func: Callable[..., Generator[_T_co, Any, Any]]
|
|
||||||
args: tuple[Any, ...]
|
|
||||||
kwds: dict[str, Any]
|
|
||||||
if sys.version_info >= (3, 9):
|
|
||||||
def __exit__(
|
|
||||||
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
) -> bool | None: ...
|
|
||||||
else:
|
|
||||||
def __exit__(
|
|
||||||
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
) -> bool | None: ...
|
|
||||||
|
|
||||||
def contextmanager(func: Callable[_P, Iterator[_T_co]]) -> Callable[_P, _GeneratorContextManager[_T_co]]: ...
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
|
||||||
_AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]])
|
|
||||||
|
|
||||||
class AsyncContextDecorator:
|
|
||||||
def __call__(self, func: _AF) -> _AF: ...
|
|
||||||
|
|
||||||
class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co, bool | None], AsyncContextDecorator):
|
|
||||||
# __init__ and these attributes are actually defined in the base class _GeneratorContextManagerBase,
|
|
||||||
# which is more trouble than it's worth to include in the stub (see #6676)
|
|
||||||
def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
|
|
||||||
gen: AsyncGenerator[_T_co, Any]
|
|
||||||
func: Callable[..., AsyncGenerator[_T_co, Any]]
|
|
||||||
args: tuple[Any, ...]
|
|
||||||
kwds: dict[str, Any]
|
|
||||||
async def __aexit__(
|
|
||||||
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
) -> bool | None: ...
|
|
||||||
|
|
||||||
else:
|
|
||||||
class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co, bool | None]):
|
|
||||||
def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
|
|
||||||
gen: AsyncGenerator[_T_co, Any]
|
|
||||||
func: Callable[..., AsyncGenerator[_T_co, Any]]
|
|
||||||
args: tuple[Any, ...]
|
|
||||||
kwds: dict[str, Any]
|
|
||||||
async def __aexit__(
|
|
||||||
self, typ: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
||||||
) -> bool | None: ...
|
|
||||||
|
|
||||||
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T_co]]) -> Callable[_P, _AsyncGeneratorContextManager[_T_co]]: ...
|
|
||||||
|
|
||||||
class _SupportsClose(Protocol):
|
|
||||||
def close(self) -> object: ...
|
|
||||||
|
|
||||||
_SupportsCloseT = TypeVar("_SupportsCloseT", bound=_SupportsClose)
|
|
||||||
|
|
||||||
class closing(AbstractContextManager[_SupportsCloseT, None]):
|
|
||||||
def __init__(self, thing: _SupportsCloseT) -> None: ...
|
|
||||||
def __exit__(self, *exc_info: Unused) -> None: ...
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
|
||||||
class _SupportsAclose(Protocol):
|
|
||||||
def aclose(self) -> Awaitable[object]: ...
|
|
||||||
|
|
||||||
_SupportsAcloseT = TypeVar("_SupportsAcloseT", bound=_SupportsAclose)
|
|
||||||
|
|
||||||
class aclosing(AbstractAsyncContextManager[_SupportsAcloseT, None]):
|
|
||||||
def __init__(self, thing: _SupportsAcloseT) -> None: ...
|
|
||||||
async def __aexit__(self, *exc_info: Unused) -> None: ...
|
|
||||||
|
|
||||||
class suppress(AbstractContextManager[None, bool]):
|
|
||||||
def __init__(self, *exceptions: type[BaseException]) -> None: ...
|
|
||||||
def __exit__(
|
|
||||||
self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None
|
|
||||||
) -> bool: ...
|
|
||||||
|
|
||||||
class _RedirectStream(AbstractContextManager[_T_io, None]):
|
|
||||||
def __init__(self, new_target: _T_io) -> None: ...
|
|
||||||
def __exit__(
|
|
||||||
self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None
|
|
||||||
) -> None: ...
|
|
||||||
|
|
||||||
class redirect_stdout(_RedirectStream[_T_io]): ...
|
|
||||||
class redirect_stderr(_RedirectStream[_T_io]): ...
|
|
||||||
|
|
||||||
# In reality this is a subclass of `AbstractContextManager`;
|
|
||||||
# see #7961 for why we don't do that in the stub
|
|
||||||
class ExitStack(Generic[_ExitT_co], metaclass=abc.ABCMeta):
|
|
||||||
def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ...
|
|
||||||
def push(self, exit: _CM_EF) -> _CM_EF: ...
|
|
||||||
def callback(self, callback: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ...
|
|
||||||
def pop_all(self) -> Self: ...
|
|
||||||
def close(self) -> None: ...
|
|
||||||
def __enter__(self) -> Self: ...
|
|
||||||
def __exit__(
|
|
||||||
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
|
||||||
) -> _ExitT_co: ...
|
|
||||||
|
|
||||||
_ExitCoroFunc: TypeAlias = Callable[
|
|
||||||
[type[BaseException] | None, BaseException | None, TracebackType | None], Awaitable[bool | None]
|
|
||||||
]
|
|
||||||
_ACM_EF = TypeVar("_ACM_EF", bound=AbstractAsyncContextManager[Any, Any] | _ExitCoroFunc)
|
|
||||||
|
|
||||||
# In reality this is a subclass of `AbstractAsyncContextManager`;
|
|
||||||
# see #7961 for why we don't do that in the stub
|
|
||||||
class AsyncExitStack(Generic[_ExitT_co], metaclass=abc.ABCMeta):
|
|
||||||
def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ...
|
|
||||||
async def enter_async_context(self, cm: AbstractAsyncContextManager[_T, _ExitT_co]) -> _T: ...
|
|
||||||
def push(self, exit: _CM_EF) -> _CM_EF: ...
|
|
||||||
def push_async_exit(self, exit: _ACM_EF) -> _ACM_EF: ...
|
|
||||||
def callback(self, callback: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ...
|
|
||||||
def push_async_callback(
|
|
||||||
self, callback: Callable[_P, Awaitable[_T]], /, *args: _P.args, **kwds: _P.kwargs
|
|
||||||
) -> Callable[_P, Awaitable[_T]]: ...
|
|
||||||
def pop_all(self) -> Self: ...
|
|
||||||
async def aclose(self) -> None: ...
|
|
||||||
async def __aenter__(self) -> Self: ...
|
|
||||||
async def __aexit__(
|
|
||||||
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
|
||||||
) -> bool: ...
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
|
||||||
class nullcontext(AbstractContextManager[_T, None], AbstractAsyncContextManager[_T, None]):
|
|
||||||
enter_result: _T
|
|
||||||
@overload
|
|
||||||
def __init__(self: nullcontext[None], enter_result: None = None) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780
|
|
||||||
def __enter__(self) -> _T: ...
|
|
||||||
def __exit__(self, *exctype: Unused) -> None: ...
|
|
||||||
async def __aenter__(self) -> _T: ...
|
|
||||||
async def __aexit__(self, *exctype: Unused) -> None: ...
|
|
||||||
|
|
||||||
else:
|
|
||||||
class nullcontext(AbstractContextManager[_T, None]):
|
|
||||||
enter_result: _T
|
|
||||||
@overload
|
|
||||||
def __init__(self: nullcontext[None], enter_result: None = None) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780
|
|
||||||
def __enter__(self) -> _T: ...
|
|
||||||
def __exit__(self, *exctype: Unused) -> None: ...
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
|
||||||
_T_fd_or_any_path = TypeVar("_T_fd_or_any_path", bound=FileDescriptorOrPath)
|
|
||||||
|
|
||||||
class chdir(AbstractContextManager[None, None], Generic[_T_fd_or_any_path]):
|
|
||||||
path: _T_fd_or_any_path
|
|
||||||
def __init__(self, path: _T_fd_or_any_path) -> None: ...
|
|
||||||
def __enter__(self) -> None: ...
|
|
||||||
def __exit__(self, *excinfo: Unused) -> None: ...
|
|
||||||
19
docs/conf.py
19
docs/conf.py
|
|
@ -11,6 +11,8 @@
|
||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
|
@ -23,10 +25,7 @@
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = [
|
extensions = ['sphinx.ext.intersphinx']
|
||||||
'sphinx.ext.intersphinx',
|
|
||||||
'sphinx_rtd_theme',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
|
@ -42,7 +41,7 @@ master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'contextlib2'
|
project = u'contextlib2'
|
||||||
copyright = u'2024, Alyssa Coghlan'
|
copyright = u'2021, Nick Coghlan'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
|
@ -93,7 +92,7 @@ pygments_style = 'sphinx'
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'sphinx_rtd_theme'
|
html_theme = 'default'
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
|
|
@ -122,7 +121,7 @@ html_theme = 'sphinx_rtd_theme'
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = []
|
html_static_path = ['_static']
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
|
|
@ -181,7 +180,7 @@ htmlhelp_basename = 'contextlib2doc'
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'contextlib2.tex', u'contextlib2 Documentation',
|
('index', 'contextlib2.tex', u'contextlib2 Documentation',
|
||||||
u'Alyssa Coghlan', 'manual'),
|
u'Nick Coghlan', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
|
@ -214,9 +213,9 @@ latex_documents = [
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
('index', 'contextlib2', u'contextlib2 Documentation',
|
('index', 'contextlib2', u'contextlib2 Documentation',
|
||||||
[u'Alyssa Coghlan'], 1)
|
[u'Nick Coghlan'], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Configuration for intersphinx: refer to the Python 3 standard library.
|
# Configuration for intersphinx: refer to the Python 3 standard library.
|
||||||
intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
|
intersphinx_mapping = {'http://docs.python.org/3': None}
|
||||||
|
|
|
||||||
|
|
@ -30,16 +30,16 @@ Functions and classes provided:
|
||||||
|
|
||||||
This function is a :term:`decorator` that can be used to define a factory
|
This function is a :term:`decorator` that can be used to define a factory
|
||||||
function for :keyword:`with` statement context managers, without needing to
|
function for :keyword:`with` statement context managers, without needing to
|
||||||
create a class or separate :meth:`~object.__enter__` and :meth:`~object.__exit__` methods.
|
create a class or separate :meth:`__enter__` and :meth:`__exit__` methods.
|
||||||
|
|
||||||
While many objects natively support use in with statements, sometimes a
|
While many objects natively support use in with statements, sometimes a
|
||||||
resource needs to be managed that isn't a context manager in its own right,
|
resource needs to be managed that isn't a context manager in its own right,
|
||||||
and doesn't implement a ``close()`` method for use with ``contextlib2.closing``
|
and doesn't implement a ``close()`` method for use with ``contextlib.closing``
|
||||||
|
|
||||||
An abstract example would be the following to ensure correct resource
|
An abstract example would be the following to ensure correct resource
|
||||||
management::
|
management::
|
||||||
|
|
||||||
from contextlib2 import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def managed_resource(*args, **kwds):
|
def managed_resource(*args, **kwds):
|
||||||
|
|
@ -51,8 +51,6 @@ Functions and classes provided:
|
||||||
# Code to release resource, e.g.:
|
# Code to release resource, e.g.:
|
||||||
release_resource(resource)
|
release_resource(resource)
|
||||||
|
|
||||||
The function can then be used like this::
|
|
||||||
|
|
||||||
>>> with managed_resource(timeout=3600) as resource:
|
>>> with managed_resource(timeout=3600) as resource:
|
||||||
... # Resource is released at the end of this block,
|
... # Resource is released at the end of this block,
|
||||||
... # even if code in the block raises an exception
|
... # even if code in the block raises an exception
|
||||||
|
|
@ -83,18 +81,18 @@ Functions and classes provided:
|
||||||
|
|
||||||
.. decorator:: asynccontextmanager
|
.. decorator:: asynccontextmanager
|
||||||
|
|
||||||
Similar to :func:`~contextlib2.contextmanager`, but creates an
|
Similar to :func:`~contextlib.contextmanager`, but creates an
|
||||||
:ref:`asynchronous context manager <async-context-managers>`.
|
:ref:`asynchronous context manager <async-context-managers>`.
|
||||||
|
|
||||||
This function is a :term:`decorator` that can be used to define a factory
|
This function is a :term:`decorator` that can be used to define a factory
|
||||||
function for :keyword:`async with` statement asynchronous context managers,
|
function for :keyword:`async with` statement asynchronous context managers,
|
||||||
without needing to create a class or separate :meth:`~object.__aenter__` and
|
without needing to create a class or separate :meth:`__aenter__` and
|
||||||
:meth:`~object.__aexit__` methods. It must be applied to an :term:`asynchronous
|
:meth:`__aexit__` methods. It must be applied to an :term:`asynchronous
|
||||||
generator` function.
|
generator` function.
|
||||||
|
|
||||||
A simple example::
|
A simple example::
|
||||||
|
|
||||||
from contextlib2 import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def get_connection():
|
async def get_connection():
|
||||||
|
|
@ -117,9 +115,7 @@ Functions and classes provided:
|
||||||
either as decorators or with :keyword:`async with` statements::
|
either as decorators or with :keyword:`async with` statements::
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from contextlib2 import asynccontextmanager
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def timeit():
|
async def timeit():
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
try:
|
try:
|
||||||
|
|
@ -127,9 +123,9 @@ Functions and classes provided:
|
||||||
finally:
|
finally:
|
||||||
print(f'it took {time.monotonic() - now}s to run')
|
print(f'it took {time.monotonic() - now}s to run')
|
||||||
|
|
||||||
@timeit()
|
@timeit()
|
||||||
async def main():
|
async def main():
|
||||||
# ... async code ...
|
# ... async code ...
|
||||||
|
|
||||||
When used as a decorator, a new generator instance is implicitly created on
|
When used as a decorator, a new generator instance is implicitly created on
|
||||||
each function call. This allows the otherwise "one-shot" context managers
|
each function call. This allows the otherwise "one-shot" context managers
|
||||||
|
|
@ -142,7 +138,7 @@ Functions and classes provided:
|
||||||
Return a context manager that closes *thing* upon completion of the block. This
|
Return a context manager that closes *thing* upon completion of the block. This
|
||||||
is basically equivalent to::
|
is basically equivalent to::
|
||||||
|
|
||||||
from contextlib2 import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def closing(thing):
|
def closing(thing):
|
||||||
|
|
@ -153,31 +149,23 @@ Functions and classes provided:
|
||||||
|
|
||||||
And lets you write code like this::
|
And lets you write code like this::
|
||||||
|
|
||||||
from contextlib2 import closing
|
from contextlib import closing
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
with closing(urlopen('https://www.python.org')) as page:
|
with closing(urlopen('http://www.python.org')) as page:
|
||||||
for line in page:
|
for line in page:
|
||||||
print(line)
|
print(line)
|
||||||
|
|
||||||
without needing to explicitly close ``page``. Even if an error occurs,
|
without needing to explicitly close ``page``. Even if an error occurs,
|
||||||
``page.close()`` will be called when the :keyword:`with` block is exited.
|
``page.close()`` will be called when the :keyword:`with` block is exited.
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Most types managing resources support the :term:`context manager` protocol,
|
.. class:: aclosing(thing)
|
||||||
which closes *thing* on leaving the :keyword:`with` statement.
|
|
||||||
As such, :func:`!closing` is most useful for third party types that don't
|
|
||||||
support context managers.
|
|
||||||
This example is purely for illustration purposes,
|
|
||||||
as :func:`~urllib.request.urlopen` would normally be used in a context manager.
|
|
||||||
|
|
||||||
.. function:: aclosing(thing)
|
|
||||||
|
|
||||||
Return an async context manager that calls the ``aclose()`` method of *thing*
|
Return an async context manager that calls the ``aclose()`` method of *thing*
|
||||||
upon completion of the block. This is basically equivalent to::
|
upon completion of the block. This is basically equivalent to::
|
||||||
|
|
||||||
from contextlib2 import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def aclosing(thing):
|
async def aclosing(thing):
|
||||||
|
|
@ -190,7 +178,7 @@ Functions and classes provided:
|
||||||
generators when they happen to exit early by :keyword:`break` or an
|
generators when they happen to exit early by :keyword:`break` or an
|
||||||
exception. For example::
|
exception. For example::
|
||||||
|
|
||||||
from contextlib2 import aclosing
|
from contextlib import aclosing
|
||||||
|
|
||||||
async with aclosing(my_generator()) as values:
|
async with aclosing(my_generator()) as values:
|
||||||
async for value in values:
|
async for value in values:
|
||||||
|
|
@ -217,10 +205,10 @@ Functions and classes provided:
|
||||||
def myfunction(arg, ignore_exceptions=False):
|
def myfunction(arg, ignore_exceptions=False):
|
||||||
if ignore_exceptions:
|
if ignore_exceptions:
|
||||||
# Use suppress to ignore all exceptions.
|
# Use suppress to ignore all exceptions.
|
||||||
cm = contextlib2.suppress(Exception)
|
cm = contextlib.suppress(Exception)
|
||||||
else:
|
else:
|
||||||
# Do not ignore any exceptions, cm has no effect.
|
# Do not ignore any exceptions, cm has no effect.
|
||||||
cm = contextlib2.nullcontext()
|
cm = contextlib.nullcontext()
|
||||||
with cm:
|
with cm:
|
||||||
# Do something
|
# Do something
|
||||||
|
|
||||||
|
|
@ -241,15 +229,15 @@ Functions and classes provided:
|
||||||
:ref:`asynchronous context managers <async-context-managers>`::
|
:ref:`asynchronous context managers <async-context-managers>`::
|
||||||
|
|
||||||
async def send_http(session=None):
|
async def send_http(session=None):
|
||||||
if not session:
|
if not session:
|
||||||
# If no http session, create it with aiohttp
|
# If no http session, create it with aiohttp
|
||||||
cm = aiohttp.ClientSession()
|
cm = aiohttp.ClientSession()
|
||||||
else:
|
else:
|
||||||
# Caller is responsible for closing the session
|
# Caller is responsible for closing the session
|
||||||
cm = nullcontext(session)
|
cm = nullcontext(session)
|
||||||
|
|
||||||
async with cm as session:
|
async with cm as session:
|
||||||
# Send http requests with session
|
# Send http requests with session
|
||||||
|
|
||||||
.. versionadded:: 0.6.0
|
.. versionadded:: 0.6.0
|
||||||
Part of the standard library in Python 3.7 and later
|
Part of the standard library in Python 3.7 and later
|
||||||
|
|
@ -272,7 +260,7 @@ Functions and classes provided:
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
from contextlib2 import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
with suppress(FileNotFoundError):
|
with suppress(FileNotFoundError):
|
||||||
os.remove('somefile.tmp')
|
os.remove('somefile.tmp')
|
||||||
|
|
@ -294,17 +282,9 @@ Functions and classes provided:
|
||||||
|
|
||||||
This context manager is :ref:`reentrant <reentrant-cms>`.
|
This context manager is :ref:`reentrant <reentrant-cms>`.
|
||||||
|
|
||||||
If the code within the :keyword:`!with` block raises a
|
|
||||||
:exc:`BaseExceptionGroup`, suppressed exceptions are removed from the
|
|
||||||
group. If any exceptions in the group are not suppressed, a group containing
|
|
||||||
them is re-raised.
|
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
Part of the standard library in Python 3.4 and later
|
Part of the standard library in Python 3.4 and later
|
||||||
|
|
||||||
.. versionchanged:: 24.6.0
|
|
||||||
Updated to Python 3.12 version that supports suppressing exceptions raised
|
|
||||||
as part of a :exc:`BaseExceptionGroup`.
|
|
||||||
|
|
||||||
.. function:: redirect_stdout(new_target)
|
.. function:: redirect_stdout(new_target)
|
||||||
|
|
||||||
|
|
@ -349,7 +329,7 @@ Functions and classes provided:
|
||||||
|
|
||||||
.. function:: redirect_stderr(new_target)
|
.. function:: redirect_stderr(new_target)
|
||||||
|
|
||||||
Similar to :func:`~contextlib2.redirect_stdout` but redirecting
|
Similar to :func:`~contextlib.redirect_stdout` but redirecting
|
||||||
:data:`sys.stderr` to another file or file-like object.
|
:data:`sys.stderr` to another file or file-like object.
|
||||||
|
|
||||||
This context manager is :ref:`reentrant <reentrant-cms>`.
|
This context manager is :ref:`reentrant <reentrant-cms>`.
|
||||||
|
|
@ -358,24 +338,6 @@ Functions and classes provided:
|
||||||
Part of the standard library in Python 3.5 and later
|
Part of the standard library in Python 3.5 and later
|
||||||
|
|
||||||
|
|
||||||
.. function:: chdir(path)
|
|
||||||
|
|
||||||
Non parallel-safe context manager to change the current working directory.
|
|
||||||
As this changes a global state, the working directory, it is not suitable
|
|
||||||
for use in most threaded or async contexts. It is also not suitable for most
|
|
||||||
non-linear code execution, like generators, where the program execution is
|
|
||||||
temporarily relinquished -- unless explicitly desired, you should not yield
|
|
||||||
when this context manager is active.
|
|
||||||
|
|
||||||
This is a simple wrapper around :func:`~os.chdir`, it changes the current
|
|
||||||
working directory upon entering and restores the old one on exit.
|
|
||||||
|
|
||||||
This context manager is :ref:`reentrant <reentrant-cms>`.
|
|
||||||
|
|
||||||
.. versionadded:: 24.6.0
|
|
||||||
Part of the standard library in Python 3.11 and later
|
|
||||||
|
|
||||||
|
|
||||||
.. class:: ContextDecorator()
|
.. class:: ContextDecorator()
|
||||||
|
|
||||||
A base class that enables a context manager to also be used as a decorator.
|
A base class that enables a context manager to also be used as a decorator.
|
||||||
|
|
@ -389,7 +351,7 @@ Functions and classes provided:
|
||||||
|
|
||||||
Example of ``ContextDecorator``::
|
Example of ``ContextDecorator``::
|
||||||
|
|
||||||
from contextlib2 import ContextDecorator
|
from contextlib import ContextDecorator
|
||||||
|
|
||||||
class mycontext(ContextDecorator):
|
class mycontext(ContextDecorator):
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
|
@ -400,8 +362,6 @@ Functions and classes provided:
|
||||||
print('Finishing')
|
print('Finishing')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
The class can then be used like this::
|
|
||||||
|
|
||||||
>>> @mycontext()
|
>>> @mycontext()
|
||||||
... def function():
|
... def function():
|
||||||
... print('The bit in the middle')
|
... print('The bit in the middle')
|
||||||
|
|
@ -436,7 +396,7 @@ Functions and classes provided:
|
||||||
Existing context managers that already have a base class can be extended by
|
Existing context managers that already have a base class can be extended by
|
||||||
using ``ContextDecorator`` as a mixin class::
|
using ``ContextDecorator`` as a mixin class::
|
||||||
|
|
||||||
from contextlib2 import ContextDecorator
|
from contextlib import ContextDecorator
|
||||||
|
|
||||||
class mycontext(ContextBaseClass, ContextDecorator):
|
class mycontext(ContextBaseClass, ContextDecorator):
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
|
@ -459,7 +419,7 @@ Functions and classes provided:
|
||||||
Example of ``AsyncContextDecorator``::
|
Example of ``AsyncContextDecorator``::
|
||||||
|
|
||||||
from asyncio import run
|
from asyncio import run
|
||||||
from contextlib2 import AsyncContextDecorator
|
from contextlib import AsyncContextDecorator
|
||||||
|
|
||||||
class mycontext(AsyncContextDecorator):
|
class mycontext(AsyncContextDecorator):
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
|
|
@ -470,8 +430,6 @@ Functions and classes provided:
|
||||||
print('Finishing')
|
print('Finishing')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
The class can then be used like this::
|
|
||||||
|
|
||||||
>>> @mycontext()
|
>>> @mycontext()
|
||||||
... async def function():
|
... async def function():
|
||||||
... print('The bit in the middle')
|
... print('The bit in the middle')
|
||||||
|
|
@ -509,9 +467,6 @@ Functions and classes provided:
|
||||||
# the with statement, even if attempts to open files later
|
# the with statement, even if attempts to open files later
|
||||||
# in the list raise an exception
|
# in the list raise an exception
|
||||||
|
|
||||||
The :meth:`~object.__enter__` method returns the :class:`ExitStack` instance, and
|
|
||||||
performs no additional operations.
|
|
||||||
|
|
||||||
Each instance maintains a stack of registered callbacks that are called in
|
Each instance maintains a stack of registered callbacks that are called in
|
||||||
reverse order when the instance is closed (either explicitly or implicitly
|
reverse order when the instance is closed (either explicitly or implicitly
|
||||||
at the end of a :keyword:`with` statement). Note that callbacks are *not*
|
at the end of a :keyword:`with` statement). Note that callbacks are *not*
|
||||||
|
|
@ -538,32 +493,27 @@ Functions and classes provided:
|
||||||
|
|
||||||
.. method:: enter_context(cm)
|
.. method:: enter_context(cm)
|
||||||
|
|
||||||
Enters a new context manager and adds its :meth:`~object.__exit__` method to
|
Enters a new context manager and adds its :meth:`__exit__` method to
|
||||||
the callback stack. The return value is the result of the context
|
the callback stack. The return value is the result of the context
|
||||||
manager's own :meth:`~object.__enter__` method.
|
manager's own :meth:`__enter__` method.
|
||||||
|
|
||||||
These context managers may suppress exceptions just as they normally
|
These context managers may suppress exceptions just as they normally
|
||||||
would if used directly as part of a :keyword:`with` statement.
|
would if used directly as part of a :keyword:`with` statement.
|
||||||
|
|
||||||
.. versionchanged:: 24.6.0
|
|
||||||
When running on Python 3.11 or later, raises :exc:`TypeError` instead
|
|
||||||
of :exc:`AttributeError` if *cm* is not a context manager. This aligns
|
|
||||||
with the behaviour of :keyword:`with` statements in Python 3.11+.
|
|
||||||
|
|
||||||
.. method:: push(exit)
|
.. method:: push(exit)
|
||||||
|
|
||||||
Adds a context manager's :meth:`~object.__exit__` method to the callback stack.
|
Adds a context manager's :meth:`__exit__` method to the callback stack.
|
||||||
|
|
||||||
As ``__enter__`` is *not* invoked, this method can be used to cover
|
As ``__enter__`` is *not* invoked, this method can be used to cover
|
||||||
part of an :meth:`~object.__enter__` implementation with a context manager's own
|
part of an :meth:`__enter__` implementation with a context manager's own
|
||||||
:meth:`~object.__exit__` method.
|
:meth:`__exit__` method.
|
||||||
|
|
||||||
If passed an object that is not a context manager, this method assumes
|
If passed an object that is not a context manager, this method assumes
|
||||||
it is a callback with the same signature as a context manager's
|
it is a callback with the same signature as a context manager's
|
||||||
:meth:`~object.__exit__` method and adds it directly to the callback stack.
|
:meth:`__exit__` method and adds it directly to the callback stack.
|
||||||
|
|
||||||
By returning true values, these callbacks can suppress exceptions the
|
By returning true values, these callbacks can suppress exceptions the
|
||||||
same way context manager :meth:`~object.__exit__` methods can.
|
same way context manager :meth:`__exit__` methods can.
|
||||||
|
|
||||||
The passed in object is returned from the function, allowing this
|
The passed in object is returned from the function, allowing this
|
||||||
method to be used as a function decorator.
|
method to be used as a function decorator.
|
||||||
|
|
@ -612,33 +562,26 @@ Functions and classes provided:
|
||||||
asynchronous context managers, as well as having coroutines for
|
asynchronous context managers, as well as having coroutines for
|
||||||
cleanup logic.
|
cleanup logic.
|
||||||
|
|
||||||
The :meth:`~ExitStack.close` method is not implemented; :meth:`aclose` must be used
|
The :meth:`close` method is not implemented, :meth:`aclose` must be used
|
||||||
instead.
|
instead.
|
||||||
|
|
||||||
.. method:: enter_async_context(cm)
|
.. method:: enter_async_context(cm)
|
||||||
:async:
|
|
||||||
|
|
||||||
Similar to :meth:`ExitStack.enter_context` but expects an asynchronous context
|
Similar to :meth:`enter_context` but expects an asynchronous context
|
||||||
manager.
|
manager.
|
||||||
|
|
||||||
.. versionchanged:: 24.6.0
|
|
||||||
When running on Python 3.11 or later, raises :exc:`TypeError` instead
|
|
||||||
of :exc:`AttributeError` if *cm* is not an asynchronous context manager.
|
|
||||||
This aligns with the behaviour of ``async with`` statements in Python 3.11+.
|
|
||||||
|
|
||||||
.. method:: push_async_exit(exit)
|
.. method:: push_async_exit(exit)
|
||||||
|
|
||||||
Similar to :meth:`ExitStack.push` but expects either an asynchronous context manager
|
Similar to :meth:`push` but expects either an asynchronous context manager
|
||||||
or a coroutine function.
|
or a coroutine function.
|
||||||
|
|
||||||
.. method:: push_async_callback(callback, /, *args, **kwds)
|
.. method:: push_async_callback(callback, /, *args, **kwds)
|
||||||
|
|
||||||
Similar to :meth:`ExitStack.callback` but expects a coroutine function.
|
Similar to :meth:`callback` but expects a coroutine function.
|
||||||
|
|
||||||
.. method:: aclose()
|
.. method:: aclose()
|
||||||
:async:
|
|
||||||
|
|
||||||
Similar to :meth:`ExitStack.close` but properly handles awaitables.
|
Similar to :meth:`close` but properly handles awaitables.
|
||||||
|
|
||||||
Continuing the example for :func:`asynccontextmanager`::
|
Continuing the example for :func:`asynccontextmanager`::
|
||||||
|
|
||||||
|
|
@ -657,7 +600,7 @@ Examples and Recipes
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
This section describes some examples and recipes for making effective use of
|
This section describes some examples and recipes for making effective use of
|
||||||
the tools provided by :mod:`contextlib2`.
|
the tools provided by :mod:`contextlib`.
|
||||||
|
|
||||||
|
|
||||||
Supporting a variable number of context managers
|
Supporting a variable number of context managers
|
||||||
|
|
@ -715,13 +658,13 @@ Cleaning up in an ``__enter__`` implementation
|
||||||
|
|
||||||
As noted in the documentation of :meth:`ExitStack.push`, this
|
As noted in the documentation of :meth:`ExitStack.push`, this
|
||||||
method can be useful in cleaning up an already allocated resource if later
|
method can be useful in cleaning up an already allocated resource if later
|
||||||
steps in the :meth:`~object.__enter__` implementation fail.
|
steps in the :meth:`__enter__` implementation fail.
|
||||||
|
|
||||||
Here's an example of doing this for a context manager that accepts resource
|
Here's an example of doing this for a context manager that accepts resource
|
||||||
acquisition and release functions, along with an optional validation function,
|
acquisition and release functions, along with an optional validation function,
|
||||||
and maps them to the context management protocol::
|
and maps them to the context management protocol::
|
||||||
|
|
||||||
from contextlib2 import contextmanager, AbstractContextManager, ExitStack
|
from contextlib import contextmanager, AbstractContextManager, ExitStack
|
||||||
|
|
||||||
class ResourceManager(AbstractContextManager):
|
class ResourceManager(AbstractContextManager):
|
||||||
|
|
||||||
|
|
@ -781,7 +724,7 @@ up being separated by arbitrarily long sections of code.
|
||||||
execution at the end of a ``with`` statement, and then later decide to skip
|
execution at the end of a ``with`` statement, and then later decide to skip
|
||||||
executing that callback::
|
executing that callback::
|
||||||
|
|
||||||
from contextlib2 import ExitStack
|
from contextlib import ExitStack
|
||||||
|
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
stack.callback(cleanup_resources)
|
stack.callback(cleanup_resources)
|
||||||
|
|
@ -795,7 +738,7 @@ rather than requiring a separate flag variable.
|
||||||
If a particular application uses this pattern a lot, it can be simplified
|
If a particular application uses this pattern a lot, it can be simplified
|
||||||
even further by means of a small helper class::
|
even further by means of a small helper class::
|
||||||
|
|
||||||
from contextlib2 import ExitStack
|
from contextlib import ExitStack
|
||||||
|
|
||||||
class Callback(ExitStack):
|
class Callback(ExitStack):
|
||||||
def __init__(self, callback, /, *args, **kwds):
|
def __init__(self, callback, /, *args, **kwds):
|
||||||
|
|
@ -815,7 +758,7 @@ function, then it is still possible to use the decorator form of
|
||||||
:meth:`ExitStack.callback` to declare the resource cleanup in
|
:meth:`ExitStack.callback` to declare the resource cleanup in
|
||||||
advance::
|
advance::
|
||||||
|
|
||||||
from contextlib2 import ExitStack
|
from contextlib import ExitStack
|
||||||
|
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
@stack.callback
|
@stack.callback
|
||||||
|
|
@ -842,7 +785,7 @@ writing both a function decorator and a context manager for the task,
|
||||||
inheriting from :class:`ContextDecorator` provides both capabilities in a
|
inheriting from :class:`ContextDecorator` provides both capabilities in a
|
||||||
single definition::
|
single definition::
|
||||||
|
|
||||||
from contextlib2 import ContextDecorator
|
from contextlib import ContextDecorator
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
@ -872,7 +815,7 @@ And also as a function decorator::
|
||||||
|
|
||||||
Note that there is one additional limitation when using context managers
|
Note that there is one additional limitation when using context managers
|
||||||
as function decorators: there's no way to access the return value of
|
as function decorators: there's no way to access the return value of
|
||||||
:meth:`~object.__enter__`. If that value is needed, then it is still necessary to use
|
:meth:`__enter__`. If that value is needed, then it is still necessary to use
|
||||||
an explicit ``with`` statement.
|
an explicit ``with`` statement.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
@ -904,7 +847,7 @@ Context managers created using :func:`contextmanager` are also single use
|
||||||
context managers, and will complain about the underlying generator failing
|
context managers, and will complain about the underlying generator failing
|
||||||
to yield if an attempt is made to use them a second time::
|
to yield if an attempt is made to use them a second time::
|
||||||
|
|
||||||
>>> from contextlib2 import contextmanager
|
>>> from contextlib import contextmanager
|
||||||
>>> @contextmanager
|
>>> @contextmanager
|
||||||
... def singleuse():
|
... def singleuse():
|
||||||
... print("Before")
|
... print("Before")
|
||||||
|
|
@ -936,10 +879,10 @@ but may also be used *inside* a :keyword:`!with` statement that is already
|
||||||
using the same context manager.
|
using the same context manager.
|
||||||
|
|
||||||
:class:`threading.RLock` is an example of a reentrant context manager, as are
|
:class:`threading.RLock` is an example of a reentrant context manager, as are
|
||||||
:func:`suppress`, :func:`redirect_stdout`, and :func:`chdir`. Here's a very
|
:func:`suppress` and :func:`redirect_stdout`. Here's a very simple example of
|
||||||
simple example of reentrant use::
|
reentrant use::
|
||||||
|
|
||||||
>>> from contextlib2 import redirect_stdout
|
>>> from contextlib import redirect_stdout
|
||||||
>>> from io import StringIO
|
>>> from io import StringIO
|
||||||
>>> stream = StringIO()
|
>>> stream = StringIO()
|
||||||
>>> write_to_stream = redirect_stdout(stream)
|
>>> write_to_stream = redirect_stdout(stream)
|
||||||
|
|
@ -985,7 +928,7 @@ Another example of a reusable, but not reentrant, context manager is
|
||||||
when leaving any with statement, regardless of where those callbacks
|
when leaving any with statement, regardless of where those callbacks
|
||||||
were added::
|
were added::
|
||||||
|
|
||||||
>>> from contextlib2 import ExitStack
|
>>> from contextlib import ExitStack
|
||||||
>>> stack = ExitStack()
|
>>> stack = ExitStack()
|
||||||
>>> with stack:
|
>>> with stack:
|
||||||
... stack.callback(print, "Callback: from first context")
|
... stack.callback(print, "Callback: from first context")
|
||||||
|
|
@ -1019,7 +962,7 @@ statement, which is unlikely to be desirable behaviour.
|
||||||
Using separate :class:`ExitStack` instances instead of reusing a single
|
Using separate :class:`ExitStack` instances instead of reusing a single
|
||||||
instance avoids that problem::
|
instance avoids that problem::
|
||||||
|
|
||||||
>>> from contextlib2 import ExitStack
|
>>> from contextlib import ExitStack
|
||||||
>>> with ExitStack() as outer_stack:
|
>>> with ExitStack() as outer_stack:
|
||||||
... outer_stack.callback(print, "Callback: from outer context")
|
... outer_stack.callback(print, "Callback: from outer context")
|
||||||
... with ExitStack() as inner_stack:
|
... with ExitStack() as inner_stack:
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,14 @@ involving the ``with`` and ``async with`` statements.
|
||||||
Additions Relative to the Standard Library
|
Additions Relative to the Standard Library
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
This module is primarily a backport of the Python 3.12.3 version of
|
This module is primarily a backport of the Python 3.10 version of
|
||||||
:mod:`contextlib` to earlier releases. (Note: as of the start of the Python 3.13
|
:mod:`contextlib` to earlier releases. The async context management features
|
||||||
beta release cycle, there have been no subsequent changes to ``contextlib``)
|
require asynchronous generator support in the language runtime, so the oldest
|
||||||
|
supported version is now Python 3.6 (contextlib2 0.6.0 and earlier support
|
||||||
|
older Python versions by omitting all asynchronous features).
|
||||||
|
|
||||||
The module makes use of positional-only argument syntax in several call
|
This module is also a proving ground for new features not yet part of the
|
||||||
signatures, so the oldest supported Python version is Python 3.8.
|
standard library. There are currently no such features in the module.
|
||||||
|
|
||||||
This module may also be used as a proving ground for new features not yet part
|
|
||||||
of the standard library. There are currently no such features in the module.
|
|
||||||
|
|
||||||
Finally, this module contains some deprecated APIs which never graduated to
|
Finally, this module contains some deprecated APIs which never graduated to
|
||||||
standard library inclusion. These interfaces are no longer documented, but may
|
standard library inclusion. These interfaces are no longer documented, but may
|
||||||
|
|
@ -56,7 +55,7 @@ PyPI page`_.
|
||||||
There are no operating system or distribution specific versions of this
|
There are no operating system or distribution specific versions of this
|
||||||
module - it is a pure Python module that should work on all platforms.
|
module - it is a pure Python module that should work on all platforms.
|
||||||
|
|
||||||
Supported Python versions are currently 3.8+.
|
Supported Python versions are currently 3.6+.
|
||||||
|
|
||||||
.. _Python Package Index: http://pypi.python.org
|
.. _Python Package Index: http://pypi.python.org
|
||||||
.. _pip: http://www.pip-installer.org
|
.. _pip: http://www.pip-installer.org
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
sphinx-rtd-theme
|
|
||||||
2
setup.cfg
Normal file
2
setup.cfg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[bdist_wheel]
|
||||||
|
universal=1
|
||||||
20
setup.py
20
setup.py
|
|
@ -4,29 +4,18 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
# Note: The minimum Python version requirement is set on the basis of
|
|
||||||
# "if it's not tested, it's broken".
|
|
||||||
# Specifically, if a Python version is no longer available for testing
|
|
||||||
# in CI, then the minimum supported Python version will be increased.
|
|
||||||
# That way there's no risk of a release that breaks older Python versions.
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='contextlib2',
|
name='contextlib2',
|
||||||
version=open('VERSION.txt').read().strip(),
|
version=open('VERSION.txt').read().strip(),
|
||||||
python_requires='>=3.7',
|
python_requires='>=3.6',
|
||||||
packages=['contextlib2'],
|
packages=['contextlib2'],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
license='PSF License',
|
license='PSF License',
|
||||||
description='Backports and enhancements for the contextlib module',
|
description='Backports and enhancements for the contextlib module',
|
||||||
long_description=open('README.rst').read(),
|
long_description=open('README.rst').read(),
|
||||||
author='Alyssa Coghlan',
|
author='Nick Coghlan',
|
||||||
author_email='ncoghlan@gmail.com',
|
author_email='ncoghlan@gmail.com',
|
||||||
url='https://github.com/jazzband/contextlib2',
|
url='http://contextlib2.readthedocs.org',
|
||||||
project_urls= {
|
|
||||||
'Documentation': 'https://contextlib2.readthedocs.org',
|
|
||||||
'Source': 'https://github.com/jazzband/contextlib2.git',
|
|
||||||
'Issue Tracker': 'https://github.com/jazzband/contextlib2.git',
|
|
||||||
}
|
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Development Status :: 5 - Production/Stable',
|
||||||
'License :: OSI Approved :: Apache Software License',
|
'License :: OSI Approved :: Apache Software License',
|
||||||
|
|
@ -34,12 +23,11 @@ setup(
|
||||||
# These are the Python versions tested, it may work on others
|
# These are the Python versions tested, it may work on others
|
||||||
# It definitely won't work on versions without native async support
|
# It definitely won't work on versions without native async support
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
'Programming Language :: Python :: 3.9',
|
'Programming Language :: Python :: 3.9',
|
||||||
'Programming Language :: Python :: 3.10',
|
'Programming Language :: Python :: 3.10',
|
||||||
'Programming Language :: Python :: 3.11',
|
|
||||||
'Programming Language :: Python :: 3.12',
|
|
||||||
],
|
],
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
test_contextlib uses this folder for chdir tests
|
|
||||||
|
|
@ -2,100 +2,5 @@
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
# Extra contextlib2 helpers checking CPython version-dependent details
|
|
||||||
_py_ver = sys.version_info
|
|
||||||
|
|
||||||
cl2_gens_have_gi_suspended = (_py_ver >= (3, 11))
|
|
||||||
cl2_async_gens_have_ag_suspended = (_py_ver >= (3, 12))
|
|
||||||
|
|
||||||
cl2_have_exception_groups = (_py_ver >= (3, 11))
|
|
||||||
cl2_requires_exception_groups = unittest.skipIf(not cl2_have_exception_groups,
|
|
||||||
"Test requires exception groups")
|
|
||||||
|
|
||||||
cl2_check_traceback_details = (_py_ver >= (3, 10))
|
|
||||||
|
|
||||||
# CM protocol checking switched to TypeError in Python 3.11
|
|
||||||
cl2_cm_api_exc_type = TypeError if (_py_ver >= (3, 11)) else AttributeError
|
|
||||||
if cl2_cm_api_exc_type is AttributeError:
|
|
||||||
cl2_cm_api_exc_text_sync = {
|
|
||||||
"": "has no attribute",
|
|
||||||
"__enter__": "__enter__",
|
|
||||||
"__exit__": "__exit__",
|
|
||||||
}
|
|
||||||
cl2_cm_api_exc_text_async = cl2_cm_api_exc_text_sync
|
|
||||||
else:
|
|
||||||
cl2_cm_api_exc_text_sync = {
|
|
||||||
"": "the context manager",
|
|
||||||
"__enter__": "the context manager",
|
|
||||||
"__exit__": "the context manager.*__exit__",
|
|
||||||
}
|
|
||||||
cl2_cm_api_exc_text_async = {
|
|
||||||
"": "asynchronous context manager",
|
|
||||||
"__enter__": "asynchronous context manager",
|
|
||||||
"__exit__": "asynchronous context manager.*__exit__",
|
|
||||||
}
|
|
||||||
|
|
||||||
def cl2_cm_api_exc_info_sync(check_context="", /):
|
|
||||||
return cl2_cm_api_exc_type, cl2_cm_api_exc_text_sync[check_context]
|
|
||||||
|
|
||||||
def cl2_cm_api_exc_info_async(check_context="", /):
|
|
||||||
return cl2_cm_api_exc_type, cl2_cm_api_exc_text_async[check_context]
|
|
||||||
|
|
||||||
# Some tests check docstring details
|
|
||||||
requires_docstrings = unittest.skipIf(sys.flags.optimize >= 2,
|
requires_docstrings = unittest.skipIf(sys.flags.optimize >= 2,
|
||||||
"Test requires docstrings")
|
"Test requires docstrings")
|
||||||
|
|
||||||
# Some tests check CPython implementation details
|
|
||||||
def _parse_guards(guards):
|
|
||||||
# Returns a tuple ({platform_name: run_me}, default_value)
|
|
||||||
if not guards:
|
|
||||||
return ({'cpython': True}, False)
|
|
||||||
is_true = list(guards.values())[0]
|
|
||||||
assert list(guards.values()) == [is_true] * len(guards) # all True or all False
|
|
||||||
return (guards, not is_true)
|
|
||||||
|
|
||||||
# Use the following check to guard CPython's implementation-specific tests --
|
|
||||||
# or to run them only on the implementation(s) guarded by the arguments.
|
|
||||||
def check_impl_detail(**guards):
|
|
||||||
"""This function returns True or False depending on the host platform.
|
|
||||||
Examples:
|
|
||||||
if check_impl_detail(): # only on CPython (default)
|
|
||||||
if check_impl_detail(jython=True): # only on Jython
|
|
||||||
if check_impl_detail(cpython=False): # everywhere except on CPython
|
|
||||||
"""
|
|
||||||
guards, default = _parse_guards(guards)
|
|
||||||
return guards.get(sys.implementation.name, default)
|
|
||||||
|
|
||||||
# Early reference release tests force gc collection
|
|
||||||
def gc_collect():
|
|
||||||
"""Force as many objects as possible to be collected.
|
|
||||||
|
|
||||||
In non-CPython implementations of Python, this is needed because timely
|
|
||||||
deallocation is not guaranteed by the garbage collector. (Even in CPython
|
|
||||||
this can be the case in case of reference cycles.) This means that __del__
|
|
||||||
methods may be called later than expected and weakrefs may remain alive for
|
|
||||||
longer than expected. This function tries its best to force all garbage
|
|
||||||
objects to disappear.
|
|
||||||
"""
|
|
||||||
import gc
|
|
||||||
gc.collect()
|
|
||||||
gc.collect()
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
# test_contextlib_async includes some socket-based tests
|
|
||||||
# Emscripten's socket emulation and WASI sockets have limitations.
|
|
||||||
is_emscripten = sys.platform == "emscripten"
|
|
||||||
is_wasi = sys.platform == "wasi"
|
|
||||||
has_socket_support = not is_emscripten and not is_wasi
|
|
||||||
|
|
||||||
def requires_working_socket(*, module=False):
|
|
||||||
"""Skip tests or modules that require working sockets
|
|
||||||
|
|
||||||
Can be used as a function/class decorator or to skip an entire module.
|
|
||||||
"""
|
|
||||||
msg = "requires socket support"
|
|
||||||
if module:
|
|
||||||
if not has_socket_support:
|
|
||||||
raise unittest.SkipTest(msg)
|
|
||||||
else:
|
|
||||||
return unittest.skipUnless(has_socket_support, msg)
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
"""Enough of the test.support.testcase APIs to run the contextlib test suite"""
|
|
||||||
from . import cl2_have_exception_groups
|
|
||||||
|
|
||||||
if not cl2_have_exception_groups:
|
|
||||||
# Placeholder to let the isinstance check below run on older versions
|
|
||||||
class ExceptionGroup(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ExceptionIsLikeMixin:
|
|
||||||
def assertExceptionIsLike(self, exc, template):
|
|
||||||
"""
|
|
||||||
Passes when the provided `exc` matches the structure of `template`.
|
|
||||||
Individual exceptions don't have to be the same objects or even pass
|
|
||||||
an equality test: they only need to be the same type and contain equal
|
|
||||||
`exc_obj.args`.
|
|
||||||
"""
|
|
||||||
if exc is None and template is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if template is None:
|
|
||||||
self.fail(f"unexpected exception: {exc}")
|
|
||||||
|
|
||||||
if exc is None:
|
|
||||||
self.fail(f"expected an exception like {template!r}, got None")
|
|
||||||
|
|
||||||
if not isinstance(exc, ExceptionGroup):
|
|
||||||
self.assertEqual(exc.__class__, template.__class__)
|
|
||||||
self.assertEqual(exc.args[0], template.args[0])
|
|
||||||
else:
|
|
||||||
self.assertEqual(exc.message, template.message)
|
|
||||||
self.assertEqual(len(exc.exceptions), len(template.exceptions))
|
|
||||||
for e, t in zip(exc.exceptions, template.exceptions):
|
|
||||||
self.assertExceptionIsLike(e, t)
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
"""Unit tests for synchronous features of contextlib2.py"""
|
"""Unit tests for contextlib.py, and other context managers."""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
|
||||||
import unittest
|
import unittest
|
||||||
from contextlib2 import * # Tests __all__
|
from contextlib2 import * # Tests __all__
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import os_helper
|
from test.support import os_helper
|
||||||
from test.support.testcase import ExceptionIsLikeMixin
|
|
||||||
import weakref
|
import weakref
|
||||||
|
import gc
|
||||||
|
|
||||||
|
|
||||||
class TestAbstractContextManager(unittest.TestCase):
|
class TestAbstractContextManager(unittest.TestCase):
|
||||||
|
|
@ -89,56 +87,6 @@ class ContextManagerTestCase(unittest.TestCase):
|
||||||
raise ZeroDivisionError()
|
raise ZeroDivisionError()
|
||||||
self.assertEqual(state, [1, 42, 999])
|
self.assertEqual(state, [1, 42, 999])
|
||||||
|
|
||||||
def test_contextmanager_traceback(self):
|
|
||||||
@contextmanager
|
|
||||||
def f():
|
|
||||||
yield
|
|
||||||
|
|
||||||
try:
|
|
||||||
with f():
|
|
||||||
1/0
|
|
||||||
except ZeroDivisionError as e:
|
|
||||||
frames = traceback.extract_tb(e.__traceback__)
|
|
||||||
|
|
||||||
self.assertEqual(len(frames), 1)
|
|
||||||
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
|
|
||||||
self.assertEqual(frames[0].line, '1/0')
|
|
||||||
|
|
||||||
# Repeat with RuntimeError (which goes through a different code path)
|
|
||||||
class RuntimeErrorSubclass(RuntimeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
with f():
|
|
||||||
raise RuntimeErrorSubclass(42)
|
|
||||||
except RuntimeErrorSubclass as e:
|
|
||||||
frames = traceback.extract_tb(e.__traceback__)
|
|
||||||
|
|
||||||
self.assertEqual(len(frames), 1)
|
|
||||||
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
|
|
||||||
self.assertEqual(frames[0].line, 'raise RuntimeErrorSubclass(42)')
|
|
||||||
|
|
||||||
class StopIterationSubclass(StopIteration):
|
|
||||||
pass
|
|
||||||
|
|
||||||
for stop_exc in (
|
|
||||||
StopIteration('spam'),
|
|
||||||
StopIterationSubclass('spam'),
|
|
||||||
):
|
|
||||||
with self.subTest(type=type(stop_exc)):
|
|
||||||
try:
|
|
||||||
with f():
|
|
||||||
raise stop_exc
|
|
||||||
except type(stop_exc) as e:
|
|
||||||
self.assertIs(e, stop_exc)
|
|
||||||
frames = traceback.extract_tb(e.__traceback__)
|
|
||||||
else:
|
|
||||||
self.fail(f'{stop_exc} was suppressed')
|
|
||||||
|
|
||||||
self.assertEqual(len(frames), 1)
|
|
||||||
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
|
|
||||||
self.assertEqual(frames[0].line, 'raise stop_exc')
|
|
||||||
|
|
||||||
def test_contextmanager_no_reraise(self):
|
def test_contextmanager_no_reraise(self):
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def whee():
|
def whee():
|
||||||
|
|
@ -157,48 +105,9 @@ class ContextManagerTestCase(unittest.TestCase):
|
||||||
yield
|
yield
|
||||||
ctx = whoo()
|
ctx = whoo()
|
||||||
ctx.__enter__()
|
ctx.__enter__()
|
||||||
with self.assertRaises(RuntimeError):
|
self.assertRaises(
|
||||||
ctx.__exit__(TypeError, TypeError("foo"), None)
|
RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None
|
||||||
if support.check_impl_detail(cpython=True):
|
)
|
||||||
# The "gen" attribute is an implementation detail.
|
|
||||||
if support.cl2_gens_have_gi_suspended:
|
|
||||||
self.assertFalse(ctx.gen.gi_suspended)
|
|
||||||
|
|
||||||
def test_contextmanager_trap_no_yield(self):
|
|
||||||
@contextmanager
|
|
||||||
def whoo():
|
|
||||||
if False:
|
|
||||||
yield
|
|
||||||
ctx = whoo()
|
|
||||||
with self.assertRaises(RuntimeError):
|
|
||||||
ctx.__enter__()
|
|
||||||
|
|
||||||
def test_contextmanager_trap_second_yield(self):
|
|
||||||
@contextmanager
|
|
||||||
def whoo():
|
|
||||||
yield
|
|
||||||
yield
|
|
||||||
ctx = whoo()
|
|
||||||
ctx.__enter__()
|
|
||||||
with self.assertRaises(RuntimeError):
|
|
||||||
ctx.__exit__(None, None, None)
|
|
||||||
if support.check_impl_detail(cpython=True):
|
|
||||||
# The "gen" attribute is an implementation detail.
|
|
||||||
if support.cl2_gens_have_gi_suspended:
|
|
||||||
self.assertFalse(ctx.gen.gi_suspended)
|
|
||||||
|
|
||||||
def test_contextmanager_non_normalised(self):
|
|
||||||
@contextmanager
|
|
||||||
def whoo():
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
except RuntimeError:
|
|
||||||
raise SyntaxError
|
|
||||||
|
|
||||||
ctx = whoo()
|
|
||||||
ctx.__enter__()
|
|
||||||
with self.assertRaises(SyntaxError):
|
|
||||||
ctx.__exit__(RuntimeError, None, None)
|
|
||||||
|
|
||||||
def test_contextmanager_except(self):
|
def test_contextmanager_except(self):
|
||||||
state = []
|
state = []
|
||||||
|
|
@ -218,22 +127,19 @@ class ContextManagerTestCase(unittest.TestCase):
|
||||||
self.assertEqual(state, [1, 42, 999])
|
self.assertEqual(state, [1, 42, 999])
|
||||||
|
|
||||||
def test_contextmanager_except_stopiter(self):
|
def test_contextmanager_except_stopiter(self):
|
||||||
|
stop_exc = StopIteration('spam')
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def woohoo():
|
def woohoo():
|
||||||
yield
|
yield
|
||||||
|
try:
|
||||||
class StopIterationSubclass(StopIteration):
|
with self.assertWarnsRegex(DeprecationWarning,
|
||||||
pass
|
"StopIteration"):
|
||||||
|
with woohoo():
|
||||||
for stop_exc in (StopIteration('spam'), StopIterationSubclass('spam')):
|
raise stop_exc
|
||||||
with self.subTest(type=type(stop_exc)):
|
except Exception as ex:
|
||||||
try:
|
self.assertIs(ex, stop_exc)
|
||||||
with woohoo():
|
else:
|
||||||
raise stop_exc
|
self.fail('StopIteration was suppressed')
|
||||||
except Exception as ex:
|
|
||||||
self.assertIs(ex, stop_exc)
|
|
||||||
else:
|
|
||||||
self.fail(f'{stop_exc} was suppressed')
|
|
||||||
|
|
||||||
def test_contextmanager_except_pep479(self):
|
def test_contextmanager_except_pep479(self):
|
||||||
code = """\
|
code = """\
|
||||||
|
|
@ -279,25 +185,6 @@ def woohoo():
|
||||||
self.assertEqual(ex.args[0], 'issue29692:Unchained')
|
self.assertEqual(ex.args[0], 'issue29692:Unchained')
|
||||||
self.assertIsNone(ex.__cause__)
|
self.assertIsNone(ex.__cause__)
|
||||||
|
|
||||||
def test_contextmanager_wrap_runtimeerror(self):
|
|
||||||
@contextmanager
|
|
||||||
def woohoo():
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
except Exception as exc:
|
|
||||||
raise RuntimeError(f'caught {exc}') from exc
|
|
||||||
|
|
||||||
with self.assertRaises(RuntimeError):
|
|
||||||
with woohoo():
|
|
||||||
1 / 0
|
|
||||||
|
|
||||||
# If the context manager wrapped StopIteration in a RuntimeError,
|
|
||||||
# we also unwrap it, because we can't tell whether the wrapping was
|
|
||||||
# done by the generator machinery or by the generator itself.
|
|
||||||
with self.assertRaises(StopIteration):
|
|
||||||
with woohoo():
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
def _create_contextmanager_attribs(self):
|
def _create_contextmanager_attribs(self):
|
||||||
def attribs(**kw):
|
def attribs(**kw):
|
||||||
def decorate(func):
|
def decorate(func):
|
||||||
|
|
@ -309,7 +196,6 @@ def woohoo():
|
||||||
@attribs(foo='bar')
|
@attribs(foo='bar')
|
||||||
def baz(spam):
|
def baz(spam):
|
||||||
"""Whee!"""
|
"""Whee!"""
|
||||||
yield
|
|
||||||
return baz
|
return baz
|
||||||
|
|
||||||
def test_contextmanager_attribs(self):
|
def test_contextmanager_attribs(self):
|
||||||
|
|
@ -344,7 +230,7 @@ def woohoo():
|
||||||
a = weakref.ref(a)
|
a = weakref.ref(a)
|
||||||
b = weakref.ref(b)
|
b = weakref.ref(b)
|
||||||
# Allow test to work with a non-refcounted GC
|
# Allow test to work with a non-refcounted GC
|
||||||
support.gc_collect()
|
gc.collect(); gc.collect(); gc.collect()
|
||||||
self.assertIsNone(a())
|
self.assertIsNone(a())
|
||||||
self.assertIsNone(b())
|
self.assertIsNone(b())
|
||||||
yield
|
yield
|
||||||
|
|
@ -366,11 +252,8 @@ def woohoo():
|
||||||
|
|
||||||
def test_recursive(self):
|
def test_recursive(self):
|
||||||
depth = 0
|
depth = 0
|
||||||
ncols = 0
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def woohoo():
|
def woohoo():
|
||||||
nonlocal ncols
|
|
||||||
ncols += 1
|
|
||||||
nonlocal depth
|
nonlocal depth
|
||||||
before = depth
|
before = depth
|
||||||
depth += 1
|
depth += 1
|
||||||
|
|
@ -384,7 +267,6 @@ def woohoo():
|
||||||
recursive()
|
recursive()
|
||||||
|
|
||||||
recursive()
|
recursive()
|
||||||
self.assertEqual(ncols, 10)
|
|
||||||
self.assertEqual(depth, 0)
|
self.assertEqual(depth, 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -612,8 +494,7 @@ class TestContextDecorator(unittest.TestCase):
|
||||||
def __exit__(self, *exc):
|
def __exit__(self, *exc):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
expected_error, expected_text = support.cl2_cm_api_exc_info_sync("__enter__")
|
with self.assertRaises(AttributeError):
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
with mycontext():
|
with mycontext():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -625,8 +506,7 @@ class TestContextDecorator(unittest.TestCase):
|
||||||
def __uxit__(self, *exc):
|
def __uxit__(self, *exc):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
expected_error, expected_text = support.cl2_cm_api_exc_info_sync("__exit__")
|
with self.assertRaises(AttributeError):
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
with mycontext():
|
with mycontext():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -784,26 +664,6 @@ class TestBaseExitStack:
|
||||||
result.append(2)
|
result.append(2)
|
||||||
self.assertEqual(result, [1, 2, 3, 4])
|
self.assertEqual(result, [1, 2, 3, 4])
|
||||||
|
|
||||||
def test_enter_context_errors(self):
|
|
||||||
class LacksEnterAndExit:
|
|
||||||
pass
|
|
||||||
class LacksEnter:
|
|
||||||
def __exit__(self, *exc_info):
|
|
||||||
pass
|
|
||||||
class LacksExit:
|
|
||||||
def __enter__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
expected_error, expected_text = support.cl2_cm_api_exc_info_sync()
|
|
||||||
with self.exit_stack() as stack:
|
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
stack.enter_context(LacksEnterAndExit())
|
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
stack.enter_context(LacksEnter())
|
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
stack.enter_context(LacksExit())
|
|
||||||
self.assertFalse(stack._exit_callbacks)
|
|
||||||
|
|
||||||
def test_close(self):
|
def test_close(self):
|
||||||
result = []
|
result = []
|
||||||
with self.exit_stack() as stack:
|
with self.exit_stack() as stack:
|
||||||
|
|
@ -839,41 +699,6 @@ class TestBaseExitStack:
|
||||||
stack.push(lambda *exc: True)
|
stack.push(lambda *exc: True)
|
||||||
1/0
|
1/0
|
||||||
|
|
||||||
def test_exit_exception_traceback(self):
|
|
||||||
# This test captures the current behavior of ExitStack so that we know
|
|
||||||
# if we ever unintendedly change it. It is not a statement of what the
|
|
||||||
# desired behavior is (for instance, we may want to remove some of the
|
|
||||||
# internal contextlib frames).
|
|
||||||
|
|
||||||
def raise_exc(exc):
|
|
||||||
raise exc
|
|
||||||
|
|
||||||
try:
|
|
||||||
with self.exit_stack() as stack:
|
|
||||||
stack.callback(raise_exc, ValueError)
|
|
||||||
1/0
|
|
||||||
except ValueError as e:
|
|
||||||
exc = e
|
|
||||||
|
|
||||||
self.assertIsInstance(exc, ValueError)
|
|
||||||
ve_frames = traceback.extract_tb(exc.__traceback__)
|
|
||||||
expected = \
|
|
||||||
[('test_exit_exception_traceback', 'with self.exit_stack() as stack:')] + \
|
|
||||||
self.callback_error_internal_frames + \
|
|
||||||
[('_exit_wrapper', 'callback(*args, **kwds)'),
|
|
||||||
('raise_exc', 'raise exc')]
|
|
||||||
|
|
||||||
# This check fails on PyPy 3.10
|
|
||||||
# It also fails on CPython 3.9 and earlier versions
|
|
||||||
if support.check_impl_detail(cpython=True) and support.cl2_check_traceback_details:
|
|
||||||
self.assertEqual(
|
|
||||||
[(f.name, f.line) for f in ve_frames], expected)
|
|
||||||
|
|
||||||
self.assertIsInstance(exc.__context__, ZeroDivisionError)
|
|
||||||
zde_frames = traceback.extract_tb(exc.__context__.__traceback__)
|
|
||||||
self.assertEqual([(f.name, f.line) for f in zde_frames],
|
|
||||||
[('test_exit_exception_traceback', '1/0')])
|
|
||||||
|
|
||||||
def test_exit_exception_chaining_reference(self):
|
def test_exit_exception_chaining_reference(self):
|
||||||
# Sanity check to make sure that ExitStack chaining matches
|
# Sanity check to make sure that ExitStack chaining matches
|
||||||
# actual nested with statements
|
# actual nested with statements
|
||||||
|
|
@ -953,40 +778,6 @@ class TestBaseExitStack:
|
||||||
self.assertIsInstance(inner_exc, ValueError)
|
self.assertIsInstance(inner_exc, ValueError)
|
||||||
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
|
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
|
||||||
|
|
||||||
def test_exit_exception_explicit_none_context(self):
|
|
||||||
# Ensure ExitStack chaining matches actual nested `with` statements
|
|
||||||
# regarding explicit __context__ = None.
|
|
||||||
|
|
||||||
class MyException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def my_cm():
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
except BaseException:
|
|
||||||
exc = MyException()
|
|
||||||
try:
|
|
||||||
raise exc
|
|
||||||
finally:
|
|
||||||
exc.__context__ = None
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def my_cm_with_exit_stack():
|
|
||||||
with self.exit_stack() as stack:
|
|
||||||
stack.enter_context(my_cm())
|
|
||||||
yield stack
|
|
||||||
|
|
||||||
for cm in (my_cm, my_cm_with_exit_stack):
|
|
||||||
with self.subTest():
|
|
||||||
try:
|
|
||||||
with cm():
|
|
||||||
raise IndexError()
|
|
||||||
except MyException as exc:
|
|
||||||
self.assertIsNone(exc.__context__)
|
|
||||||
else:
|
|
||||||
self.fail("Expected IndexError, but no exception was raised")
|
|
||||||
|
|
||||||
def test_exit_exception_non_suppressing(self):
|
def test_exit_exception_non_suppressing(self):
|
||||||
# http://bugs.python.org/issue19092
|
# http://bugs.python.org/issue19092
|
||||||
def raise_exc(exc):
|
def raise_exc(exc):
|
||||||
|
|
@ -1098,12 +889,9 @@ class TestBaseExitStack:
|
||||||
def test_instance_bypass(self):
|
def test_instance_bypass(self):
|
||||||
class Example(object): pass
|
class Example(object): pass
|
||||||
cm = Example()
|
cm = Example()
|
||||||
cm.__enter__ = object()
|
|
||||||
cm.__exit__ = object()
|
cm.__exit__ = object()
|
||||||
stack = self.exit_stack()
|
stack = self.exit_stack()
|
||||||
expected_error, expected_text = support.cl2_cm_api_exc_info_sync()
|
self.assertRaises(AttributeError, stack.enter_context, cm)
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
stack.enter_context(cm)
|
|
||||||
stack.push(cm)
|
stack.push(cm)
|
||||||
self.assertIs(stack._exit_callbacks[-1][1], cm)
|
self.assertIs(stack._exit_callbacks[-1][1], cm)
|
||||||
|
|
||||||
|
|
@ -1144,10 +932,6 @@ class TestBaseExitStack:
|
||||||
|
|
||||||
class TestExitStack(TestBaseExitStack, unittest.TestCase):
|
class TestExitStack(TestBaseExitStack, unittest.TestCase):
|
||||||
exit_stack = ExitStack
|
exit_stack = ExitStack
|
||||||
callback_error_internal_frames = [
|
|
||||||
('__exit__', 'raise exc_details[1]'),
|
|
||||||
('__exit__', 'if cb(*exc_details):'),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class TestRedirectStream:
|
class TestRedirectStream:
|
||||||
|
|
@ -1219,7 +1003,7 @@ class TestRedirectStderr(TestRedirectStream, unittest.TestCase):
|
||||||
orig_stream = "stderr"
|
orig_stream = "stderr"
|
||||||
|
|
||||||
|
|
||||||
class TestSuppress(ExceptionIsLikeMixin, unittest.TestCase):
|
class TestSuppress(unittest.TestCase):
|
||||||
|
|
||||||
@support.requires_docstrings
|
@support.requires_docstrings
|
||||||
def test_instance_docs(self):
|
def test_instance_docs(self):
|
||||||
|
|
@ -1273,96 +1057,5 @@ class TestSuppress(ExceptionIsLikeMixin, unittest.TestCase):
|
||||||
1/0
|
1/0
|
||||||
self.assertTrue(outer_continued)
|
self.assertTrue(outer_continued)
|
||||||
|
|
||||||
@support.cl2_requires_exception_groups
|
|
||||||
def test_exception_groups(self):
|
|
||||||
eg_ve = lambda: ExceptionGroup(
|
|
||||||
"EG with ValueErrors only",
|
|
||||||
[ValueError("ve1"), ValueError("ve2"), ValueError("ve3")],
|
|
||||||
)
|
|
||||||
eg_all = lambda: ExceptionGroup(
|
|
||||||
"EG with many types of exceptions",
|
|
||||||
[ValueError("ve1"), KeyError("ke1"), ValueError("ve2"), KeyError("ke2")],
|
|
||||||
)
|
|
||||||
with suppress(ValueError):
|
|
||||||
raise eg_ve()
|
|
||||||
with suppress(ValueError, KeyError):
|
|
||||||
raise eg_all()
|
|
||||||
with self.assertRaises(ExceptionGroup) as eg1:
|
|
||||||
with suppress(ValueError):
|
|
||||||
raise eg_all()
|
|
||||||
self.assertExceptionIsLike(
|
|
||||||
eg1.exception,
|
|
||||||
ExceptionGroup(
|
|
||||||
"EG with many types of exceptions",
|
|
||||||
[KeyError("ke1"), KeyError("ke2")],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# Check handling of BaseExceptionGroup, using GeneratorExit so that
|
|
||||||
# we don't accidentally discard a ctrl-c with KeyboardInterrupt.
|
|
||||||
with suppress(GeneratorExit):
|
|
||||||
raise BaseExceptionGroup("message", [GeneratorExit()])
|
|
||||||
# If we raise a BaseException group, we can still suppress parts
|
|
||||||
with self.assertRaises(BaseExceptionGroup) as eg1:
|
|
||||||
with suppress(KeyError):
|
|
||||||
raise BaseExceptionGroup("message", [GeneratorExit("g"), KeyError("k")])
|
|
||||||
self.assertExceptionIsLike(
|
|
||||||
eg1.exception, BaseExceptionGroup("message", [GeneratorExit("g")]),
|
|
||||||
)
|
|
||||||
# If we suppress all the leaf BaseExceptions, we get a non-base ExceptionGroup
|
|
||||||
with self.assertRaises(ExceptionGroup) as eg1:
|
|
||||||
with suppress(GeneratorExit):
|
|
||||||
raise BaseExceptionGroup("message", [GeneratorExit("g"), KeyError("k")])
|
|
||||||
self.assertExceptionIsLike(
|
|
||||||
eg1.exception, ExceptionGroup("message", [KeyError("k")]),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestChdir(unittest.TestCase):
|
|
||||||
def make_relative_path(self, *parts):
|
|
||||||
return os.path.join(
|
|
||||||
os.path.dirname(os.path.realpath(__file__)),
|
|
||||||
*parts,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_simple(self):
|
|
||||||
old_cwd = os.getcwd()
|
|
||||||
target = self.make_relative_path('data')
|
|
||||||
self.assertNotEqual(old_cwd, target)
|
|
||||||
|
|
||||||
with chdir(target):
|
|
||||||
self.assertEqual(os.getcwd(), target)
|
|
||||||
self.assertEqual(os.getcwd(), old_cwd)
|
|
||||||
|
|
||||||
def test_reentrant(self):
|
|
||||||
old_cwd = os.getcwd()
|
|
||||||
target1 = self.make_relative_path('data')
|
|
||||||
target2 = self.make_relative_path('ziptestdata')
|
|
||||||
self.assertNotIn(old_cwd, (target1, target2))
|
|
||||||
chdir1, chdir2 = chdir(target1), chdir(target2)
|
|
||||||
|
|
||||||
with chdir1:
|
|
||||||
self.assertEqual(os.getcwd(), target1)
|
|
||||||
with chdir2:
|
|
||||||
self.assertEqual(os.getcwd(), target2)
|
|
||||||
with chdir1:
|
|
||||||
self.assertEqual(os.getcwd(), target1)
|
|
||||||
self.assertEqual(os.getcwd(), target2)
|
|
||||||
self.assertEqual(os.getcwd(), target1)
|
|
||||||
self.assertEqual(os.getcwd(), old_cwd)
|
|
||||||
|
|
||||||
def test_exception(self):
|
|
||||||
old_cwd = os.getcwd()
|
|
||||||
target = self.make_relative_path('data')
|
|
||||||
self.assertNotEqual(old_cwd, target)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with chdir(target):
|
|
||||||
self.assertEqual(os.getcwd(), target)
|
|
||||||
raise RuntimeError("boom")
|
|
||||||
except RuntimeError as re:
|
|
||||||
self.assertEqual(str(re), "boom")
|
|
||||||
self.assertEqual(os.getcwd(), old_cwd)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
"""Unit tests for asynchronous features of contextlib2.py"""
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib2 import (
|
from contextlib2 import (
|
||||||
asynccontextmanager, AbstractAsyncContextManager,
|
asynccontextmanager, AbstractAsyncContextManager,
|
||||||
|
|
@ -7,23 +5,24 @@ from contextlib2 import (
|
||||||
import functools
|
import functools
|
||||||
from test import support
|
from test import support
|
||||||
import unittest
|
import unittest
|
||||||
import traceback
|
|
||||||
|
|
||||||
from .test_contextlib import TestBaseExitStack
|
from test.test_contextlib import TestBaseExitStack
|
||||||
|
|
||||||
support.requires_working_socket(module=True)
|
|
||||||
|
|
||||||
def _async_test(func):
|
def _async_test(func):
|
||||||
"""Decorator to turn an async function into a test case."""
|
"""Decorator to turn an async function into a test case."""
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
coro = func(*args, **kwargs)
|
coro = func(*args, **kwargs)
|
||||||
asyncio.run(coro)
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
try:
|
||||||
|
return loop.run_until_complete(coro)
|
||||||
|
finally:
|
||||||
|
loop.close()
|
||||||
|
asyncio.set_event_loop_policy(None)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def tearDownModule():
|
|
||||||
asyncio.set_event_loop_policy(None)
|
|
||||||
|
|
||||||
|
|
||||||
class TestAbstractAsyncContextManager(unittest.TestCase):
|
class TestAbstractAsyncContextManager(unittest.TestCase):
|
||||||
|
|
||||||
|
|
@ -51,11 +50,15 @@ class TestAbstractAsyncContextManager(unittest.TestCase):
|
||||||
async with ctx():
|
async with ctx():
|
||||||
yield 11
|
yield 11
|
||||||
|
|
||||||
g = gen()
|
ret = []
|
||||||
async for val in g:
|
exc = ValueError(22)
|
||||||
self.assertEqual(val, 11)
|
with self.assertRaises(ValueError):
|
||||||
break
|
async with ctx():
|
||||||
await g.aclose()
|
async for val in gen():
|
||||||
|
ret.append(val)
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
self.assertEqual(ret, [11])
|
||||||
|
|
||||||
def test_exit_is_abstract(self):
|
def test_exit_is_abstract(self):
|
||||||
class MissingAexit(AbstractAsyncContextManager):
|
class MissingAexit(AbstractAsyncContextManager):
|
||||||
|
|
@ -124,62 +127,6 @@ class AsyncContextManagerTestCase(unittest.TestCase):
|
||||||
raise ZeroDivisionError()
|
raise ZeroDivisionError()
|
||||||
self.assertEqual(state, [1, 42, 999])
|
self.assertEqual(state, [1, 42, 999])
|
||||||
|
|
||||||
@_async_test
|
|
||||||
async def test_contextmanager_traceback(self):
|
|
||||||
@asynccontextmanager
|
|
||||||
async def f():
|
|
||||||
yield
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with f():
|
|
||||||
1/0
|
|
||||||
except ZeroDivisionError as e:
|
|
||||||
frames = traceback.extract_tb(e.__traceback__)
|
|
||||||
|
|
||||||
self.assertEqual(len(frames), 1)
|
|
||||||
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
|
|
||||||
self.assertEqual(frames[0].line, '1/0')
|
|
||||||
|
|
||||||
# Repeat with RuntimeError (which goes through a different code path)
|
|
||||||
class RuntimeErrorSubclass(RuntimeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with f():
|
|
||||||
raise RuntimeErrorSubclass(42)
|
|
||||||
except RuntimeErrorSubclass as e:
|
|
||||||
frames = traceback.extract_tb(e.__traceback__)
|
|
||||||
|
|
||||||
self.assertEqual(len(frames), 1)
|
|
||||||
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
|
|
||||||
self.assertEqual(frames[0].line, 'raise RuntimeErrorSubclass(42)')
|
|
||||||
|
|
||||||
class StopIterationSubclass(StopIteration):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class StopAsyncIterationSubclass(StopAsyncIteration):
|
|
||||||
pass
|
|
||||||
|
|
||||||
for stop_exc in (
|
|
||||||
StopIteration('spam'),
|
|
||||||
StopAsyncIteration('ham'),
|
|
||||||
StopIterationSubclass('spam'),
|
|
||||||
StopAsyncIterationSubclass('spam')
|
|
||||||
):
|
|
||||||
with self.subTest(type=type(stop_exc)):
|
|
||||||
try:
|
|
||||||
async with f():
|
|
||||||
raise stop_exc
|
|
||||||
except type(stop_exc) as e:
|
|
||||||
self.assertIs(e, stop_exc)
|
|
||||||
frames = traceback.extract_tb(e.__traceback__)
|
|
||||||
else:
|
|
||||||
self.fail(f'{stop_exc} was suppressed')
|
|
||||||
|
|
||||||
self.assertEqual(len(frames), 1)
|
|
||||||
self.assertEqual(frames[0].name, 'test_contextmanager_traceback')
|
|
||||||
self.assertEqual(frames[0].line, 'raise stop_exc')
|
|
||||||
|
|
||||||
@_async_test
|
@_async_test
|
||||||
async def test_contextmanager_no_reraise(self):
|
async def test_contextmanager_no_reraise(self):
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
|
|
@ -202,10 +149,6 @@ class AsyncContextManagerTestCase(unittest.TestCase):
|
||||||
await ctx.__aenter__()
|
await ctx.__aenter__()
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
await ctx.__aexit__(TypeError, TypeError('foo'), None)
|
await ctx.__aexit__(TypeError, TypeError('foo'), None)
|
||||||
if support.check_impl_detail(cpython=True):
|
|
||||||
# The "gen" attribute is an implementation detail.
|
|
||||||
if support.cl2_async_gens_have_ag_suspended:
|
|
||||||
self.assertFalse(ctx.gen.ag_suspended)
|
|
||||||
|
|
||||||
@_async_test
|
@_async_test
|
||||||
async def test_contextmanager_trap_no_yield(self):
|
async def test_contextmanager_trap_no_yield(self):
|
||||||
|
|
@ -227,10 +170,6 @@ class AsyncContextManagerTestCase(unittest.TestCase):
|
||||||
await ctx.__aenter__()
|
await ctx.__aenter__()
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(RuntimeError):
|
||||||
await ctx.__aexit__(None, None, None)
|
await ctx.__aexit__(None, None, None)
|
||||||
if support.check_impl_detail(cpython=True):
|
|
||||||
# The "gen" attribute is an implementation detail.
|
|
||||||
if support.cl2_async_gens_have_ag_suspended:
|
|
||||||
self.assertFalse(ctx.gen.ag_suspended)
|
|
||||||
|
|
||||||
@_async_test
|
@_async_test
|
||||||
async def test_contextmanager_non_normalised(self):
|
async def test_contextmanager_non_normalised(self):
|
||||||
|
|
@ -270,18 +209,7 @@ class AsyncContextManagerTestCase(unittest.TestCase):
|
||||||
async def woohoo():
|
async def woohoo():
|
||||||
yield
|
yield
|
||||||
|
|
||||||
class StopIterationSubclass(StopIteration):
|
for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):
|
||||||
pass
|
|
||||||
|
|
||||||
class StopAsyncIterationSubclass(StopAsyncIteration):
|
|
||||||
pass
|
|
||||||
|
|
||||||
for stop_exc in (
|
|
||||||
StopIteration('spam'),
|
|
||||||
StopAsyncIteration('ham'),
|
|
||||||
StopIterationSubclass('spam'),
|
|
||||||
StopAsyncIterationSubclass('spam')
|
|
||||||
):
|
|
||||||
with self.subTest(type=type(stop_exc)):
|
with self.subTest(type=type(stop_exc)):
|
||||||
try:
|
try:
|
||||||
async with woohoo():
|
async with woohoo():
|
||||||
|
|
@ -379,82 +307,6 @@ class AsyncContextManagerTestCase(unittest.TestCase):
|
||||||
self.assertEqual(ncols, 10)
|
self.assertEqual(ncols, 10)
|
||||||
self.assertEqual(depth, 0)
|
self.assertEqual(depth, 0)
|
||||||
|
|
||||||
@_async_test
|
|
||||||
async def test_decorator(self):
|
|
||||||
entered = False
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def context():
|
|
||||||
nonlocal entered
|
|
||||||
entered = True
|
|
||||||
yield
|
|
||||||
entered = False
|
|
||||||
|
|
||||||
@context()
|
|
||||||
async def test():
|
|
||||||
self.assertTrue(entered)
|
|
||||||
|
|
||||||
self.assertFalse(entered)
|
|
||||||
await test()
|
|
||||||
self.assertFalse(entered)
|
|
||||||
|
|
||||||
@_async_test
|
|
||||||
async def test_decorator_with_exception(self):
|
|
||||||
entered = False
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def context():
|
|
||||||
nonlocal entered
|
|
||||||
try:
|
|
||||||
entered = True
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
entered = False
|
|
||||||
|
|
||||||
@context()
|
|
||||||
async def test():
|
|
||||||
self.assertTrue(entered)
|
|
||||||
raise NameError('foo')
|
|
||||||
|
|
||||||
self.assertFalse(entered)
|
|
||||||
with self.assertRaisesRegex(NameError, 'foo'):
|
|
||||||
await test()
|
|
||||||
self.assertFalse(entered)
|
|
||||||
|
|
||||||
@_async_test
|
|
||||||
async def test_decorating_method(self):
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def context():
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
class Test(object):
|
|
||||||
|
|
||||||
@context()
|
|
||||||
async def method(self, a, b, c=None):
|
|
||||||
self.a = a
|
|
||||||
self.b = b
|
|
||||||
self.c = c
|
|
||||||
|
|
||||||
# these tests are for argument passing when used as a decorator
|
|
||||||
test = Test()
|
|
||||||
await test.method(1, 2)
|
|
||||||
self.assertEqual(test.a, 1)
|
|
||||||
self.assertEqual(test.b, 2)
|
|
||||||
self.assertEqual(test.c, None)
|
|
||||||
|
|
||||||
test = Test()
|
|
||||||
await test.method('a', 'b', 'c')
|
|
||||||
self.assertEqual(test.a, 'a')
|
|
||||||
self.assertEqual(test.b, 'b')
|
|
||||||
self.assertEqual(test.c, 'c')
|
|
||||||
|
|
||||||
test = Test()
|
|
||||||
await test.method(a=1, b=2)
|
|
||||||
self.assertEqual(test.a, 1)
|
|
||||||
self.assertEqual(test.b, 2)
|
|
||||||
|
|
||||||
|
|
||||||
class AclosingTestCase(unittest.TestCase):
|
class AclosingTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
@ -547,13 +399,6 @@ class TestAsyncExitStack(TestBaseExitStack, unittest.TestCase):
|
||||||
return self.run_coroutine(self.__aexit__(*exc_details))
|
return self.run_coroutine(self.__aexit__(*exc_details))
|
||||||
|
|
||||||
exit_stack = SyncAsyncExitStack
|
exit_stack = SyncAsyncExitStack
|
||||||
callback_error_internal_frames = [
|
|
||||||
('__exit__', 'return self.run_coroutine(self.__aexit__(*exc_details))'),
|
|
||||||
('run_coroutine', 'raise exc'),
|
|
||||||
('run_coroutine', 'raise exc'),
|
|
||||||
('__aexit__', 'raise exc_details[1]'),
|
|
||||||
('__aexit__', 'cb_suppress = cb(*exc_details)'),
|
|
||||||
]
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.loop = asyncio.new_event_loop()
|
self.loop = asyncio.new_event_loop()
|
||||||
|
|
@ -641,7 +486,7 @@ class TestAsyncExitStack(TestBaseExitStack, unittest.TestCase):
|
||||||
1/0
|
1/0
|
||||||
|
|
||||||
@_async_test
|
@_async_test
|
||||||
async def test_enter_async_context(self):
|
async def test_async_enter_context(self):
|
||||||
class TestCM(object):
|
class TestCM(object):
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
result.append(1)
|
result.append(1)
|
||||||
|
|
@ -662,27 +507,6 @@ class TestAsyncExitStack(TestBaseExitStack, unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(result, [1, 2, 3, 4])
|
self.assertEqual(result, [1, 2, 3, 4])
|
||||||
|
|
||||||
@_async_test
|
|
||||||
async def test_enter_async_context_errors(self):
|
|
||||||
class LacksEnterAndExit:
|
|
||||||
pass
|
|
||||||
class LacksEnter:
|
|
||||||
async def __aexit__(self, *exc_info):
|
|
||||||
pass
|
|
||||||
class LacksExit:
|
|
||||||
async def __aenter__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
expected_error, expected_text = support.cl2_cm_api_exc_info_async()
|
|
||||||
async with self.exit_stack() as stack:
|
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
await stack.enter_async_context(LacksEnterAndExit())
|
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
await stack.enter_async_context(LacksEnter())
|
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
await stack.enter_async_context(LacksExit())
|
|
||||||
self.assertFalse(stack._exit_callbacks)
|
|
||||||
|
|
||||||
@_async_test
|
@_async_test
|
||||||
async def test_async_exit_exception_chaining(self):
|
async def test_async_exit_exception_chaining(self):
|
||||||
# Ensure exception chaining matches the reference behaviour
|
# Ensure exception chaining matches the reference behaviour
|
||||||
|
|
@ -715,54 +539,6 @@ class TestAsyncExitStack(TestBaseExitStack, unittest.TestCase):
|
||||||
self.assertIsInstance(inner_exc, ValueError)
|
self.assertIsInstance(inner_exc, ValueError)
|
||||||
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
|
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
|
||||||
|
|
||||||
@_async_test
|
|
||||||
async def test_async_exit_exception_explicit_none_context(self):
|
|
||||||
# Ensure AsyncExitStack chaining matches actual nested `with` statements
|
|
||||||
# regarding explicit __context__ = None.
|
|
||||||
|
|
||||||
class MyException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def my_cm():
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
except BaseException:
|
|
||||||
exc = MyException()
|
|
||||||
try:
|
|
||||||
raise exc
|
|
||||||
finally:
|
|
||||||
exc.__context__ = None
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def my_cm_with_exit_stack():
|
|
||||||
async with self.exit_stack() as stack:
|
|
||||||
await stack.enter_async_context(my_cm())
|
|
||||||
yield stack
|
|
||||||
|
|
||||||
for cm in (my_cm, my_cm_with_exit_stack):
|
|
||||||
with self.subTest():
|
|
||||||
try:
|
|
||||||
async with cm():
|
|
||||||
raise IndexError()
|
|
||||||
except MyException as exc:
|
|
||||||
self.assertIsNone(exc.__context__)
|
|
||||||
else:
|
|
||||||
self.fail("Expected IndexError, but no exception was raised")
|
|
||||||
|
|
||||||
@_async_test
|
|
||||||
async def test_instance_bypass_async(self):
|
|
||||||
class Example(object): pass
|
|
||||||
cm = Example()
|
|
||||||
cm.__aenter__ = object()
|
|
||||||
cm.__aexit__ = object()
|
|
||||||
stack = self.exit_stack()
|
|
||||||
expected_error, expected_text = support.cl2_cm_api_exc_info_async()
|
|
||||||
with self.assertRaisesRegex(expected_error, expected_text):
|
|
||||||
await stack.enter_async_context(cm)
|
|
||||||
stack.push_async_exit(cm)
|
|
||||||
self.assertIs(stack._exit_callbacks[-1][1], cm)
|
|
||||||
|
|
||||||
|
|
||||||
class TestAsyncNullcontext(unittest.TestCase):
|
class TestAsyncNullcontext(unittest.TestCase):
|
||||||
@_async_test
|
@_async_test
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
test_contextlib uses this folder for chdir tests
|
|
||||||
11
tox.ini
11
tox.ini
|
|
@ -1,6 +1,5 @@
|
||||||
[tox]
|
[tox]
|
||||||
# Python 3.8 is the first version with positional-only argument syntax support
|
envlist = py{36,37,38,39,3_10,py3}
|
||||||
envlist = py{38,39,3.10,3.11,3.12,py3}
|
|
||||||
skip_missing_interpreters = True
|
skip_missing_interpreters = True
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
|
@ -16,9 +15,9 @@ deps =
|
||||||
|
|
||||||
[gh-actions]
|
[gh-actions]
|
||||||
python =
|
python =
|
||||||
|
3.6: py36
|
||||||
|
3.7: py37
|
||||||
3.8: py38
|
3.8: py38
|
||||||
3.9: py39
|
3.9: py39
|
||||||
3.10: py3.10
|
3.10: py3_10
|
||||||
3.11: py3.11
|
pypy3: pypy3
|
||||||
3.12: py3.12
|
|
||||||
pypy-3.10: pypy3
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue