Skip to content

Conversation

@ionelmc
Copy link
Owner

@ionelmc ionelmc commented Oct 18, 2025

Closes #73. Closes #65.

cjwatson and others added 5 commits October 18, 2025 02:57
The doctests weren't being run and had bitrotted.  Make them work again.
This fixes `CoverageWarning: Couldn't parse Python file
'.../tests/badsyntax.py' (couldnt-parse)`.

Based on a change by Oldřich Jedlička.
Using of the class constructor and passing it the args values is not always
correct, the custom parameters could be different to the args actually recorded
by the built-in Exception class. Try to use the __new__ to create class instance
and initialize the args attribute afterwards to prevent calling constructor with
wrong arguments. Other instance attributes are initialized in a standard way by
pickle module, so this way the reconstruction of the exception is complete.

Subclasses of OSError are known exceptions to this, because they initialize
read-only attributes from the constructor argument values, so usage of the
__new__ factory method leads to lost values of those read-only attributes.

Fixes: #65

Signed-off-by: Oldřich Jedlička <[email protected]>
@codecov
Copy link

codecov bot commented Oct 18, 2025

Codecov Report

❌ Patch coverage is 98.10127% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.80%. Comparing base (4116639) to head (1791c15).
⚠️ Report is 54 commits behind head on master.

Files with missing lines Patch % Lines
src/tblib/pickling_support.py 91.66% 1 Missing and 1 partial ⚠️
tests/test_pickle_exception.py 99.13% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #82      +/-   ##
==========================================
+ Coverage   89.45%   96.80%   +7.35%     
==========================================
  Files           4        9       +5     
  Lines         275      564     +289     
  Branches       46       46              
==========================================
+ Hits          246      546     +300     
+ Misses         18       14       -4     
+ Partials       11        4       -7     
Flag Coverage Δ
py310 (macos) 93.78% <96.83%> (?)
py310 (ubuntu) 93.78% <96.83%> (?)
py310 (windows) 93.78% <96.83%> (?)
py311 (macos) 96.26% <98.10%> (?)
py311 (ubuntu) 96.26% <98.10%> (?)
py311 (windows) 96.26% <98.10%> (?)
py312 (macos) 96.26% <98.10%> (?)
py312 (ubuntu) 96.26% <98.10%> (?)
py312 (windows) 96.26% <98.10%> (?)
py313 (macos) 96.26% <98.10%> (?)
py313 (ubuntu) 96.26% <98.10%> (?)
py313 (windows) 96.26% <98.10%> (?)
py314 (macos) 96.26% <96.83%> (?)
py314 (ubuntu) 96.26% <96.83%> (?)
py314 (windows) 96.26% <96.83%> (?)
py39 (macos) 93.43% <96.83%> (?)
py39 (ubuntu) 93.43% <96.83%> (?)
py39 (windows) 93.43% <96.83%> (?)
pypy310 (macos) 91.29% <96.83%> (?)
pypy310 (ubuntu) 91.29% <96.83%> (?)
pypy310 (windows) 91.29% <96.83%> (?)
pypy311 (macos) 93.78% <96.83%> (?)
pypy311 (ubuntu) 93.78% <96.83%> (?)
pypy311 (windows) 93.78% <96.83%> (?)
pypy39 (macos) 90.95% <96.83%> (?)
pypy39 (ubuntu) 90.95% <96.83%> (?)
pypy39 (windows) 90.95% <96.83%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ionelmc ionelmc merged commit ac82d41 into master Oct 20, 2025
107 of 110 checks passed
@ionelmc ionelmc deleted the rebased-73 branch October 20, 2025 09:58
@ionelmc ionelmc mentioned this pull request Oct 20, 2025
@oldium
Copy link
Contributor

oldium commented Oct 20, 2025

I finally got to test this and the result is different from my patch. Compare the original exception (shortened):

Traceback (most recent call last):
  File "timeslots\vss.py", line 84, in my_open
PermissionError: [Errno 13] Permission denied: 'C:\\Users\\work\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Network\\Cookies'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "timeslots\controller.py", line 290, in set_browser_thread
  ...
  File "timeslots\vss.py", line 89, in my_open
timeslots.exceptions.AdminRequiredException: This operation requires administrator privileges. Please run as administrator.

deserialized with your fix:

Traceback (most recent call last):
  File "timeslots\vss.py", line 84, in my_open
PermissionError: [WinError None] Permission denied: 'C:\\Users\\work\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Network\\Cookies' -> None

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "timeslots\controller.py", line 290, in set_browser_thread
  ...
  File "timeslots\vss.py", line 89, in my_open
timeslots.exceptions.AdminRequiredException: This operation requires administrator privileges. Please run as administrator.

and deserialized with my version of the fix:

Traceback (most recent call last):
  File "timeslots\vss.py", line 84, in my_open
PermissionError: [Errno 13] Permission denied: 'C:\\Users\\work\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Network\\Cookies'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "timeslots\controller.py", line 290, in set_browser_thread
  ...
  File "timeslots\vss.py", line 89, in my_open
timeslots.exceptions.AdminRequiredException: This operation requires administrator privileges. Please run as administrator.

@oldium
Copy link
Contributor

oldium commented Oct 20, 2025

According to the source code it is important what is being set: https://github.com/python/cpython/blob/main/Objects/exceptions.c#L2286

@ionelmc
Copy link
Owner Author

ionelmc commented Oct 20, 2025

Huh, what version of python is that?

@oldium
Copy link
Contributor

oldium commented Oct 20, 2025

It is 3.11.9. The difference is in the __str__ output. Here is the test case. The first one passes, the second one not.

def test_oserror():
    try:
        raise OSError(2, 'err', 3, None, 5)
    except Exception as e:
        exc = e

    str_output = str(exc)
    tblib.pickling_support.install(exc)
    exc = pickle.loads(pickle.dumps(exc))

    assert isinstance(exc, OSError)
    assert exc.errno == 2
    assert exc.strerror == 'err'
    assert exc.filename == 3
    assert exc.filename2 == 5
    assert exc.__traceback__ is not None
    assert str_output == str(exc)


def test_real_oserror():
    try:
        import os
        os.open('non-existing-file', os.O_RDONLY)
        exc = None
    except Exception as e:
        exc = e

    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)

@oldium
Copy link
Contributor

oldium commented Oct 20, 2025

I am on Windows. The issue is that the Py_None and NULL are two different values, so setting the attribute values to None does not respect the original NULL value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unpickling exceptions can fail with TypeError about the number of arguments given in specific scenarios

4 participants