From d8b51071770564a3e1ace11abb7d7aebbae2f263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 20 Oct 2025 15:23:26 +0300 Subject: [PATCH 1/6] Do not set winerror or filenames if they are None. --- .pre-commit-config.yaml | 2 +- src/tblib/pickling_support.py | 14 +++++++------- tests/test_pickle_exception.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb42dd0..90fdbc4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,5 +22,5 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - id: mixed-line-ending - args: [--fix=lf] + args: [--fix] - id: debug-statements diff --git a/src/tblib/pickling_support.py b/src/tblib/pickling_support.py index a561aa4..d0bdae8 100644 --- a/src/tblib/pickling_support.py +++ b/src/tblib/pickling_support.py @@ -65,13 +65,13 @@ def pickle_exception( 'args': obj.args, } if isinstance(obj, OSError): - attrs.update( - errno=obj.errno, - strerror=obj.strerror, - winerror=getattr(obj, 'winerror', None), - filename=obj.filename, - filename2=obj.filename2, - ) + attrs.update(errno=obj.errno, strerror=obj.strerror) + if (winerror := getattr(obj, 'winerror', None)) is not None: + attrs['winerror'] = winerror + if obj.filename is not None: + attrs['filename'] = obj.filename + if obj.filename2 is not None: + attrs['filename2'] = obj.filename2 return ( unpickle_exception_with_attrs, diff --git a/tests/test_pickle_exception.py b/tests/test_pickle_exception.py index 476a47d..0e1f35e 100644 --- a/tests/test_pickle_exception.py +++ b/tests/test_pickle_exception.py @@ -1,3 +1,4 @@ +import traceback from traceback import format_exception try: @@ -230,6 +231,35 @@ def test_oserror(): assert exc.__traceback__ is not None +class OpenError(Exception): + pass + + +def bad_open(): + try: + raise PermissionError(13, 'Booboo', 'filename', None, None) + except Exception as e: + raise OpenError(e) from e + + +def test_permissionerror(): + try: + bad_open() + except Exception as e: + exc = e + + tblib.pickling_support.install(exc) + exc = pickle.loads(pickle.dumps(exc)) + print(''.join(traceback.format_exception(exc))) + assert isinstance(exc, OpenError) + assert exc.__traceback__ is not None + assert repr(exc) == "OpenError(PermissionError(13, 'Booboo'))" + assert str(exc) == "[Errno 13] Booboo: 'filename'" + assert exc.args[0].errno == 13 + assert exc.args[0].strerror == 'Booboo' + assert exc.args[0].filename == 'filename' + + class BadError(Exception): def __init__(self): super().__init__('Bad Bad Bad!') From 4384126804b78e002efcf1744dffbbecf86e75e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 20 Oct 2025 15:29:07 +0300 Subject: [PATCH 2/6] Remove debug print. --- tests/test_pickle_exception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pickle_exception.py b/tests/test_pickle_exception.py index 0e1f35e..014fc1c 100644 --- a/tests/test_pickle_exception.py +++ b/tests/test_pickle_exception.py @@ -250,7 +250,7 @@ def test_permissionerror(): tblib.pickling_support.install(exc) exc = pickle.loads(pickle.dumps(exc)) - print(''.join(traceback.format_exception(exc))) + assert isinstance(exc, OpenError) assert exc.__traceback__ is not None assert repr(exc) == "OpenError(PermissionError(13, 'Booboo'))" From 38d5c29e573d5d860ff290036a97f6d02b18c1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 20 Oct 2025 15:38:15 +0300 Subject: [PATCH 3/6] Lint fix. --- .pre-commit-config.yaml | 1 - tests/test_pickle_exception.py | 1 - 2 files changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 90fdbc4..ca6432e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,5 +22,4 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - id: mixed-line-ending - args: [--fix] - id: debug-statements diff --git a/tests/test_pickle_exception.py b/tests/test_pickle_exception.py index 014fc1c..dc52405 100644 --- a/tests/test_pickle_exception.py +++ b/tests/test_pickle_exception.py @@ -1,4 +1,3 @@ -import traceback from traceback import format_exception try: From 8464c81210d3bd9baa9f71e7ee891d8c38b32022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 20 Oct 2025 15:54:18 +0300 Subject: [PATCH 4/6] Add coveralls badge. --- .cookiecutterrc | 2 +- README.rst | 12 ++++-------- ci/templates/.github/workflows/github-actions.yml | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.cookiecutterrc b/.cookiecutterrc index f069a1d..f360b98 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -9,7 +9,7 @@ default_context: codecov: 'yes' command_line_interface: 'no' command_line_interface_bin_name: '-' - coveralls: 'no' + coveralls: 'yes' distribution_name: tblib email: contact@ionelmc.ro formatter_quote_style: single diff --git a/README.rst b/README.rst index bed352a..fb3bfcf 100644 --- a/README.rst +++ b/README.rst @@ -10,37 +10,33 @@ Overview * - docs - |docs| * - tests - - |github-actions| |codecov| + - |github-actions| |coveralls| |codecov| * - package - |version| |wheel| |supported-versions| |supported-implementations| |commits-since| .. |docs| image:: https://readthedocs.org/projects/python-tblib/badge/?style=flat :target: https://readthedocs.org/projects/python-tblib/ :alt: Documentation Status - .. |github-actions| image:: https://github.com/ionelmc/python-tblib/actions/workflows/github-actions.yml/badge.svg :alt: GitHub Actions Build Status :target: https://github.com/ionelmc/python-tblib/actions - +.. |coveralls| image:: https://coveralls.io/repos/github/ionelmc/python-tblib/badge.svg?branch=master + :alt: Coverage Status + :target: https://coveralls.io/github/ionelmc/python-tblib?branch=master .. |codecov| image:: https://codecov.io/gh/ionelmc/python-tblib/branch/master/graphs/badge.svg?branch=master :alt: Coverage Status :target: https://app.codecov.io/github/ionelmc/python-tblib - .. |version| image:: https://img.shields.io/pypi/v/tblib.svg :alt: PyPI Package latest release :target: https://pypi.org/project/tblib - .. |wheel| image:: https://img.shields.io/pypi/wheel/tblib.svg :alt: PyPI Wheel :target: https://pypi.org/project/tblib - .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/tblib.svg :alt: Supported versions :target: https://pypi.org/project/tblib - .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/tblib.svg :alt: Supported implementations :target: https://pypi.org/project/tblib - .. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-tblib/v3.1.0.svg :alt: Commits since latest release :target: https://github.com/ionelmc/python-tblib/compare/v3.1.0...master diff --git a/ci/templates/.github/workflows/github-actions.yml b/ci/templates/.github/workflows/github-actions.yml index ff7b53f..469538d 100644 --- a/ci/templates/.github/workflows/github-actions.yml +++ b/ci/templates/.github/workflows/github-actions.yml @@ -79,4 +79,4 @@ jobs: - uses: coverallsapp/github-action@v2 with: parallel-finished: true -{{ '' }} +{{- '' }} From ae2bb89f777db46c50cfca4611be0ed97c906f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 20 Oct 2025 15:56:29 +0300 Subject: [PATCH 5/6] Dont include 32bit envs anymore. --- .github/workflows/github-actions.yml | 108 +++++------------- .../.github/workflows/github-actions.yml | 3 +- 2 files changed, 28 insertions(+), 83 deletions(-) diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index a76bf38..3c737eb 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -17,217 +17,163 @@ jobs: python: '3.13' tox_env: 'docs' os: 'ubuntu-latest' - - name: 'py39 (ubuntu)' + - name: 'py39 (ubuntu/x64)' python: '3.9' python_arch: 'x64' tox_env: 'py39' os: 'ubuntu-latest' cover: true - - name: 'py39 (windows)' + - name: 'py39 (windows/x64)' python: '3.9' python_arch: 'x64' tox_env: 'py39' os: 'windows-latest' cover: true - - name: 'py39 (windows)' - python: '3.9' - python_arch: 'x86' - tox_env: 'py39' - os: 'windows-latest' - cover: true - - name: 'py39 (macos)' + - name: 'py39 (macos/arm64)' python: '3.9' python_arch: 'arm64' tox_env: 'py39' os: 'macos-latest' cover: true - - name: 'py310 (ubuntu)' + - name: 'py310 (ubuntu/x64)' python: '3.10' python_arch: 'x64' tox_env: 'py310' os: 'ubuntu-latest' cover: true - - name: 'py310 (windows)' + - name: 'py310 (windows/x64)' python: '3.10' python_arch: 'x64' tox_env: 'py310' os: 'windows-latest' cover: true - - name: 'py310 (windows)' - python: '3.10' - python_arch: 'x86' - tox_env: 'py310' - os: 'windows-latest' - cover: true - - name: 'py310 (macos)' + - name: 'py310 (macos/arm64)' python: '3.10' python_arch: 'arm64' tox_env: 'py310' os: 'macos-latest' cover: true - - name: 'py311 (ubuntu)' + - name: 'py311 (ubuntu/x64)' python: '3.11' python_arch: 'x64' tox_env: 'py311' os: 'ubuntu-latest' cover: true - - name: 'py311 (windows)' + - name: 'py311 (windows/x64)' python: '3.11' python_arch: 'x64' tox_env: 'py311' os: 'windows-latest' cover: true - - name: 'py311 (windows)' - python: '3.11' - python_arch: 'x86' - tox_env: 'py311' - os: 'windows-latest' - cover: true - - name: 'py311 (macos)' + - name: 'py311 (macos/arm64)' python: '3.11' python_arch: 'arm64' tox_env: 'py311' os: 'macos-latest' cover: true - - name: 'py312 (ubuntu)' + - name: 'py312 (ubuntu/x64)' python: '3.12' python_arch: 'x64' tox_env: 'py312' os: 'ubuntu-latest' cover: true - - name: 'py312 (windows)' + - name: 'py312 (windows/x64)' python: '3.12' python_arch: 'x64' tox_env: 'py312' os: 'windows-latest' cover: true - - name: 'py312 (windows)' - python: '3.12' - python_arch: 'x86' - tox_env: 'py312' - os: 'windows-latest' - cover: true - - name: 'py312 (macos)' + - name: 'py312 (macos/arm64)' python: '3.12' python_arch: 'arm64' tox_env: 'py312' os: 'macos-latest' cover: true - - name: 'py313 (ubuntu)' + - name: 'py313 (ubuntu/x64)' python: '3.13' python_arch: 'x64' tox_env: 'py313' os: 'ubuntu-latest' cover: true - - name: 'py313 (windows)' + - name: 'py313 (windows/x64)' python: '3.13' python_arch: 'x64' tox_env: 'py313' os: 'windows-latest' cover: true - - name: 'py313 (windows)' - python: '3.13' - python_arch: 'x86' - tox_env: 'py313' - os: 'windows-latest' - cover: true - - name: 'py313 (macos)' + - name: 'py313 (macos/arm64)' python: '3.13' python_arch: 'arm64' tox_env: 'py313' os: 'macos-latest' cover: true - - name: 'py314 (ubuntu)' + - name: 'py314 (ubuntu/x64)' python: '3.14' python_arch: 'x64' tox_env: 'py314' os: 'ubuntu-latest' cover: true - - name: 'py314 (windows)' + - name: 'py314 (windows/x64)' python: '3.14' python_arch: 'x64' tox_env: 'py314' os: 'windows-latest' cover: true - - name: 'py314 (windows)' - python: '3.14' - python_arch: 'x86' - tox_env: 'py314' - os: 'windows-latest' - cover: true - - name: 'py314 (macos)' + - name: 'py314 (macos/arm64)' python: '3.14' python_arch: 'arm64' tox_env: 'py314' os: 'macos-latest' cover: true - - name: 'pypy39 (ubuntu)' + - name: 'pypy39 (ubuntu/x64)' python: 'pypy-3.9' python_arch: 'x64' tox_env: 'pypy39' os: 'ubuntu-latest' cover: true - - name: 'pypy39 (windows)' + - name: 'pypy39 (windows/x64)' python: 'pypy-3.9' python_arch: 'x64' tox_env: 'pypy39' os: 'windows-latest' cover: true - - name: 'pypy39 (windows)' - python: 'pypy-3.9' - python_arch: 'x86' - tox_env: 'pypy39' - os: 'windows-latest' - cover: true - - name: 'pypy39 (macos)' + - name: 'pypy39 (macos/arm64)' python: 'pypy-3.9' python_arch: 'arm64' tox_env: 'pypy39' os: 'macos-latest' cover: true - - name: 'pypy310 (ubuntu)' + - name: 'pypy310 (ubuntu/x64)' python: 'pypy-3.10' python_arch: 'x64' tox_env: 'pypy310' os: 'ubuntu-latest' cover: true - - name: 'pypy310 (windows)' + - name: 'pypy310 (windows/x64)' python: 'pypy-3.10' python_arch: 'x64' tox_env: 'pypy310' os: 'windows-latest' cover: true - - name: 'pypy310 (windows)' - python: 'pypy-3.10' - python_arch: 'x86' - tox_env: 'pypy310' - os: 'windows-latest' - cover: true - - name: 'pypy310 (macos)' + - name: 'pypy310 (macos/arm64)' python: 'pypy-3.10' python_arch: 'arm64' tox_env: 'pypy310' os: 'macos-latest' cover: true - - name: 'pypy311 (ubuntu)' + - name: 'pypy311 (ubuntu/x64)' python: 'pypy-3.11' python_arch: 'x64' tox_env: 'pypy311' os: 'ubuntu-latest' cover: true - - name: 'pypy311 (windows)' + - name: 'pypy311 (windows/x64)' python: 'pypy-3.11' python_arch: 'x64' tox_env: 'pypy311' os: 'windows-latest' cover: true - - name: 'pypy311 (windows)' - python: 'pypy-3.11' - python_arch: 'x86' - tox_env: 'pypy311' - os: 'windows-latest' - cover: true - - name: 'pypy311 (macos)' + - name: 'pypy311 (macos/arm64)' python: 'pypy-3.11' python_arch: 'arm64' tox_env: 'pypy311' diff --git a/ci/templates/.github/workflows/github-actions.yml b/ci/templates/.github/workflows/github-actions.yml index 469538d..05c828e 100644 --- a/ci/templates/.github/workflows/github-actions.yml +++ b/ci/templates/.github/workflows/github-actions.yml @@ -30,10 +30,9 @@ jobs: {% for os, python_arch in [ ['ubuntu', 'x64'], ['windows', 'x64'], - ['windows', 'x86'], ['macos', 'arm64'], ] %} - - name: '{{ env }} ({{ os }})' + - name: '{{ env }} ({{ os }}/{{ python_arch }})' python: '{{ python }}' python_arch: '{{ python_arch }}' tox_env: '{{ env }}' From 8ef251568ca5c709df3e885599f840ec3022b6ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Mon, 20 Oct 2025 19:43:58 +0300 Subject: [PATCH 6/6] Add a test with native oserror. --- tests/test_pickle_exception.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_pickle_exception.py b/tests/test_pickle_exception.py index dc52405..ebe93ee 100644 --- a/tests/test_pickle_exception.py +++ b/tests/test_pickle_exception.py @@ -1,3 +1,4 @@ +import os from traceback import format_exception try: @@ -345,3 +346,20 @@ def test_oserror_simple(): assert exc.errno == 13 assert exc.strerror == 'Permission denied' assert exc.__traceback__ is not None + + +def test_real_oserror(): + try: + os.open('non-existing-file', os.O_RDONLY) + except Exception as e: + exc = e + else: + pytest.fail('os.open should have raised an OSError') + + str_output = str(exc) + tblib.pickling_support.install(exc) + exc = pickle.loads(pickle.dumps(exc)) + + assert isinstance(exc, OSError) + assert exc.errno == 2 + assert str_output == str(exc)