mirror of
https://github.com/jazzband/contextlib2.git
synced 2026-03-20 15:40:27 +00:00
Compare commits
34 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a547a3247 | ||
|
|
e713c7cd64 | ||
|
|
0d2a7d056e | ||
|
|
15d711ad16 | ||
|
|
892462960d | ||
|
|
3fbca593ec | ||
|
|
f64cf04df8 | ||
|
|
8fe4d73971 | ||
|
|
7b862aaa80 | ||
|
|
defc103aae | ||
|
|
7e2a0a900a | ||
|
|
7c6b72683c | ||
|
|
4be95fed29 | ||
|
|
690922cfde | ||
|
|
c127f51df1 | ||
|
|
241de07097 | ||
|
|
ff30b3a68f | ||
|
|
9ef8594837 | ||
|
|
89893bba60 | ||
|
|
26ad9aef80 | ||
|
|
c1d9e6a4a7 | ||
|
|
d63f68b104 | ||
|
|
0828b5a332 | ||
|
|
1ea4e4dfe0 | ||
|
|
3feaef8f91 | ||
|
|
89c5189648 | ||
|
|
5373afc4ba | ||
|
|
3d2540dda1 | ||
|
|
d78420a808 | ||
|
|
a35252eefd | ||
|
|
16db3dcf86 | ||
|
|
2dc4c04492 | ||
|
|
908e2da622 | ||
|
|
c907a9975e |
36 changed files with 2495 additions and 748 deletions
19
.github/workflows/test.yml
vendored
19
.github/workflows/test.yml
vendored
|
|
@ -1,6 +1,12 @@
|
||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -8,13 +14,16 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
max-parallel: 5
|
max-parallel: 5
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [3.6, 3.7, 3.8, 3.9, '3.10.0-beta - 3.10', 'pypy3']
|
python-version: [3.8, 3.9, '3.10', 3.11, 3.12, 'pypy-3.10']
|
||||||
|
|
||||||
|
# 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@v2
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
|
@ -24,7 +33,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@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.pip-cache.outputs.dir }}
|
path: ${{ steps.pip-cache.outputs.dir }}
|
||||||
key:
|
key:
|
||||||
|
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -11,3 +11,7 @@ MANIFEST
|
||||||
.coverage
|
.coverage
|
||||||
coverage.xml
|
coverage.xml
|
||||||
htmlcov/
|
htmlcov/
|
||||||
|
|
||||||
|
# Patching output files
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
|
|
||||||
14
.pre-commit-config.yaml
Normal file
14
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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
|
||||||
35
.readthedocs.yaml
Normal file
35
.readthedocs.yaml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# 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
|
||||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# 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
|
recursive-include dev *.patch *.allowlist *.sh
|
||||||
|
|
|
||||||
36
NEWS.rst
36
NEWS.rst
|
|
@ -1,6 +1,40 @@
|
||||||
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)
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
@ -64,7 +98,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 (Nick Coghlan)
|
[Jazzband](https://jazzband.co/) project! This means that I (Alyssa 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
|
||||||
* PyPy3
|
* CPython 3.11
|
||||||
|
* 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.10, 4 files needed to be copied from the CPython reference
|
As of Python 3.12.3, 4 files needed to be copied from the CPython reference
|
||||||
implementation to contextlib2:
|
implementation to contextlib2:
|
||||||
|
|
||||||
* ``Doc/contextlib.rst`` -> ``docs/contextlib2.rst``
|
* ``Doc/library/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,19 +72,31 @@ 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 made to ``contextlib2/__init__.py`` to get it to run on the older
|
* changes 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 made to ``contextlib2/__init__.pyi`` to make the Python version
|
* changes to ``test/test_contextlib.py`` and ``test/test_contextlib_async.py``
|
||||||
|
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 made to ``docs/contextlib2.rst`` to use ``contextlib2`` version
|
* changes 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 @@
|
||||||
21.6.0
|
24.6.0rc1
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,60 @@
|
||||||
"""contextlib2 - backports and enhancements to the contextlib module"""
|
"""Utilities for with-statement contexts. See PEP 343."""
|
||||||
|
|
||||||
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.6/3.7/3.8 compatibility: GenericAlias may not be defined
|
# Python 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)
|
||||||
|
|
@ -86,6 +114,7 @@ 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()
|
||||||
|
|
@ -143,18 +172,20 @@ 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):
|
||||||
# _GCM instances are one-shot context managers, so the
|
# _GCMB 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
|
||||||
|
|
@ -164,21 +195,24 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise RuntimeError("generator didn't yield") from None
|
raise RuntimeError("generator didn't yield") from None
|
||||||
|
|
||||||
def __exit__(self, type, value, traceback):
|
def __exit__(self, typ, value, traceback):
|
||||||
if type is None:
|
if typ is None:
|
||||||
try:
|
try:
|
||||||
next(self.gen)
|
next(self.gen)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("generator didn't stop")
|
try:
|
||||||
|
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 = type()
|
value = typ()
|
||||||
try:
|
try:
|
||||||
self.gen.throw(type, value, traceback)
|
self.gen.throw(value)
|
||||||
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
|
||||||
|
|
@ -187,68 +221,7 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
|
||||||
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:
|
||||||
return False
|
exc.__traceback__ = traceback
|
||||||
# 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
|
||||||
|
|
@ -256,13 +229,101 @@ class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
|
||||||
# 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 isinstance(value, (StopIteration, StopAsyncIteration)):
|
if (
|
||||||
if exc.__cause__ is value:
|
isinstance(value, StopIteration)
|
||||||
return False
|
and exc.__cause__ is value
|
||||||
|
):
|
||||||
|
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):
|
||||||
|
|
@ -449,7 +510,16 @@ 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
|
||||||
return exctype is not None and issubclass(exctype, self._exceptions)
|
if exctype is None:
|
||||||
|
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:
|
||||||
|
|
@ -460,9 +530,7 @@ class _BaseExitStack:
|
||||||
return MethodType(cm_exit, cm)
|
return MethodType(cm_exit, cm)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_cb_wrapper(*args, **kwds):
|
def _create_cb_wrapper(callback, /, *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
|
||||||
|
|
@ -505,24 +573,22 @@ 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.
|
||||||
_cm_type = type(cm)
|
cls = type(cm)
|
||||||
_exit = _cm_type.__exit__
|
try:
|
||||||
result = _cm_type.__enter__(cm)
|
_enter = cls.__enter__
|
||||||
|
_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(*args, **kwds):
|
def callback(self, 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
|
||||||
|
|
@ -565,10 +631,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 old_exc:
|
if exc_context is None or 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 None or exc_context is frame_exc:
|
if 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
|
||||||
|
|
@ -628,9 +694,7 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager):
|
||||||
return MethodType(cm_exit, cm)
|
return MethodType(cm_exit, cm)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_async_cb_wrapper(*args, **kwds):
|
def _create_async_cb_wrapper(callback, /, *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
|
||||||
|
|
@ -641,9 +705,15 @@ 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.
|
||||||
"""
|
"""
|
||||||
_cm_type = type(cm)
|
cls = type(cm)
|
||||||
_exit = _cm_type.__aexit__
|
try:
|
||||||
result = await _cm_type.__aenter__(cm)
|
_enter = cls.__aenter__
|
||||||
|
_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
|
||||||
|
|
||||||
|
|
@ -665,18 +735,11 @@ 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(*args, **kwds):
|
def push_async_callback(self, 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
|
||||||
|
|
@ -708,10 +771,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 old_exc:
|
if exc_context is None or 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 None or exc_context is frame_exc:
|
if 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
|
||||||
|
|
@ -779,11 +842,26 @@ 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):
|
||||||
"""Backwards compatibility alias for ExitStack"""
|
"""(DEPRECATED) 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,132 +1,201 @@
|
||||||
# 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
|
||||||
|
|
||||||
import sys
|
# For updates: https://github.com/python/typeshed/blob/main/stdlib/contextlib.pyi
|
||||||
from types import TracebackType
|
|
||||||
from typing import (
|
# Last updated: 2024-05-22
|
||||||
IO,
|
# Updated from: https://github.com/python/typeshed/blob/aa2d33df211e1e4f70883388febf750ac524d2bb/stdlib/contextlib.pyi
|
||||||
Any,
|
# Saved to: dev/typeshed_contextlib.pyi
|
||||||
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)
|
||||||
|
|
||||||
AbstractContextManager = ContextManager
|
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 True:
|
if True:
|
||||||
AbstractAsyncContextManager = AsyncContextManager
|
__all__ += ["aclosing"]
|
||||||
|
|
||||||
|
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=Optional[IO[str]])
|
_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])
|
_F = TypeVar("_F", bound=Callable[..., Any])
|
||||||
_P = ParamSpec("_P")
|
_P = ParamSpec("_P")
|
||||||
|
|
||||||
_ExitFunc = Callable[[Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]], bool]
|
_ExitFunc: TypeAlias = Callable[[type[BaseException] | None, BaseException | None, TracebackType | None], bool | None]
|
||||||
_CM_EF = TypeVar("_CM_EF", ContextManager[Any], _ExitFunc)
|
_CM_EF = TypeVar("_CM_EF", bound=AbstractContextManager[Any, Any] | _ExitFunc)
|
||||||
|
|
||||||
class _GeneratorContextManager(ContextManager[_T_co]):
|
@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: ...
|
def __call__(self, func: _F) -> _F: ...
|
||||||
|
|
||||||
# type ignore to deal with incomplete ParamSpec support in mypy
|
class _GeneratorContextManager(AbstractContextManager[_T_co, bool | None], ContextDecorator):
|
||||||
def contextmanager(func: Callable[_P, Iterator[_T]]) -> Callable[_P, _GeneratorContextManager[_T]]: ... # type: ignore
|
# __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 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:
|
||||||
def asynccontextmanager(func: Callable[_P, AsyncIterator[_T]]) -> Callable[_P, AsyncContextManager[_T]]: ... # type: ignore
|
_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: ...
|
||||||
|
|
||||||
|
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(ContextManager[_SupportsCloseT]):
|
class closing(AbstractContextManager[_SupportsCloseT, None]):
|
||||||
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):
|
||||||
async def aclose(self) -> object: ...
|
def aclose(self) -> Awaitable[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: ...
|
||||||
_AF = TypeVar("_AF", bound=Callable[..., Awaitable[Any]])
|
async def __aexit__(self, *exc_info: Unused) -> None: ...
|
||||||
class AsyncContextDecorator:
|
|
||||||
def __call__(self, func: _AF) -> _AF: ...
|
|
||||||
|
|
||||||
class suppress(ContextManager[None]):
|
class suppress(AbstractContextManager[None, bool]):
|
||||||
def __init__(self, *exceptions: Type[BaseException]) -> None: ...
|
def __init__(self, *exceptions: type[BaseException]) -> None: ...
|
||||||
def __exit__(
|
def __exit__(
|
||||||
self, exctype: Optional[Type[BaseException]], excinst: Optional[BaseException], exctb: Optional[TracebackType]
|
self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None
|
||||||
) -> bool: ...
|
) -> bool: ...
|
||||||
|
|
||||||
class redirect_stdout(ContextManager[_T_io]):
|
class _RedirectStream(AbstractContextManager[_T_io, None]):
|
||||||
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_stderr(ContextManager[_T_io]):
|
class redirect_stdout(_RedirectStream[_T_io]): ...
|
||||||
def __init__(self, new_target: _T_io) -> None: ...
|
class redirect_stderr(_RedirectStream[_T_io]): ...
|
||||||
|
|
||||||
class ContextDecorator:
|
# In reality this is a subclass of `AbstractContextManager`;
|
||||||
def __call__(self, func: _F) -> _F: ...
|
# see #7961 for why we don't do that in the stub
|
||||||
|
class ExitStack(Generic[_ExitT_co], metaclass=abc.ABCMeta):
|
||||||
_U = TypeVar("_U", bound=ExitStack)
|
def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ...
|
||||||
|
|
||||||
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[..., Any], *args: Any, **kwds: Any) -> Callable[..., Any]: ...
|
def callback(self, callback: Callable[_P, _T], /, *args: _P.args, **kwds: _P.kwargs) -> Callable[_P, _T]: ...
|
||||||
def pop_all(self: _U) -> _U: ...
|
def pop_all(self) -> Self: ...
|
||||||
def close(self) -> None: ...
|
def close(self) -> None: ...
|
||||||
def __enter__(self: _U) -> _U: ...
|
def __enter__(self) -> Self: ...
|
||||||
def __exit__(
|
def __exit__(
|
||||||
self,
|
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
|
||||||
__exc_type: Optional[Type[BaseException]],
|
) -> _ExitT_co: ...
|
||||||
__exc_value: Optional[BaseException],
|
|
||||||
__traceback: Optional[TracebackType],
|
_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: ...
|
) -> bool: ...
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
_S = TypeVar("_S", bound=AsyncExitStack)
|
class nullcontext(AbstractContextManager[_T, None], AbstractAsyncContextManager[_T, None]):
|
||||||
|
|
||||||
_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: ...
|
def __init__(self: nullcontext[None], enter_result: None = None) -> None: ...
|
||||||
@overload
|
@overload
|
||||||
def __init__(self: nullcontext[_T], enter_result: _T) -> None: ...
|
def __init__(self: nullcontext[_T], enter_result: _T) -> None: ... # pyright: ignore[reportInvalidTypeVarUse] #11780
|
||||||
def __enter__(self) -> _T: ...
|
def __enter__(self) -> _T: ...
|
||||||
def __exit__(self, *exctype: Any) -> bool: ...
|
def __exit__(self, *exctype: Unused) -> None: ...
|
||||||
|
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,3 +1,10 @@
|
||||||
# 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__
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
--- ../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
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
--- ../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
|
|
||||||
--------------------
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
--- ../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()
|
|
||||||
116
dev/py3_12_py_to_contextlib2.patch
Normal file
116
dev/py3_12_py_to_contextlib2.patch
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
--- /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()
|
||||||
112
dev/py3_12_pyi_to_contextlib2.patch
Normal file
112
dev/py3_12_pyi_to_contextlib2.patch
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
--- /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]):
|
||||||
453
dev/py3_12_rst_to_contextlib2.patch
Normal file
453
dev/py3_12_rst_to_contextlib2.patch
Normal file
|
|
@ -0,0 +1,453 @@
|
||||||
|
--- /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:
|
||||||
67
dev/py3_12_test_async_to_contextlib2.patch
Normal file
67
dev/py3_12_test_async_to_contextlib2.patch
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
--- /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)
|
||||||
106
dev/py3_12_test_to_contextlib2.patch
Normal file
106
dev/py3_12_test_to_contextlib2.patch
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
--- /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",
|
||||||
27
dev/save_diff_snapshot.sh
Executable file
27
dev/save_diff_snapshot.sh
Executable file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/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"
|
||||||
19
dev/sync_from_cpython.sh
Executable file
19
dev/sync_from_cpython.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/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
|
||||||
209
dev/typeshed_contextlib.pyi
Normal file
209
dev/typeshed_contextlib.pyi
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
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,8 +11,6 @@
|
||||||
# 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.
|
||||||
|
|
@ -25,7 +23,10 @@ import sys, os
|
||||||
|
|
||||||
# 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 = ['sphinx.ext.intersphinx']
|
extensions = [
|
||||||
|
'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']
|
||||||
|
|
@ -41,7 +42,7 @@ master_doc = 'index'
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'contextlib2'
|
project = u'contextlib2'
|
||||||
copyright = u'2021, Nick Coghlan'
|
copyright = u'2024, Alyssa 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
|
||||||
|
|
@ -92,7 +93,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 = 'default'
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
|
||||||
# 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
|
||||||
|
|
@ -121,7 +122,7 @@ html_theme = 'default'
|
||||||
# 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 = ['_static']
|
html_static_path = []
|
||||||
|
|
||||||
# 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.
|
||||||
|
|
@ -180,7 +181,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'Nick Coghlan', 'manual'),
|
u'Alyssa 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
|
||||||
|
|
@ -213,9 +214,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'Nick Coghlan'], 1)
|
[u'Alyssa Coghlan'], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Configuration for intersphinx: refer to the Python 3 standard library.
|
# Configuration for intersphinx: refer to the Python 3 standard library.
|
||||||
intersphinx_mapping = {'http://docs.python.org/3': None}
|
intersphinx_mapping = {'python': ('https://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:`__enter__` and :meth:`__exit__` methods.
|
create a class or separate :meth:`~object.__enter__` and :meth:`~object.__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 ``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
|
An abstract example would be the following to ensure correct resource
|
||||||
management::
|
management::
|
||||||
|
|
||||||
from contextlib import contextmanager
|
from contextlib2 import contextmanager
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def managed_resource(*args, **kwds):
|
def managed_resource(*args, **kwds):
|
||||||
|
|
@ -51,6 +51,8 @@ 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
|
||||||
|
|
@ -81,18 +83,18 @@ Functions and classes provided:
|
||||||
|
|
||||||
.. decorator:: asynccontextmanager
|
.. 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>`.
|
: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:`__aenter__` and
|
without needing to create a class or separate :meth:`~object.__aenter__` and
|
||||||
:meth:`__aexit__` methods. It must be applied to an :term:`asynchronous
|
:meth:`~object.__aexit__` methods. It must be applied to an :term:`asynchronous
|
||||||
generator` function.
|
generator` function.
|
||||||
|
|
||||||
A simple example::
|
A simple example::
|
||||||
|
|
||||||
from contextlib import asynccontextmanager
|
from contextlib2 import asynccontextmanager
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def get_connection():
|
async def get_connection():
|
||||||
|
|
@ -115,7 +117,9 @@ 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:
|
||||||
|
|
@ -123,9 +127,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
|
||||||
|
|
@ -138,7 +142,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 contextlib import contextmanager
|
from contextlib2 import contextmanager
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def closing(thing):
|
def closing(thing):
|
||||||
|
|
@ -149,23 +153,31 @@ Functions and classes provided:
|
||||||
|
|
||||||
And lets you write code like this::
|
And lets you write code like this::
|
||||||
|
|
||||||
from contextlib import closing
|
from contextlib2 import closing
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
with closing(urlopen('http://www.python.org')) as page:
|
with closing(urlopen('https://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::
|
||||||
|
|
||||||
.. class:: aclosing(thing)
|
Most types managing resources support the :term:`context manager` protocol,
|
||||||
|
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 contextlib import asynccontextmanager
|
from contextlib2 import asynccontextmanager
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def aclosing(thing):
|
async def aclosing(thing):
|
||||||
|
|
@ -178,7 +190,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 contextlib import aclosing
|
from contextlib2 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:
|
||||||
|
|
@ -205,10 +217,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 = contextlib.suppress(Exception)
|
cm = contextlib2.suppress(Exception)
|
||||||
else:
|
else:
|
||||||
# Do not ignore any exceptions, cm has no effect.
|
# Do not ignore any exceptions, cm has no effect.
|
||||||
cm = contextlib.nullcontext()
|
cm = contextlib2.nullcontext()
|
||||||
with cm:
|
with cm:
|
||||||
# Do something
|
# Do something
|
||||||
|
|
||||||
|
|
@ -229,15 +241,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
|
||||||
|
|
@ -260,7 +272,7 @@ Functions and classes provided:
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
from contextlib import suppress
|
from contextlib2 import suppress
|
||||||
|
|
||||||
with suppress(FileNotFoundError):
|
with suppress(FileNotFoundError):
|
||||||
os.remove('somefile.tmp')
|
os.remove('somefile.tmp')
|
||||||
|
|
@ -282,9 +294,17 @@ 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)
|
||||||
|
|
||||||
|
|
@ -329,7 +349,7 @@ Functions and classes provided:
|
||||||
|
|
||||||
.. function:: redirect_stderr(new_target)
|
.. 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.
|
: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>`.
|
||||||
|
|
@ -338,6 +358,24 @@ 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.
|
||||||
|
|
@ -351,7 +389,7 @@ Functions and classes provided:
|
||||||
|
|
||||||
Example of ``ContextDecorator``::
|
Example of ``ContextDecorator``::
|
||||||
|
|
||||||
from contextlib import ContextDecorator
|
from contextlib2 import ContextDecorator
|
||||||
|
|
||||||
class mycontext(ContextDecorator):
|
class mycontext(ContextDecorator):
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
|
@ -362,6 +400,8 @@ 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')
|
||||||
|
|
@ -396,7 +436,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 contextlib import ContextDecorator
|
from contextlib2 import ContextDecorator
|
||||||
|
|
||||||
class mycontext(ContextBaseClass, ContextDecorator):
|
class mycontext(ContextBaseClass, ContextDecorator):
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
|
@ -419,7 +459,7 @@ Functions and classes provided:
|
||||||
Example of ``AsyncContextDecorator``::
|
Example of ``AsyncContextDecorator``::
|
||||||
|
|
||||||
from asyncio import run
|
from asyncio import run
|
||||||
from contextlib import AsyncContextDecorator
|
from contextlib2 import AsyncContextDecorator
|
||||||
|
|
||||||
class mycontext(AsyncContextDecorator):
|
class mycontext(AsyncContextDecorator):
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
|
|
@ -430,6 +470,8 @@ 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')
|
||||||
|
|
@ -467,6 +509,9 @@ 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*
|
||||||
|
|
@ -493,27 +538,32 @@ Functions and classes provided:
|
||||||
|
|
||||||
.. method:: enter_context(cm)
|
.. method:: enter_context(cm)
|
||||||
|
|
||||||
Enters a new context manager and adds its :meth:`__exit__` method to
|
Enters a new context manager and adds its :meth:`~object.__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:`__enter__` method.
|
manager's own :meth:`~object.__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:`__exit__` method to the callback stack.
|
Adds a context manager's :meth:`~object.__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:`__enter__` implementation with a context manager's own
|
part of an :meth:`~object.__enter__` implementation with a context manager's own
|
||||||
:meth:`__exit__` method.
|
:meth:`~object.__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:`__exit__` method and adds it directly to the callback stack.
|
:meth:`~object.__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:`__exit__` methods can.
|
same way context manager :meth:`~object.__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.
|
||||||
|
|
@ -562,26 +612,33 @@ 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:`close` method is not implemented, :meth:`aclose` must be used
|
The :meth:`~ExitStack.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:`enter_context` but expects an asynchronous context
|
Similar to :meth:`ExitStack.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:`push` but expects either an asynchronous context manager
|
Similar to :meth:`ExitStack.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:`callback` but expects a coroutine function.
|
Similar to :meth:`ExitStack.callback` but expects a coroutine function.
|
||||||
|
|
||||||
.. method:: aclose()
|
.. method:: aclose()
|
||||||
|
:async:
|
||||||
|
|
||||||
Similar to :meth:`close` but properly handles awaitables.
|
Similar to :meth:`ExitStack.close` but properly handles awaitables.
|
||||||
|
|
||||||
Continuing the example for :func:`asynccontextmanager`::
|
Continuing the example for :func:`asynccontextmanager`::
|
||||||
|
|
||||||
|
|
@ -600,7 +657,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:`contextlib`.
|
the tools provided by :mod:`contextlib2`.
|
||||||
|
|
||||||
|
|
||||||
Supporting a variable number of context managers
|
Supporting a variable number of context managers
|
||||||
|
|
@ -658,13 +715,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:`__enter__` implementation fail.
|
steps in the :meth:`~object.__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 contextlib import contextmanager, AbstractContextManager, ExitStack
|
from contextlib2 import contextmanager, AbstractContextManager, ExitStack
|
||||||
|
|
||||||
class ResourceManager(AbstractContextManager):
|
class ResourceManager(AbstractContextManager):
|
||||||
|
|
||||||
|
|
@ -724,7 +781,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 contextlib import ExitStack
|
from contextlib2 import ExitStack
|
||||||
|
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
stack.callback(cleanup_resources)
|
stack.callback(cleanup_resources)
|
||||||
|
|
@ -738,7 +795,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 contextlib import ExitStack
|
from contextlib2 import ExitStack
|
||||||
|
|
||||||
class Callback(ExitStack):
|
class Callback(ExitStack):
|
||||||
def __init__(self, callback, /, *args, **kwds):
|
def __init__(self, callback, /, *args, **kwds):
|
||||||
|
|
@ -758,7 +815,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 contextlib import ExitStack
|
from contextlib2 import ExitStack
|
||||||
|
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
@stack.callback
|
@stack.callback
|
||||||
|
|
@ -785,7 +842,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 contextlib import ContextDecorator
|
from contextlib2 import ContextDecorator
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
@ -815,7 +872,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:`__enter__`. If that value is needed, then it is still necessary to use
|
:meth:`~object.__enter__`. If that value is needed, then it is still necessary to use
|
||||||
an explicit ``with`` statement.
|
an explicit ``with`` statement.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
@ -847,7 +904,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 contextlib import contextmanager
|
>>> from contextlib2 import contextmanager
|
||||||
>>> @contextmanager
|
>>> @contextmanager
|
||||||
... def singleuse():
|
... def singleuse():
|
||||||
... print("Before")
|
... print("Before")
|
||||||
|
|
@ -879,10 +936,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` and :func:`redirect_stdout`. Here's a very simple example of
|
:func:`suppress`, :func:`redirect_stdout`, and :func:`chdir`. Here's a very
|
||||||
reentrant use::
|
simple example of reentrant use::
|
||||||
|
|
||||||
>>> from contextlib import redirect_stdout
|
>>> from contextlib2 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)
|
||||||
|
|
@ -928,7 +985,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 contextlib import ExitStack
|
>>> from contextlib2 import ExitStack
|
||||||
>>> stack = ExitStack()
|
>>> stack = ExitStack()
|
||||||
>>> with stack:
|
>>> with stack:
|
||||||
... stack.callback(print, "Callback: from first context")
|
... stack.callback(print, "Callback: from first context")
|
||||||
|
|
@ -962,7 +1019,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 contextlib import ExitStack
|
>>> from contextlib2 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,14 +21,15 @@ 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.10 version of
|
This module is primarily a backport of the Python 3.12.3 version of
|
||||||
:mod:`contextlib` to earlier releases. The async context management features
|
:mod:`contextlib` to earlier releases. (Note: as of the start of the Python 3.13
|
||||||
require asynchronous generator support in the language runtime, so the oldest
|
beta release cycle, there have been no subsequent changes to ``contextlib``)
|
||||||
supported version is now Python 3.6 (contextlib2 0.6.0 and earlier support
|
|
||||||
older Python versions by omitting all asynchronous features).
|
|
||||||
|
|
||||||
This module is also a proving ground for new features not yet part of the
|
The module makes use of positional-only argument syntax in several call
|
||||||
standard library. There are currently no such features in the module.
|
signatures, so the oldest supported Python version is Python 3.8.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -55,7 +56,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.6+.
|
Supported Python versions are currently 3.8+.
|
||||||
|
|
||||||
.. _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
docs/requirements.txt
Normal file
1
docs/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
sphinx-rtd-theme
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
[bdist_wheel]
|
|
||||||
universal=1
|
|
||||||
20
setup.py
20
setup.py
|
|
@ -4,18 +4,29 @@ 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.6',
|
python_requires='>=3.7',
|
||||||
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='Nick Coghlan',
|
author='Alyssa Coghlan',
|
||||||
author_email='ncoghlan@gmail.com',
|
author_email='ncoghlan@gmail.com',
|
||||||
url='http://contextlib2.readthedocs.org',
|
url='https://github.com/jazzband/contextlib2',
|
||||||
|
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',
|
||||||
|
|
@ -23,11 +34,12 @@ 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
test/data/README.txt
Normal file
1
test/data/README.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
test_contextlib uses this folder for chdir tests
|
||||||
|
|
@ -2,5 +2,100 @@
|
||||||
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)
|
||||||
|
|
|
||||||
33
test/support/testcase.py
Normal file
33
test/support/testcase.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
"""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,15 +1,17 @@
|
||||||
"""Unit tests for contextlib.py, and other context managers."""
|
"""Unit tests for synchronous features of contextlib2.py"""
|
||||||
|
|
||||||
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):
|
||||||
|
|
@ -87,6 +89,56 @@ 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():
|
||||||
|
|
@ -105,9 +157,48 @@ class ContextManagerTestCase(unittest.TestCase):
|
||||||
yield
|
yield
|
||||||
ctx = whoo()
|
ctx = whoo()
|
||||||
ctx.__enter__()
|
ctx.__enter__()
|
||||||
self.assertRaises(
|
with self.assertRaises(RuntimeError):
|
||||||
RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None
|
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 = []
|
||||||
|
|
@ -127,19 +218,22 @@ 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:
|
|
||||||
with self.assertWarnsRegex(DeprecationWarning,
|
class StopIterationSubclass(StopIteration):
|
||||||
"StopIteration"):
|
pass
|
||||||
with woohoo():
|
|
||||||
raise stop_exc
|
for stop_exc in (StopIteration('spam'), StopIterationSubclass('spam')):
|
||||||
except Exception as ex:
|
with self.subTest(type=type(stop_exc)):
|
||||||
self.assertIs(ex, stop_exc)
|
try:
|
||||||
else:
|
with woohoo():
|
||||||
self.fail('StopIteration was suppressed')
|
raise stop_exc
|
||||||
|
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 = """\
|
||||||
|
|
@ -185,6 +279,25 @@ 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):
|
||||||
|
|
@ -196,6 +309,7 @@ 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):
|
||||||
|
|
@ -230,7 +344,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
|
||||||
gc.collect(); gc.collect(); gc.collect()
|
support.gc_collect()
|
||||||
self.assertIsNone(a())
|
self.assertIsNone(a())
|
||||||
self.assertIsNone(b())
|
self.assertIsNone(b())
|
||||||
yield
|
yield
|
||||||
|
|
@ -252,8 +366,11 @@ 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
|
||||||
|
|
@ -267,6 +384,7 @@ def woohoo():
|
||||||
recursive()
|
recursive()
|
||||||
|
|
||||||
recursive()
|
recursive()
|
||||||
|
self.assertEqual(ncols, 10)
|
||||||
self.assertEqual(depth, 0)
|
self.assertEqual(depth, 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -494,7 +612,8 @@ class TestContextDecorator(unittest.TestCase):
|
||||||
def __exit__(self, *exc):
|
def __exit__(self, *exc):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
with self.assertRaises(AttributeError):
|
expected_error, expected_text = support.cl2_cm_api_exc_info_sync("__enter__")
|
||||||
|
with self.assertRaisesRegex(expected_error, expected_text):
|
||||||
with mycontext():
|
with mycontext():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -506,7 +625,8 @@ class TestContextDecorator(unittest.TestCase):
|
||||||
def __uxit__(self, *exc):
|
def __uxit__(self, *exc):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
with self.assertRaises(AttributeError):
|
expected_error, expected_text = support.cl2_cm_api_exc_info_sync("__exit__")
|
||||||
|
with self.assertRaisesRegex(expected_error, expected_text):
|
||||||
with mycontext():
|
with mycontext():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -664,6 +784,26 @@ 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:
|
||||||
|
|
@ -699,6 +839,41 @@ 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
|
||||||
|
|
@ -778,6 +953,40 @@ 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):
|
||||||
|
|
@ -889,9 +1098,12 @@ 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()
|
||||||
self.assertRaises(AttributeError, stack.enter_context, cm)
|
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)
|
stack.push(cm)
|
||||||
self.assertIs(stack._exit_callbacks[-1][1], cm)
|
self.assertIs(stack._exit_callbacks[-1][1], cm)
|
||||||
|
|
||||||
|
|
@ -932,6 +1144,10 @@ 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:
|
||||||
|
|
@ -1003,7 +1219,7 @@ class TestRedirectStderr(TestRedirectStream, unittest.TestCase):
|
||||||
orig_stream = "stderr"
|
orig_stream = "stderr"
|
||||||
|
|
||||||
|
|
||||||
class TestSuppress(unittest.TestCase):
|
class TestSuppress(ExceptionIsLikeMixin, unittest.TestCase):
|
||||||
|
|
||||||
@support.requires_docstrings
|
@support.requires_docstrings
|
||||||
def test_instance_docs(self):
|
def test_instance_docs(self):
|
||||||
|
|
@ -1057,5 +1273,96 @@ class TestSuppress(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,3 +1,5 @@
|
||||||
|
"""Unit tests for asynchronous features of contextlib2.py"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib2 import (
|
from contextlib2 import (
|
||||||
asynccontextmanager, AbstractAsyncContextManager,
|
asynccontextmanager, AbstractAsyncContextManager,
|
||||||
|
|
@ -5,24 +7,23 @@ from contextlib2 import (
|
||||||
import functools
|
import functools
|
||||||
from test import support
|
from test import support
|
||||||
import unittest
|
import unittest
|
||||||
|
import traceback
|
||||||
|
|
||||||
from test.test_contextlib import TestBaseExitStack
|
from .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)
|
||||||
loop = asyncio.new_event_loop()
|
asyncio.run(coro)
|
||||||
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):
|
||||||
|
|
||||||
|
|
@ -50,15 +51,11 @@ class TestAbstractAsyncContextManager(unittest.TestCase):
|
||||||
async with ctx():
|
async with ctx():
|
||||||
yield 11
|
yield 11
|
||||||
|
|
||||||
ret = []
|
g = gen()
|
||||||
exc = ValueError(22)
|
async for val in g:
|
||||||
with self.assertRaises(ValueError):
|
self.assertEqual(val, 11)
|
||||||
async with ctx():
|
break
|
||||||
async for val in gen():
|
await g.aclose()
|
||||||
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):
|
||||||
|
|
@ -127,6 +124,62 @@ 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
|
||||||
|
|
@ -149,6 +202,10 @@ 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):
|
||||||
|
|
@ -170,6 +227,10 @@ 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):
|
||||||
|
|
@ -209,7 +270,18 @@ class AsyncContextManagerTestCase(unittest.TestCase):
|
||||||
async def woohoo():
|
async def woohoo():
|
||||||
yield
|
yield
|
||||||
|
|
||||||
for stop_exc in (StopIteration('spam'), StopAsyncIteration('ham')):
|
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)):
|
with self.subTest(type=type(stop_exc)):
|
||||||
try:
|
try:
|
||||||
async with woohoo():
|
async with woohoo():
|
||||||
|
|
@ -307,6 +379,82 @@ 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):
|
||||||
|
|
||||||
|
|
@ -399,6 +547,13 @@ 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()
|
||||||
|
|
@ -486,7 +641,7 @@ class TestAsyncExitStack(TestBaseExitStack, unittest.TestCase):
|
||||||
1/0
|
1/0
|
||||||
|
|
||||||
@_async_test
|
@_async_test
|
||||||
async def test_async_enter_context(self):
|
async def test_enter_async_context(self):
|
||||||
class TestCM(object):
|
class TestCM(object):
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
result.append(1)
|
result.append(1)
|
||||||
|
|
@ -507,6 +662,27 @@ 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
|
||||||
|
|
@ -539,6 +715,54 @@ 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
test/ziptestdata/README.txt
Normal file
1
test/ziptestdata/README.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
test_contextlib uses this folder for chdir tests
|
||||||
11
tox.ini
11
tox.ini
|
|
@ -1,5 +1,6 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py{36,37,38,39,3_10,py3}
|
# Python 3.8 is the first version with positional-only argument syntax support
|
||||||
|
envlist = py{38,39,3.10,3.11,3.12,py3}
|
||||||
skip_missing_interpreters = True
|
skip_missing_interpreters = True
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
|
@ -15,9 +16,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
|
||||||
pypy3: pypy3
|
3.11: py3.11
|
||||||
|
3.12: py3.12
|
||||||
|
pypy-3.10: pypy3
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue