Import Upstream version 1.2.0
This commit is contained in:
parent
41e98eced5
commit
184cb2dc60
|
@ -0,0 +1,54 @@
|
||||||
|
name: Bug Report
|
||||||
|
description: File a bug report
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: >
|
||||||
|
If you observed a crash in the library, or saw unexpected behavior in it, report
|
||||||
|
your findings here.
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Things to check first
|
||||||
|
options:
|
||||||
|
- label: >
|
||||||
|
I have searched the existing issues and didn't find my bug already reported
|
||||||
|
there
|
||||||
|
required: true
|
||||||
|
- label: >
|
||||||
|
I have checked that my bug is still present in the latest release
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: exceptiongroup-version
|
||||||
|
attributes:
|
||||||
|
label: Exceptiongroup version
|
||||||
|
description: What version of exceptiongroup were you running?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: python-version
|
||||||
|
attributes:
|
||||||
|
label: Python version
|
||||||
|
description: What version of Python were you running?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: >
|
||||||
|
Unless you are reporting a crash, tell us what you expected to happen instead.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: mwe
|
||||||
|
attributes:
|
||||||
|
label: How can we reproduce the bug?
|
||||||
|
description: >
|
||||||
|
In order to investigate the bug, we need to be able to reproduce it on our own.
|
||||||
|
Please create a
|
||||||
|
[minimum workable example](https://stackoverflow.com/help/minimal-reproducible-example)
|
||||||
|
that demonstrates the problem. List any third party libraries required for this,
|
||||||
|
but avoid using them unless absolutely necessary.
|
||||||
|
validations:
|
||||||
|
required: true
|
|
@ -0,0 +1 @@
|
||||||
|
blank_issues_enabled: false
|
|
@ -0,0 +1,35 @@
|
||||||
|
name: Feature request
|
||||||
|
description: Suggest a new feature
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: >
|
||||||
|
If you have thought of a new feature that would increase the usefulness of this
|
||||||
|
project, please use this form to send us your idea.
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Things to check first
|
||||||
|
options:
|
||||||
|
- label: >
|
||||||
|
I have searched the existing issues and didn't find my feature already
|
||||||
|
requested there
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: feature
|
||||||
|
attributes:
|
||||||
|
label: Feature description
|
||||||
|
description: >
|
||||||
|
Describe the feature in detail. The more specific the description you can give,
|
||||||
|
the easier it should be to implement this feature.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: usecase
|
||||||
|
attributes:
|
||||||
|
label: Use case
|
||||||
|
description: >
|
||||||
|
Explain why you need this feature, and why you think it would be useful to
|
||||||
|
others too.
|
||||||
|
validations:
|
||||||
|
required: true
|
|
@ -9,10 +9,12 @@ on:
|
||||||
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
build:
|
||||||
|
name: Build the source tarball and the wheel
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
environment: release
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
|
@ -20,8 +22,38 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install build
|
run: pip install build
|
||||||
- name: Create packages
|
- name: Create packages
|
||||||
run: python -m build -s -w .
|
run: python -m build
|
||||||
|
- name: Archive packages
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
publish:
|
||||||
|
name: Publish build artifacts to the PyPI
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: release
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
steps:
|
||||||
|
- name: Retrieve packages
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
- name: Upload packages
|
- name: Upload packages
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Create a GitHub release
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- id: changelog
|
||||||
|
uses: agronholm/release-notes@v1
|
||||||
with:
|
with:
|
||||||
password: ${{ secrets.pypi_token }}
|
path: CHANGES.rst
|
||||||
|
- uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
body: ${{ steps.changelog.outputs.changelog }}
|
||||||
|
|
|
@ -9,7 +9,7 @@ jobs:
|
||||||
pyright:
|
pyright:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
|
@ -27,38 +27,32 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", pypy-3.8]
|
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", pypy-3.10]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- uses: actions/cache@v3
|
allow-prereleases: true
|
||||||
with:
|
cache: pip
|
||||||
path: ~/.cache/pip
|
cache-dependency-path: pyproject.toml
|
||||||
key: pip-test-${{ matrix.python-version }}-${{ matrix.os }}
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pip install .[test] coveralls coverage[toml]
|
run: pip install -e .[test] coverage
|
||||||
- name: Test with pytest
|
- name: Test with pytest
|
||||||
run: coverage run -m pytest
|
run: coverage run -m pytest
|
||||||
- name: Upload Coverage
|
- name: Upload Coverage
|
||||||
run: coveralls --service=github
|
uses: coverallsapp/github-action@v2
|
||||||
env:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
parallel: true
|
||||||
COVERALLS_FLAG_NAME: ${{ matrix.test-name }}
|
|
||||||
COVERALLS_PARALLEL: true
|
|
||||||
|
|
||||||
coveralls:
|
coveralls:
|
||||||
name: Finish Coveralls
|
name: Finish Coveralls
|
||||||
needs: test
|
needs: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: python:3-slim
|
|
||||||
steps:
|
steps:
|
||||||
- name: Finished
|
- name: Finished
|
||||||
run: |
|
uses: coverallsapp/github-action@v2
|
||||||
pip install coveralls
|
with:
|
||||||
coveralls --service=github --finish
|
parallel-finished: true
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ __pycache__
|
||||||
.coverage
|
.coverage
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
.eggs/
|
.eggs/
|
||||||
.tox
|
.tox
|
||||||
.idea
|
.idea
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
|
@ -15,37 +15,10 @@ repos:
|
||||||
args: ["--fix=lf"]
|
args: ["--fix=lf"]
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v5.11.3
|
rev: v0.1.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: ruff
|
||||||
|
args: [--fix, --show-fixes]
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
|
||||||
rev: v3.3.1
|
|
||||||
hooks:
|
|
||||||
- id: pyupgrade
|
|
||||||
args: ["--py37-plus", "--keep-runtime-typing"]
|
|
||||||
|
|
||||||
- repo: https://github.com/psf/black
|
|
||||||
rev: 22.12.0
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
||||||
exclude: "tests/test_catch_py311.py"
|
exclude: "tests/test_catch_py311.py"
|
||||||
|
- id: ruff-format
|
||||||
- repo: https://github.com/csachs/pyproject-flake8
|
|
||||||
rev: v6.0.0.post1
|
|
||||||
hooks:
|
|
||||||
- id: pyproject-flake8
|
|
||||||
additional_dependencies: [flake8-bugbear]
|
|
||||||
exclude: "tests/test_catch_py311.py"
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
|
||||||
rev: v1.9.0
|
|
||||||
hooks:
|
|
||||||
- id: python-check-blanket-noqa
|
|
||||||
- id: python-check-blanket-type-ignore
|
|
||||||
- id: python-no-eval
|
|
||||||
- id: python-use-type-annotations
|
|
||||||
- id: rst-backticks
|
|
||||||
- id: rst-directive-colons
|
|
||||||
- id: rst-inline-touching-normal
|
|
||||||
|
|
36
CHANGES.rst
36
CHANGES.rst
|
@ -3,6 +3,42 @@ Version history
|
||||||
|
|
||||||
This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
|
This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
|
||||||
|
|
||||||
|
**1.2.0**
|
||||||
|
|
||||||
|
- Added special monkeypatching if `Apport <https://github.com/canonical/apport>`_ has
|
||||||
|
overridden ``sys.excepthook`` so it will format exception groups correctly
|
||||||
|
(PR by John Litborn)
|
||||||
|
- Added a backport of ``contextlib.suppress()`` from Python 3.12.1 which also handles
|
||||||
|
suppressing exceptions inside exception groups
|
||||||
|
- Fixed bare ``raise`` in a handler reraising the original naked exception rather than
|
||||||
|
an exception group which is what is raised when you do a ``raise`` in an ``except*``
|
||||||
|
handler
|
||||||
|
|
||||||
|
**1.1.3**
|
||||||
|
|
||||||
|
- ``catch()`` now raises a ``TypeError`` if passed an async exception handler instead of
|
||||||
|
just giving a ``RuntimeWarning`` about the coroutine never being awaited. (#66, PR by
|
||||||
|
John Litborn)
|
||||||
|
- Fixed plain ``raise`` statement in an exception handler callback to work like a
|
||||||
|
``raise`` in an ``except*`` block
|
||||||
|
- Fixed new exception group not being chained to the original exception when raising an
|
||||||
|
exception group from exceptions raised in handler callbacks
|
||||||
|
- Fixed type annotations of the ``derive()``, ``subgroup()`` and ``split()`` methods to
|
||||||
|
match the ones in typeshed
|
||||||
|
|
||||||
|
**1.1.2**
|
||||||
|
|
||||||
|
- Changed handling of exceptions in exception group handler callbacks to not wrap a
|
||||||
|
single exception in an exception group, as per
|
||||||
|
`CPython issue 103590 <https://github.com/python/cpython/issues/103590>`_
|
||||||
|
|
||||||
|
**1.1.1**
|
||||||
|
|
||||||
|
- Worked around
|
||||||
|
`CPython issue #98778 <https://github.com/python/cpython/issues/98778>`_,
|
||||||
|
``urllib.error.HTTPError(..., fp=None)`` raises ``KeyError`` on unknown attribute
|
||||||
|
access, on affected Python versions. (PR by Zac Hatfield-Dodds)
|
||||||
|
|
||||||
**1.1.0**
|
**1.1.0**
|
||||||
|
|
||||||
- Backported upstream fix for gh-99553 (custom subclasses of ``BaseExceptionGroup`` that
|
- Backported upstream fix for gh-99553 (custom subclasses of ``BaseExceptionGroup`` that
|
||||||
|
|
14
README.rst
14
README.rst
|
@ -26,6 +26,8 @@ It contains the following:
|
||||||
* ``traceback.format_exception_only()``
|
* ``traceback.format_exception_only()``
|
||||||
* ``traceback.print_exception()``
|
* ``traceback.print_exception()``
|
||||||
* ``traceback.print_exc()``
|
* ``traceback.print_exc()``
|
||||||
|
* A backported version of ``contextlib.suppress()`` from Python 3.12.1 which also
|
||||||
|
handles suppressing exceptions inside exception groups
|
||||||
|
|
||||||
If this package is imported on Python 3.11 or later, the built-in implementations of the
|
If this package is imported on Python 3.11 or later, the built-in implementations of the
|
||||||
exception group classes are used instead, ``TracebackException`` is not monkey patched
|
exception group classes are used instead, ``TracebackException`` is not monkey patched
|
||||||
|
@ -84,6 +86,18 @@ would be written with this backport like this:
|
||||||
**NOTE**: Just like with ``except*``, you cannot handle ``BaseExceptionGroup`` or
|
**NOTE**: Just like with ``except*``, you cannot handle ``BaseExceptionGroup`` or
|
||||||
``ExceptionGroup`` with ``catch()``.
|
``ExceptionGroup`` with ``catch()``.
|
||||||
|
|
||||||
|
Suppressing exceptions
|
||||||
|
======================
|
||||||
|
|
||||||
|
This library contains a backport of the ``contextlib.suppress()`` context manager from
|
||||||
|
Python 3.12.1. It allows you to selectively ignore certain exceptions, even when they're
|
||||||
|
inside exception groups::
|
||||||
|
|
||||||
|
from exceptiongroup import suppress
|
||||||
|
|
||||||
|
with suppress(RuntimeError):
|
||||||
|
raise ExceptionGroup("", [RuntimeError("boo")])
|
||||||
|
|
||||||
Notes on monkey patching
|
Notes on monkey patching
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
python-exceptiongroup (1.1.0-ok1) yangtze; urgency=medium
|
|
||||||
|
|
||||||
* Build for openkylin.
|
|
||||||
|
|
||||||
-- sufang <sufang@kylinos.cn> Tue, 07 Feb 2023 11:24:32 +0800
|
|
|
@ -1 +0,0 @@
|
||||||
src/exceptiongroup/_version.py
|
|
|
@ -1,29 +0,0 @@
|
||||||
Source: python-exceptiongroup
|
|
||||||
Section: python
|
|
||||||
Priority: optional
|
|
||||||
Maintainer: OpenKylin Developers <packaging@lists.openkylin.top>
|
|
||||||
Build-Depends:
|
|
||||||
debhelper-compat (= 13),
|
|
||||||
dh-python,
|
|
||||||
dh-sequence-python3,
|
|
||||||
pybuild-plugin-pyproject,
|
|
||||||
python3-all,
|
|
||||||
python3-flit-scm,
|
|
||||||
python3-pytest <!nocheck>,
|
|
||||||
Standards-Version: 4.6.1
|
|
||||||
Testsuite: autopkgtest-pkg-pybuild
|
|
||||||
Vcs-Git: https://gitee.com/openkylin/python-exceptiongroup.git
|
|
||||||
Vcs-Browser: https://gitee.com/openkylin/python-exceptiongroup
|
|
||||||
Homepage: https://github.com/agronholm/exceptiongroup/
|
|
||||||
Rules-Requires-Root: no
|
|
||||||
|
|
||||||
Package: python3-exceptiongroup
|
|
||||||
Architecture: all
|
|
||||||
Depends:
|
|
||||||
${misc:Depends},
|
|
||||||
${python3:Depends},
|
|
||||||
Breaks:
|
|
||||||
python3-trio (<< 0.22),
|
|
||||||
Description: Backport of PEP 654 (exception groups)
|
|
||||||
This is a backport of the BaseExceptionGroup and ExceptionGroup classes from
|
|
||||||
Python 3.11.
|
|
|
@ -1,106 +0,0 @@
|
||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
||||||
Upstream-Name: python-exceptiongroup
|
|
||||||
Source: <url://example.com>
|
|
||||||
#
|
|
||||||
# Please double check copyright with the licensecheck(1) command.
|
|
||||||
|
|
||||||
Files: .github/workflows/publish.yml
|
|
||||||
.github/workflows/test.yml
|
|
||||||
.gitignore
|
|
||||||
.pre-commit-config.yaml
|
|
||||||
CHANGES.rst
|
|
||||||
README.rst
|
|
||||||
pyproject.toml
|
|
||||||
src/exceptiongroup/__init__.py
|
|
||||||
src/exceptiongroup/_catch.py
|
|
||||||
src/exceptiongroup/_exceptions.py
|
|
||||||
src/exceptiongroup/_formatting.py
|
|
||||||
src/exceptiongroup/py.typed
|
|
||||||
tests/__init__.py
|
|
||||||
tests/conftest.py
|
|
||||||
tests/test_catch.py
|
|
||||||
tests/test_catch_py311.py
|
|
||||||
tests/test_exceptions.py
|
|
||||||
tests/test_formatting.py
|
|
||||||
Copyright: __NO_COPYRIGHT_NOR_LICENSE__
|
|
||||||
License: __NO_COPYRIGHT_NOR_LICENSE__
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------------
|
|
||||||
# Files marked as NO_LICENSE_TEXT_FOUND may be covered by the following
|
|
||||||
# license/copyright files.
|
|
||||||
|
|
||||||
#----------------------------------------------------------------------------
|
|
||||||
# License file: LICENSE
|
|
||||||
The MIT License (MIT)
|
|
||||||
.
|
|
||||||
Copyright (c) 2022 Alex Grönholm
|
|
||||||
.
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
||||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
subject to the following conditions:
|
|
||||||
.
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
.
|
|
||||||
.
|
|
||||||
This project contains code copied from the Python standard library.
|
|
||||||
The following is the required license notice for those parts.
|
|
||||||
.
|
|
||||||
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
|
||||||
--------------------------------------------
|
|
||||||
.
|
|
||||||
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
|
||||||
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
|
||||||
otherwise using this software ("Python") in source or binary form and
|
|
||||||
its associated documentation.
|
|
||||||
.
|
|
||||||
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
|
||||||
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
|
||||||
analyze, test, perform and/or display publicly, prepare derivative works,
|
|
||||||
distribute, and otherwise use Python alone or in any derivative version,
|
|
||||||
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
|
||||||
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
|
||||||
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Python Software Foundation;
|
|
||||||
All Rights Reserved" are retained in Python alone or in any derivative version
|
|
||||||
prepared by Licensee.
|
|
||||||
.
|
|
||||||
3. In the event Licensee prepares a derivative work that is based on
|
|
||||||
or incorporates Python or any part thereof, and wants to make
|
|
||||||
the derivative work available to others as provided herein, then
|
|
||||||
Licensee hereby agrees to include in any such work a brief summary of
|
|
||||||
the changes made to Python.
|
|
||||||
.
|
|
||||||
4. PSF is making Python available to Licensee on an "AS IS"
|
|
||||||
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
|
||||||
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
|
||||||
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
|
||||||
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
|
||||||
INFRINGE ANY THIRD PARTY RIGHTS.
|
|
||||||
.
|
|
||||||
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
|
||||||
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
|
||||||
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
|
||||||
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
|
||||||
.
|
|
||||||
6. This License Agreement will automatically terminate upon a material
|
|
||||||
breach of its terms and conditions.
|
|
||||||
.
|
|
||||||
7. Nothing in this License Agreement shall be deemed to create any
|
|
||||||
relationship of agency, partnership, or joint venture between PSF and
|
|
||||||
Licensee. This License Agreement does not grant permission to use PSF
|
|
||||||
trademarks or trade name in a trademark sense to endorse or promote
|
|
||||||
products or services of Licensee, or any third party.
|
|
||||||
.
|
|
||||||
8. By copying, installing or otherwise using Python, Licensee
|
|
||||||
agrees to be bound by the terms and conditions of this License
|
|
||||||
Agreement.
|
|
|
@ -1 +0,0 @@
|
||||||
# You must remove unused comment lines for the released package.
|
|
|
@ -1 +0,0 @@
|
||||||
README.rst
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/bin/make -f
|
|
||||||
|
|
||||||
include /usr/share/dpkg/pkg-info.mk
|
|
||||||
|
|
||||||
export PYBUILD_NAME=exceptiongroup
|
|
||||||
|
|
||||||
export SETUPTOOLS_SCM_PRETEND_VERSION = $(DEB_VERSION_UPSTREAM)
|
|
||||||
|
|
||||||
%:
|
|
||||||
dh $@ --buildsystem=pybuild
|
|
|
@ -1 +0,0 @@
|
||||||
3.0 (native)
|
|
|
@ -1,4 +0,0 @@
|
||||||
Bug-Database: https://github.com/agronholm/exceptiongroup//issues
|
|
||||||
Bug-Submit: https://github.com/agronholm/exceptiongroup//issues/new
|
|
||||||
Repository: https://github.com/agronholm/exceptiongroup/.git
|
|
||||||
Repository-Browse: https://github.com/agronholm/exceptiongroup/
|
|
|
@ -1,3 +0,0 @@
|
||||||
version=4
|
|
||||||
opts="pgpmode=none" \
|
|
||||||
https://github.com/agronholm/exceptiongroup/tags (?:.*?/)?v?(\d[\d.]*)\.tar\.gz
|
|
|
@ -30,6 +30,9 @@ test = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.flit.sdist]
|
[tool.flit.sdist]
|
||||||
|
include = [
|
||||||
|
"tests",
|
||||||
|
]
|
||||||
exclude = [
|
exclude = [
|
||||||
".github/*",
|
".github/*",
|
||||||
".gitignore",
|
".gitignore",
|
||||||
|
@ -41,16 +44,22 @@ version_scheme = "post-release"
|
||||||
local_scheme = "dirty-tag"
|
local_scheme = "dirty-tag"
|
||||||
write_to = "src/exceptiongroup/_version.py"
|
write_to = "src/exceptiongroup/_version.py"
|
||||||
|
|
||||||
[tool.black]
|
[tool.ruff]
|
||||||
target-version = ['py37']
|
select = [
|
||||||
|
"E", "F", "W", # default flake-8
|
||||||
|
"I", # isort
|
||||||
|
"ISC", # flake8-implicit-str-concat
|
||||||
|
"PGH", # pygrep-hooks
|
||||||
|
"RUF100", # unused noqa (yesqa)
|
||||||
|
"UP", # pyupgrade
|
||||||
|
]
|
||||||
|
|
||||||
[tool.isort]
|
[tool.ruff.pyupgrade]
|
||||||
src_paths = ["src"]
|
# Preserve types, even if a file imports `from __future__ import annotations`.
|
||||||
skip_gitignore = true
|
keep-runtime-typing = true
|
||||||
profile = "black"
|
|
||||||
|
|
||||||
[tool.flake8]
|
[tool.ruff.isort]
|
||||||
max-line-length = 88
|
known-first-party = ["exceptiongroup"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "-rsx --tb=short --strict-config --strict-markers"
|
addopts = "-rsx --tb=short --strict-config --strict-markers"
|
||||||
|
@ -69,13 +78,14 @@ exclude_lines = [
|
||||||
[tool.tox]
|
[tool.tox]
|
||||||
legacy_tox_ini = """
|
legacy_tox_ini = """
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py37, py38, py39, py310, py311, pypy3
|
envlist = py37, py38, py39, py310, py311, py312, pypy3
|
||||||
skip_missing_interpreters = true
|
skip_missing_interpreters = true
|
||||||
minversion = 4.0
|
minversion = 4.0
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
extras = test
|
extras = test
|
||||||
commands = python -m pytest {posargs}
|
commands = python -m pytest {posargs}
|
||||||
|
usedevelop = true
|
||||||
|
|
||||||
[testenv:pyright]
|
[testenv:pyright]
|
||||||
deps = pyright
|
deps = pyright
|
||||||
|
|
|
@ -6,6 +6,7 @@ __all__ = [
|
||||||
"format_exception_only",
|
"format_exception_only",
|
||||||
"print_exception",
|
"print_exception",
|
||||||
"print_exc",
|
"print_exc",
|
||||||
|
"suppress",
|
||||||
]
|
]
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -38,3 +39,8 @@ else:
|
||||||
|
|
||||||
BaseExceptionGroup = BaseExceptionGroup
|
BaseExceptionGroup = BaseExceptionGroup
|
||||||
ExceptionGroup = ExceptionGroup
|
ExceptionGroup = ExceptionGroup
|
||||||
|
|
||||||
|
if sys.version_info < (3, 12, 1):
|
||||||
|
from ._suppress import suppress
|
||||||
|
else:
|
||||||
|
from contextlib import suppress
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Callable, Iterable, Mapping
|
from collections.abc import Callable, Iterable, Mapping
|
||||||
from contextlib import AbstractContextManager
|
from contextlib import AbstractContextManager
|
||||||
|
@ -33,11 +34,20 @@ class _Catcher:
|
||||||
elif unhandled is None:
|
elif unhandled is None:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
raise unhandled from None
|
if isinstance(exc, BaseExceptionGroup):
|
||||||
|
try:
|
||||||
|
raise unhandled from exc.__cause__
|
||||||
|
except BaseExceptionGroup:
|
||||||
|
# Change __context__ to __cause__ because Python 3.11 does this
|
||||||
|
# too
|
||||||
|
unhandled.__context__ = exc.__cause__
|
||||||
|
raise
|
||||||
|
|
||||||
|
raise unhandled from exc
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def handle_exception(self, exc: BaseException) -> BaseExceptionGroup | None:
|
def handle_exception(self, exc: BaseException) -> BaseException | None:
|
||||||
excgroup: BaseExceptionGroup | None
|
excgroup: BaseExceptionGroup | None
|
||||||
if isinstance(exc, BaseExceptionGroup):
|
if isinstance(exc, BaseExceptionGroup):
|
||||||
excgroup = exc
|
excgroup = exc
|
||||||
|
@ -49,16 +59,30 @@ class _Catcher:
|
||||||
matched, excgroup = excgroup.split(exc_types)
|
matched, excgroup = excgroup.split(exc_types)
|
||||||
if matched:
|
if matched:
|
||||||
try:
|
try:
|
||||||
handler(matched)
|
try:
|
||||||
|
raise matched
|
||||||
|
except BaseExceptionGroup:
|
||||||
|
result = handler(matched)
|
||||||
|
except BaseExceptionGroup as new_exc:
|
||||||
|
if new_exc is matched:
|
||||||
|
new_exceptions.append(new_exc)
|
||||||
|
else:
|
||||||
|
new_exceptions.extend(new_exc.exceptions)
|
||||||
except BaseException as new_exc:
|
except BaseException as new_exc:
|
||||||
new_exceptions.append(new_exc)
|
new_exceptions.append(new_exc)
|
||||||
|
else:
|
||||||
|
if inspect.iscoroutine(result):
|
||||||
|
raise TypeError(
|
||||||
|
f"Error trying to handle {matched!r} with {handler!r}. "
|
||||||
|
"Exception handler must be a sync function."
|
||||||
|
) from exc
|
||||||
|
|
||||||
if not excgroup:
|
if not excgroup:
|
||||||
break
|
break
|
||||||
|
|
||||||
if new_exceptions:
|
if new_exceptions:
|
||||||
if excgroup:
|
if len(new_exceptions) == 1:
|
||||||
new_exceptions.append(excgroup)
|
return new_exceptions[0]
|
||||||
|
|
||||||
return BaseExceptionGroup("", new_exceptions)
|
return BaseExceptionGroup("", new_exceptions)
|
||||||
elif (
|
elif (
|
||||||
|
|
|
@ -27,7 +27,7 @@ def check_direct_subclass(
|
||||||
def get_condition_filter(
|
def get_condition_filter(
|
||||||
condition: type[_BaseExceptionT]
|
condition: type[_BaseExceptionT]
|
||||||
| tuple[type[_BaseExceptionT], ...]
|
| tuple[type[_BaseExceptionT], ...]
|
||||||
| Callable[[_BaseExceptionT_co], bool]
|
| Callable[[_BaseExceptionT_co], bool],
|
||||||
) -> Callable[[_BaseExceptionT_co], bool]:
|
) -> Callable[[_BaseExceptionT_co], bool]:
|
||||||
if isclass(condition) and issubclass(
|
if isclass(condition) and issubclass(
|
||||||
cast(Type[BaseException], condition), BaseException
|
cast(Type[BaseException], condition), BaseException
|
||||||
|
@ -60,7 +60,7 @@ class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]):
|
||||||
for i, exc in enumerate(__exceptions):
|
for i, exc in enumerate(__exceptions):
|
||||||
if not isinstance(exc, BaseException):
|
if not isinstance(exc, BaseException):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Item {i} of second argument (exceptions) is not an " f"exception"
|
f"Item {i} of second argument (exceptions) is not an exception"
|
||||||
)
|
)
|
||||||
|
|
||||||
if cls is BaseExceptionGroup:
|
if cls is BaseExceptionGroup:
|
||||||
|
@ -105,6 +105,12 @@ class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]):
|
||||||
) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]:
|
) -> tuple[_BaseExceptionT_co | BaseExceptionGroup[_BaseExceptionT_co], ...]:
|
||||||
return tuple(self._exceptions)
|
return tuple(self._exceptions)
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def subgroup(
|
||||||
|
self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
|
||||||
|
) -> ExceptionGroup[_ExceptionT] | None:
|
||||||
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def subgroup(
|
def subgroup(
|
||||||
self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...]
|
self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...]
|
||||||
|
@ -113,16 +119,16 @@ class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]):
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def subgroup(
|
def subgroup(
|
||||||
self: Self, __condition: Callable[[_BaseExceptionT_co], bool]
|
self, __condition: Callable[[_BaseExceptionT_co | Self], bool]
|
||||||
) -> Self | None:
|
) -> BaseExceptionGroup[_BaseExceptionT_co] | None:
|
||||||
...
|
...
|
||||||
|
|
||||||
def subgroup(
|
def subgroup(
|
||||||
self: Self,
|
self,
|
||||||
__condition: type[_BaseExceptionT]
|
__condition: type[_BaseExceptionT]
|
||||||
| tuple[type[_BaseExceptionT], ...]
|
| tuple[type[_BaseExceptionT], ...]
|
||||||
| Callable[[_BaseExceptionT_co], bool],
|
| Callable[[_BaseExceptionT_co | Self], bool],
|
||||||
) -> BaseExceptionGroup[_BaseExceptionT] | Self | None:
|
) -> BaseExceptionGroup[_BaseExceptionT] | None:
|
||||||
condition = get_condition_filter(__condition)
|
condition = get_condition_filter(__condition)
|
||||||
modified = False
|
modified = False
|
||||||
if condition(self):
|
if condition(self):
|
||||||
|
@ -155,25 +161,50 @@ class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]):
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def split(
|
def split(
|
||||||
self: Self,
|
self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
|
||||||
__condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...],
|
) -> tuple[
|
||||||
) -> tuple[BaseExceptionGroup[_BaseExceptionT] | None, Self | None]:
|
ExceptionGroup[_ExceptionT] | None,
|
||||||
|
BaseExceptionGroup[_BaseExceptionT_co] | None,
|
||||||
|
]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def split(
|
def split(
|
||||||
self: Self, __condition: Callable[[_BaseExceptionT_co], bool]
|
self, __condition: type[_BaseExceptionT] | tuple[type[_BaseExceptionT], ...]
|
||||||
) -> tuple[Self | None, Self | None]:
|
) -> tuple[
|
||||||
|
BaseExceptionGroup[_BaseExceptionT] | None,
|
||||||
|
BaseExceptionGroup[_BaseExceptionT_co] | None,
|
||||||
|
]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def split(
|
||||||
|
self, __condition: Callable[[_BaseExceptionT_co | Self], bool]
|
||||||
|
) -> tuple[
|
||||||
|
BaseExceptionGroup[_BaseExceptionT_co] | None,
|
||||||
|
BaseExceptionGroup[_BaseExceptionT_co] | None,
|
||||||
|
]:
|
||||||
...
|
...
|
||||||
|
|
||||||
def split(
|
def split(
|
||||||
self: Self,
|
self,
|
||||||
__condition: type[_BaseExceptionT]
|
__condition: type[_BaseExceptionT]
|
||||||
| tuple[type[_BaseExceptionT], ...]
|
| tuple[type[_BaseExceptionT], ...]
|
||||||
| Callable[[_BaseExceptionT_co], bool],
|
| Callable[[_BaseExceptionT_co], bool],
|
||||||
) -> tuple[BaseExceptionGroup[_BaseExceptionT] | None, Self | None] | tuple[
|
) -> (
|
||||||
Self | None, Self | None
|
tuple[
|
||||||
]:
|
ExceptionGroup[_ExceptionT] | None,
|
||||||
|
BaseExceptionGroup[_BaseExceptionT_co] | None,
|
||||||
|
]
|
||||||
|
| tuple[
|
||||||
|
BaseExceptionGroup[_BaseExceptionT] | None,
|
||||||
|
BaseExceptionGroup[_BaseExceptionT_co] | None,
|
||||||
|
]
|
||||||
|
| tuple[
|
||||||
|
BaseExceptionGroup[_BaseExceptionT_co] | None,
|
||||||
|
BaseExceptionGroup[_BaseExceptionT_co] | None,
|
||||||
|
]
|
||||||
|
):
|
||||||
condition = get_condition_filter(__condition)
|
condition = get_condition_filter(__condition)
|
||||||
if condition(self):
|
if condition(self):
|
||||||
return self, None
|
return self, None
|
||||||
|
@ -209,7 +240,19 @@ class BaseExceptionGroup(BaseException, Generic[_BaseExceptionT_co]):
|
||||||
|
|
||||||
return matching_group, nonmatching_group
|
return matching_group, nonmatching_group
|
||||||
|
|
||||||
def derive(self: Self, __excs: Sequence[_BaseExceptionT_co]) -> Self:
|
@overload
|
||||||
|
def derive(self, __excs: Sequence[_ExceptionT]) -> ExceptionGroup[_ExceptionT]:
|
||||||
|
...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def derive(
|
||||||
|
self, __excs: Sequence[_BaseExceptionT]
|
||||||
|
) -> BaseExceptionGroup[_BaseExceptionT]:
|
||||||
|
...
|
||||||
|
|
||||||
|
def derive(
|
||||||
|
self, __excs: Sequence[_BaseExceptionT]
|
||||||
|
) -> BaseExceptionGroup[_BaseExceptionT]:
|
||||||
eg = BaseExceptionGroup(self.message, __excs)
|
eg = BaseExceptionGroup(self.message, __excs)
|
||||||
if hasattr(self, "__notes__"):
|
if hasattr(self, "__notes__"):
|
||||||
# Create a new list so that add_note() only affects one exceptiongroup
|
# Create a new list so that add_note() only affects one exceptiongroup
|
||||||
|
@ -245,28 +288,32 @@ class ExceptionGroup(BaseExceptionGroup[_ExceptionT_co], Exception):
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def subgroup(
|
def subgroup(
|
||||||
self: Self, __condition: Callable[[_ExceptionT_co], bool]
|
self, __condition: Callable[[_ExceptionT_co | Self], bool]
|
||||||
) -> Self | None:
|
) -> ExceptionGroup[_ExceptionT_co] | None:
|
||||||
...
|
...
|
||||||
|
|
||||||
def subgroup(
|
def subgroup(
|
||||||
self: Self,
|
self,
|
||||||
__condition: type[_ExceptionT]
|
__condition: type[_ExceptionT]
|
||||||
| tuple[type[_ExceptionT], ...]
|
| tuple[type[_ExceptionT], ...]
|
||||||
| Callable[[_ExceptionT_co], bool],
|
| Callable[[_ExceptionT_co], bool],
|
||||||
) -> ExceptionGroup[_ExceptionT] | Self | None:
|
) -> ExceptionGroup[_ExceptionT] | None:
|
||||||
return super().subgroup(__condition)
|
return super().subgroup(__condition)
|
||||||
|
|
||||||
@overload # type: ignore[override]
|
@overload
|
||||||
def split(
|
def split(
|
||||||
self: Self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
|
self, __condition: type[_ExceptionT] | tuple[type[_ExceptionT], ...]
|
||||||
) -> tuple[ExceptionGroup[_ExceptionT] | None, Self | None]:
|
) -> tuple[
|
||||||
|
ExceptionGroup[_ExceptionT] | None, ExceptionGroup[_ExceptionT_co] | None
|
||||||
|
]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def split(
|
def split(
|
||||||
self: Self, __condition: Callable[[_ExceptionT_co], bool]
|
self, __condition: Callable[[_ExceptionT_co | Self], bool]
|
||||||
) -> tuple[Self | None, Self | None]:
|
) -> tuple[
|
||||||
|
ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None
|
||||||
|
]:
|
||||||
...
|
...
|
||||||
|
|
||||||
def split(
|
def split(
|
||||||
|
@ -274,7 +321,7 @@ class ExceptionGroup(BaseExceptionGroup[_ExceptionT_co], Exception):
|
||||||
__condition: type[_ExceptionT]
|
__condition: type[_ExceptionT]
|
||||||
| tuple[type[_ExceptionT], ...]
|
| tuple[type[_ExceptionT], ...]
|
||||||
| Callable[[_ExceptionT_co], bool],
|
| Callable[[_ExceptionT_co], bool],
|
||||||
) -> tuple[ExceptionGroup[_ExceptionT] | None, Self | None] | tuple[
|
) -> tuple[
|
||||||
Self | None, Self | None
|
ExceptionGroup[_ExceptionT_co] | None, ExceptionGroup[_ExceptionT_co] | None
|
||||||
]:
|
]:
|
||||||
return super().split(__condition)
|
return super().split(__condition)
|
||||||
|
|
|
@ -103,7 +103,16 @@ class PatchedTracebackException(traceback.TracebackException):
|
||||||
# Capture now to permit freeing resources: only complication is in the
|
# Capture now to permit freeing resources: only complication is in the
|
||||||
# unofficial API _format_final_exc_line
|
# unofficial API _format_final_exc_line
|
||||||
self._str = _safe_string(exc_value, "exception")
|
self._str = _safe_string(exc_value, "exception")
|
||||||
|
try:
|
||||||
self.__notes__ = getattr(exc_value, "__notes__", None)
|
self.__notes__ = getattr(exc_value, "__notes__", None)
|
||||||
|
except KeyError:
|
||||||
|
# Workaround for https://github.com/python/cpython/issues/98778 on Python
|
||||||
|
# <= 3.9, and some 3.10 and 3.11 patch versions.
|
||||||
|
HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ())
|
||||||
|
if sys.version_info[:2] <= (3, 11) and isinstance(exc_value, HTTPError):
|
||||||
|
self.__notes__ = None
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
if exc_type and issubclass(exc_type, SyntaxError):
|
if exc_type and issubclass(exc_type, SyntaxError):
|
||||||
# Handle SyntaxError's specially
|
# Handle SyntaxError's specially
|
||||||
|
@ -350,6 +359,46 @@ if sys.excepthook is sys.__excepthook__:
|
||||||
)
|
)
|
||||||
sys.excepthook = exceptiongroup_excepthook
|
sys.excepthook = exceptiongroup_excepthook
|
||||||
|
|
||||||
|
# Ubuntu's system Python has a sitecustomize.py file that imports
|
||||||
|
# apport_python_hook and replaces sys.excepthook.
|
||||||
|
#
|
||||||
|
# The custom hook captures the error for crash reporting, and then calls
|
||||||
|
# sys.__excepthook__ to actually print the error.
|
||||||
|
#
|
||||||
|
# We don't mind it capturing the error for crash reporting, but we want to
|
||||||
|
# take over printing the error. So we monkeypatch the apport_python_hook
|
||||||
|
# module so that instead of calling sys.__excepthook__, it calls our custom
|
||||||
|
# hook.
|
||||||
|
#
|
||||||
|
# More details: https://github.com/python-trio/trio/issues/1065
|
||||||
|
if getattr(sys.excepthook, "__name__", None) in (
|
||||||
|
"apport_excepthook",
|
||||||
|
# on ubuntu 22.10 the hook was renamed to partial_apport_excepthook
|
||||||
|
"partial_apport_excepthook",
|
||||||
|
):
|
||||||
|
# patch traceback like above
|
||||||
|
traceback.TracebackException.__init__ = ( # type: ignore[assignment]
|
||||||
|
PatchedTracebackException.__init__
|
||||||
|
)
|
||||||
|
traceback.TracebackException.format = ( # type: ignore[assignment]
|
||||||
|
PatchedTracebackException.format
|
||||||
|
)
|
||||||
|
traceback.TracebackException.format_exception_only = ( # type: ignore[assignment]
|
||||||
|
PatchedTracebackException.format_exception_only
|
||||||
|
)
|
||||||
|
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
import apport_python_hook
|
||||||
|
|
||||||
|
assert sys.excepthook is apport_python_hook.apport_excepthook
|
||||||
|
|
||||||
|
# monkeypatch the sys module that apport has imported
|
||||||
|
fake_sys = ModuleType("exceptiongroup_fake_sys")
|
||||||
|
fake_sys.__dict__.update(sys.__dict__)
|
||||||
|
fake_sys.__excepthook__ = exceptiongroup_excepthook
|
||||||
|
apport_python_hook.sys = fake_sys
|
||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def format_exception_only(__exc: BaseException) -> List[str]:
|
def format_exception_only(__exc: BaseException) -> List[str]:
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import sys
|
||||||
|
from contextlib import AbstractContextManager
|
||||||
|
|
||||||
|
if sys.version_info < (3, 11):
|
||||||
|
from ._exceptions import BaseExceptionGroup
|
||||||
|
|
||||||
|
|
||||||
|
class suppress(AbstractContextManager):
|
||||||
|
"""Backport of :class:`contextlib.suppress` from Python 3.12.1."""
|
||||||
|
|
||||||
|
def __init__(self, *exceptions):
|
||||||
|
self._exceptions = exceptions
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __exit__(self, exctype, excinst, exctb):
|
||||||
|
# Unlike isinstance and issubclass, CPython exception handling
|
||||||
|
# currently only looks at the concrete type hierarchy (ignoring
|
||||||
|
# the instance and subclass checking hooks). While Guido considers
|
||||||
|
# that a bug rather than a feature, it's a fairly hard one to fix
|
||||||
|
# due to various internal implementation details. suppress provides
|
||||||
|
# the simpler issubclass based semantics, rather than trying to
|
||||||
|
# exactly reproduce the limitations of the CPython interpreter.
|
||||||
|
#
|
||||||
|
# See http://bugs.python.org/issue12029 for more details
|
||||||
|
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
|
|
@ -0,0 +1,13 @@
|
||||||
|
# The apport_python_hook package is only installed as part of Ubuntu's system
|
||||||
|
# python, and not available in venvs. So before we can import it we have to
|
||||||
|
# make sure it's on sys.path.
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append("/usr/lib/python3/dist-packages")
|
||||||
|
import apport_python_hook # noqa: E402 # unsorted import
|
||||||
|
|
||||||
|
apport_python_hook.install()
|
||||||
|
|
||||||
|
from exceptiongroup import ExceptionGroup # noqa: E402 # unsorted import
|
||||||
|
|
||||||
|
raise ExceptionGroup("msg1", [KeyError("msg2"), ValueError("msg3")])
|
|
@ -0,0 +1,63 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import exceptiongroup
|
||||||
|
|
||||||
|
|
||||||
|
def run_script(name: str) -> subprocess.CompletedProcess[bytes]:
|
||||||
|
exceptiongroup_path = Path(exceptiongroup.__file__).parent.parent
|
||||||
|
script_path = Path(__file__).parent / name
|
||||||
|
|
||||||
|
env = dict(os.environ)
|
||||||
|
print("parent PYTHONPATH:", env.get("PYTHONPATH"))
|
||||||
|
if "PYTHONPATH" in env: # pragma: no cover
|
||||||
|
pp = env["PYTHONPATH"].split(os.pathsep)
|
||||||
|
else:
|
||||||
|
pp = []
|
||||||
|
|
||||||
|
pp.insert(0, str(exceptiongroup_path))
|
||||||
|
pp.insert(0, str(script_path.parent))
|
||||||
|
env["PYTHONPATH"] = os.pathsep.join(pp)
|
||||||
|
print("subprocess PYTHONPATH:", env.get("PYTHONPATH"))
|
||||||
|
|
||||||
|
cmd = [sys.executable, "-u", str(script_path)]
|
||||||
|
print("running:", cmd)
|
||||||
|
completed = subprocess.run(
|
||||||
|
cmd, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
|
print("process output:")
|
||||||
|
print(completed.stdout.decode("utf-8"))
|
||||||
|
return completed
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info > (3, 11),
|
||||||
|
reason="No patching is done on Python >= 3.11",
|
||||||
|
)
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not Path("/usr/lib/python3/dist-packages/apport_python_hook.py").exists(),
|
||||||
|
reason="need Ubuntu with python3-apport installed",
|
||||||
|
)
|
||||||
|
def test_apport_excepthook_monkeypatch_interaction():
|
||||||
|
completed = run_script("apport_excepthook.py")
|
||||||
|
stdout = completed.stdout.decode("utf-8")
|
||||||
|
file = Path(__file__).parent / "apport_excepthook.py"
|
||||||
|
assert stdout == (
|
||||||
|
f"""\
|
||||||
|
+ Exception Group Traceback (most recent call last):
|
||||||
|
| File "{file}", line 13, in <module>
|
||||||
|
| raise ExceptionGroup("msg1", [KeyError("msg2"), ValueError("msg3")])
|
||||||
|
| exceptiongroup.ExceptionGroup: msg1 (2 sub-exceptions)
|
||||||
|
+-+---------------- 1 ----------------
|
||||||
|
| KeyError: 'msg2'
|
||||||
|
+---------------- 2 ----------------
|
||||||
|
| ValueError: msg3
|
||||||
|
+------------------------------------
|
||||||
|
"""
|
||||||
|
)
|
|
@ -148,15 +148,41 @@ def test_catch_handler_raises():
|
||||||
def handler(exc):
|
def handler(exc):
|
||||||
raise RuntimeError("new")
|
raise RuntimeError("new")
|
||||||
|
|
||||||
try:
|
with pytest.raises(RuntimeError, match="new") as exc:
|
||||||
with catch({(ValueError, ValueError): handler}):
|
with catch({(ValueError, ValueError): handler}):
|
||||||
raise ExceptionGroup("booboo", [ValueError("bar")])
|
excgrp = ExceptionGroup("booboo", [ValueError("bar")])
|
||||||
except ExceptionGroup as exc:
|
raise excgrp
|
||||||
assert exc.message == ""
|
|
||||||
assert len(exc.exceptions) == 1
|
context = exc.value.__context__
|
||||||
assert isinstance(exc.exceptions[0], RuntimeError)
|
assert isinstance(context, ExceptionGroup)
|
||||||
else:
|
assert str(context) == "booboo (1 sub-exception)"
|
||||||
pytest.fail("Did not raise an ExceptionGroup")
|
assert len(context.exceptions) == 1
|
||||||
|
assert isinstance(context.exceptions[0], ValueError)
|
||||||
|
assert exc.value.__cause__ is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_bare_raise_in_handler():
|
||||||
|
"""Test that a bare "raise" "middle" ecxeption group gets discarded."""
|
||||||
|
|
||||||
|
def handler(exc):
|
||||||
|
raise
|
||||||
|
|
||||||
|
with pytest.raises(ExceptionGroup) as excgrp:
|
||||||
|
with catch({(ValueError,): handler, (RuntimeError,): lambda eg: None}):
|
||||||
|
try:
|
||||||
|
first_exc = RuntimeError("first")
|
||||||
|
raise first_exc
|
||||||
|
except RuntimeError as exc:
|
||||||
|
middle_exc = ExceptionGroup(
|
||||||
|
"bad", [ValueError(), ValueError(), TypeError()]
|
||||||
|
)
|
||||||
|
raise middle_exc from exc
|
||||||
|
|
||||||
|
assert len(excgrp.value.exceptions) == 2
|
||||||
|
assert all(isinstance(exc, ValueError) for exc in excgrp.value.exceptions)
|
||||||
|
assert excgrp.value is not middle_exc
|
||||||
|
assert excgrp.value.__cause__ is first_exc
|
||||||
|
assert excgrp.value.__context__ is first_exc
|
||||||
|
|
||||||
|
|
||||||
def test_catch_subclass():
|
def test_catch_subclass():
|
||||||
|
@ -168,3 +194,29 @@ def test_catch_subclass():
|
||||||
assert isinstance(lookup_errors[0], ExceptionGroup)
|
assert isinstance(lookup_errors[0], ExceptionGroup)
|
||||||
exceptions = lookup_errors[0].exceptions
|
exceptions = lookup_errors[0].exceptions
|
||||||
assert isinstance(exceptions[0], KeyError)
|
assert isinstance(exceptions[0], KeyError)
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_handler(request):
|
||||||
|
async def handler(eg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delegate(eg):
|
||||||
|
coro = handler(eg)
|
||||||
|
request.addfinalizer(coro.close)
|
||||||
|
return coro
|
||||||
|
|
||||||
|
with pytest.raises(TypeError, match="Exception handler must be a sync function."):
|
||||||
|
with catch({TypeError: delegate}):
|
||||||
|
raise ExceptionGroup("message", [TypeError("uh-oh")])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bare_reraise_from_naked_exception():
|
||||||
|
def handler(eg):
|
||||||
|
raise
|
||||||
|
|
||||||
|
with pytest.raises(ExceptionGroup) as excgrp, catch({Exception: handler}):
|
||||||
|
raise KeyError("foo")
|
||||||
|
|
||||||
|
assert len(excgrp.value.exceptions) == 1
|
||||||
|
assert isinstance(excgrp.value.exceptions[0], KeyError)
|
||||||
|
assert str(excgrp.value.exceptions[0]) == "'foo'"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from exceptiongroup import ExceptionGroup
|
from exceptiongroup import ExceptionGroup
|
||||||
|
@ -121,18 +123,24 @@ def test_catch_full_match():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 11, 4),
|
||||||
|
reason="Behavior was changed in 3.11.4",
|
||||||
|
)
|
||||||
def test_catch_handler_raises():
|
def test_catch_handler_raises():
|
||||||
|
with pytest.raises(RuntimeError, match="new") as exc:
|
||||||
try:
|
try:
|
||||||
try:
|
excgrp = ExceptionGroup("booboo", [ValueError("bar")])
|
||||||
raise ExceptionGroup("booboo", [ValueError("bar")])
|
raise excgrp
|
||||||
except* ValueError:
|
except* ValueError:
|
||||||
raise RuntimeError("new")
|
raise RuntimeError("new")
|
||||||
except ExceptionGroup as exc:
|
|
||||||
assert exc.message == ""
|
context = exc.value.__context__
|
||||||
assert len(exc.exceptions) == 1
|
assert isinstance(context, ExceptionGroup)
|
||||||
assert isinstance(exc.exceptions[0], RuntimeError)
|
assert str(context) == "booboo (1 sub-exception)"
|
||||||
else:
|
assert len(context.exceptions) == 1
|
||||||
pytest.fail("Did not raise an ExceptionGroup")
|
assert isinstance(context.exceptions[0], ValueError)
|
||||||
|
assert exc.value.__cause__ is None
|
||||||
|
|
||||||
|
|
||||||
def test_catch_subclass():
|
def test_catch_subclass():
|
||||||
|
@ -146,3 +154,37 @@ def test_catch_subclass():
|
||||||
assert isinstance(lookup_errors[0], ExceptionGroup)
|
assert isinstance(lookup_errors[0], ExceptionGroup)
|
||||||
exceptions = lookup_errors[0].exceptions
|
exceptions = lookup_errors[0].exceptions
|
||||||
assert isinstance(exceptions[0], KeyError)
|
assert isinstance(exceptions[0], KeyError)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bare_raise_in_handler():
|
||||||
|
"""Test that the "middle" ecxeption group gets discarded."""
|
||||||
|
with pytest.raises(ExceptionGroup) as excgrp:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
first_exc = RuntimeError("first")
|
||||||
|
raise first_exc
|
||||||
|
except RuntimeError as exc:
|
||||||
|
middle_exc = ExceptionGroup(
|
||||||
|
"bad", [ValueError(), ValueError(), TypeError()]
|
||||||
|
)
|
||||||
|
raise middle_exc from exc
|
||||||
|
except* ValueError:
|
||||||
|
raise
|
||||||
|
except* TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert excgrp.value is not middle_exc
|
||||||
|
assert excgrp.value.__cause__ is first_exc
|
||||||
|
assert excgrp.value.__context__ is first_exc
|
||||||
|
|
||||||
|
|
||||||
|
def test_bare_reraise_from_naked_exception():
|
||||||
|
with pytest.raises(ExceptionGroup) as excgrp:
|
||||||
|
try:
|
||||||
|
raise KeyError("foo")
|
||||||
|
except* KeyError:
|
||||||
|
raise
|
||||||
|
|
||||||
|
assert len(excgrp.value.exceptions) == 1
|
||||||
|
assert isinstance(excgrp.value.exceptions[0], KeyError)
|
||||||
|
assert str(excgrp.value.exceptions[0]) == "'foo'"
|
||||||
|
|
|
@ -290,7 +290,7 @@ class ExceptionGroupSubgroupTests(ExceptionGroupTestBase):
|
||||||
]
|
]
|
||||||
|
|
||||||
for match_type, template in testcases:
|
for match_type, template in testcases:
|
||||||
subeg = eg.subgroup(lambda e: isinstance(e, match_type)) # noqa: B023
|
subeg = eg.subgroup(lambda e: isinstance(e, match_type))
|
||||||
self.assertEqual(subeg.message, eg.message)
|
self.assertEqual(subeg.message, eg.message)
|
||||||
self.assertMatchesTemplate(subeg, ExceptionGroup, template)
|
self.assertMatchesTemplate(subeg, ExceptionGroup, template)
|
||||||
|
|
||||||
|
@ -355,7 +355,7 @@ class ExceptionGroupSplitTests(ExceptionGroupTestBase):
|
||||||
]
|
]
|
||||||
|
|
||||||
for match_type, match_template, rest_template in testcases:
|
for match_type, match_template, rest_template in testcases:
|
||||||
match, rest = eg.split(lambda e: isinstance(e, match_type)) # noqa: B023
|
match, rest = eg.split(lambda e: isinstance(e, match_type))
|
||||||
self.assertEqual(match.message, eg.message)
|
self.assertEqual(match.message, eg.message)
|
||||||
self.assertMatchesTemplate(match, ExceptionGroup, match_template)
|
self.assertMatchesTemplate(match, ExceptionGroup, match_template)
|
||||||
if rest_template is not None:
|
if rest_template is not None:
|
||||||
|
@ -451,7 +451,7 @@ class NestedExceptionGroupBasicsTest(ExceptionGroupTestBase):
|
||||||
eg = create_nested_eg()
|
eg = create_nested_eg()
|
||||||
|
|
||||||
line0 = create_nested_eg.__code__.co_firstlineno
|
line0 = create_nested_eg.__code__.co_firstlineno
|
||||||
for (tb, expected) in [
|
for tb, expected in [
|
||||||
(eg.__traceback__, line0 + 19),
|
(eg.__traceback__, line0 + 19),
|
||||||
(eg.exceptions[0].__traceback__, line0 + 6),
|
(eg.exceptions[0].__traceback__, line0 + 6),
|
||||||
(eg.exceptions[1].__traceback__, line0 + 14),
|
(eg.exceptions[1].__traceback__, line0 + 14),
|
||||||
|
@ -469,7 +469,7 @@ class NestedExceptionGroupBasicsTest(ExceptionGroupTestBase):
|
||||||
line0 = create_nested_eg.__code__.co_firstlineno
|
line0 = create_nested_eg.__code__.co_firstlineno
|
||||||
expected_tbs = [[line0 + 19, line0 + 6, line0 + 4], [line0 + 19, line0 + 14]]
|
expected_tbs = [[line0 + 19, line0 + 6, line0 + 4], [line0 + 19, line0 + 14]]
|
||||||
|
|
||||||
for (i, (_, tbs)) in enumerate(leaf_generator(eg)):
|
for i, (_, tbs) in enumerate(leaf_generator(eg)):
|
||||||
self.assertSequenceEqual([tb.tb_lineno for tb in tbs], expected_tbs[i])
|
self.assertSequenceEqual([tb.tb_lineno for tb in tbs], expected_tbs[i])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
|
from urllib.error import HTTPError
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.capture import CaptureFixture
|
from _pytest.capture import CaptureFixture
|
||||||
|
@ -528,3 +530,9 @@ def test_bug_suggestions_attributeerror_no_obj(
|
||||||
print_exception(e) # does not crash
|
print_exception(e) # does not crash
|
||||||
output = capsys.readouterr().err
|
output = capsys.readouterr().err
|
||||||
assert "NamedAttributeError" in output
|
assert "NamedAttributeError" in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_works_around_httperror_bug():
|
||||||
|
# See https://github.com/python/cpython/issues/98778 in Python <= 3.9
|
||||||
|
err = HTTPError("url", 405, "METHOD NOT ALLOWED", None, None)
|
||||||
|
traceback.TracebackException(type(err), err, None)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from exceptiongroup import suppress
|
||||||
|
|
||||||
|
if sys.version_info < (3, 11):
|
||||||
|
from exceptiongroup import BaseExceptionGroup, ExceptionGroup
|
||||||
|
|
||||||
|
|
||||||
|
def test_suppress_exception():
|
||||||
|
with pytest.raises(ExceptionGroup) as exc, suppress(SystemExit):
|
||||||
|
raise BaseExceptionGroup("", [SystemExit(1), RuntimeError("boo")])
|
||||||
|
|
||||||
|
assert len(exc.value.exceptions) == 1
|
||||||
|
assert isinstance(exc.value.exceptions[0], RuntimeError)
|
Loading…
Reference in New Issue