Skip to content

[cppyy] Preserve C++ docstring when uninformative PyExceptions/no traceback#21989

Merged
dpiparo merged 1 commit intoroot-project:masterfrom
aaronj0:pyerrors-ext
Apr 22, 2026
Merged

[cppyy] Preserve C++ docstring when uninformative PyExceptions/no traceback#21989
dpiparo merged 1 commit intoroot-project:masterfrom
aaronj0:pyerrors-ext

Conversation

@aaronj0
Copy link
Copy Markdown
Contributor

@aaronj0 aaronj0 commented Apr 21, 2026

This follows up on #18163 which broke test test_fragile:test10_documentation on the compiler-research forks since we lose a C++ side error. This patch resolves the issue by preserving C++ errors when that information can be actually useful, either when the PyException is uninformative, or there is no Python traceback. We completely preserve the intended behaviour of the original commit:

import cppyy

def inner_func(x):
    raise ValueError("hello")

def func(x):
    return inner_func(x)

cpp_wrapper = cppyy.gbl.std.function["double(double)"](func)

cpp_wrapper(3)

resulting in something like:

Traceback (most recent call last):
  File "/home/ajomy/cppyy-interop-dev/CppInterOp/cppyy/test/test_err.py", line 11, in <module>
    cpp_wrapper(3)
  File "/home/ajomy/cppyy-interop-dev/CppInterOp/cppyy/test/test_err.py", line 7, in func
    return inner_func(x)
           ^^^^^^^^^^^^^
  File "/home/ajomy/cppyy-interop-dev/CppInterOp/cppyy/test/test_err.py", line 4, in inner_func
    raise ValueError("hello")
ValueError: hello

instead of also appending ValueError: double std::function<double(double)>::operator()(double __args) => ValueError: hello to the err message

But also retaining the C++ docstring context in cases as seen in test_fragile:test10_documentation where we expect the docstring when calling the following overload:

class D {
public:
    virtual int check() { return (int)'D'; }
    virtual int check(int, int) { return (int)'D'; }
    void overload() {}
    void overload(no_such_class*) {}
    void overload(char, int i = 0) {}  // Reflex requires a named arg
    void overload(int, no_such_class* p = 0) {}
};

as d.overload(None):

    assert "void fragile::D::overload(char, int i = 0)".replace(" ", "") in err_msg

From compiler-research/CPyCppyy@7d6f1b3

Copy link
Copy Markdown
Contributor

@guitargeek guitargeek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

Test Results

    22 files      22 suites   3d 7h 28m 57s ⏱️
 3 838 tests  3 833 ✅  1 💤 4 ❌
75 760 runs  75 738 ✅ 18 💤 4 ❌

For more details on these failures, see this check.

Results for commit c74f746.

♻️ This comment has been updated with latest results.

@aaronj0
Copy link
Copy Markdown
Contributor Author

aaronj0 commented Apr 22, 2026

@guitargeek I see these failures that seem unrelated:

2026-04-21T21:59:30.1347042Z 3727/3735 Test #1860: roottest-python-JupyROOT-thread_local_notebook ....................................................***Failed    5.91 sec
2026-04-21T21:59:30.1347965Z -- TEST COMMAND -- 
2026-04-21T21:59:30.1348376Z cd /github/home/ROOT-CI/build/roottest/python/JupyROOT
2026-04-21T21:59:30.1350005Z /usr/bin/timeout -s USR2 270s /py-venv/ROOT-CI/bin/python3.12 /github/home/ROOT-CI/src/roottest/python/JupyROOT/nbdiff.py thread_local.ipynb
2026-04-21T21:59:30.1350961Z -- BEGIN TEST OUTPUT --
2026-04-21T21:59:30.1351178Z 
2026-04-21T21:59:30.1351304Z -- END TEST OUTPUT --
2026-04-21T21:59:30.1351629Z -- BEGIN TEST ERROR --
2026-04-21T21:59:30.1353154Z [IPKernelApp] ERROR | Can't load '/github/home/ROOT-CI/build/lib/ROOT/_jupyroot/kernel/magics/rootbrowsemagic.py': error: cannot import name 'browseRootFile' from 'ROOT._jupyroot.helpers.utils' (/github/home/ROOT-CI/build/lib/ROOT/_jupyroot/helpers/utils.py)
2026-04-21T21:59:30.1354700Z Traceback (most recent call last):
2026-04-21T21:59:30.1355349Z   File "/github/home/ROOT-CI/build/lib/ROOT/_jupyroot/kernel/utils.py", line 76, in __init__
2026-04-21T21:59:30.1356043Z     module = importlib.import_module(module_path)
2026-04-21T21:59:30.1356566Z              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-04-21T21:59:30.1357119Z   File "/usr/lib64/python3.12/importlib/__init__.py", line 90, in import_module
2026-04-21T21:59:30.1357805Z     return _bootstrap._gcd_import(name[level:], package, level)
2026-04-21T21:59:30.1358361Z            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-04-21T21:59:30.1358917Z   File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
2026-04-21T21:59:30.1359661Z   File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
2026-04-21T21:59:30.1360322Z   File "<frozen importlib._bootstrap>", line 1331, in _find_and_load_unlocked
2026-04-21T21:59:30.1361393Z   File "<frozen importlib._bootstrap>", line 935, in _load_unlocked
2026-04-21T21:59:30.1362035Z   File "<frozen importlib._bootstrap_external>", line 999, in exec_module
2026-04-21T21:59:30.1362767Z   File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
2026-04-21T21:59:30.1363654Z   File "/github/home/ROOT-CI/build/lib/ROOT/_jupyroot/kernel/magics/rootbrowsemagic.py", line 17, in <module>
2026-04-21T21:59:30.1364471Z     from ROOT._jupyroot.helpers.utils import browseRootFile
2026-04-21T21:59:30.1365537Z ImportError: cannot import name 'browseRootFile' from 'ROOT._jupyroot.helpers.utils' (/github/home/ROOT-CI/build/lib/ROOT/_jupyroot/helpers/utils.py)
2026-04-21T21:59:30.1366351Z 
2026-04-21T21:59:30.1366612Z During handling of the above exception, another exception occurred:
2026-04-21T21:59:30.1367019Z 

Any idea what is causing it? I don't see them on master

@aaronj0 aaronj0 added this to the 6.40.00-rc1 milestone Apr 22, 2026
@guitargeek
Copy link
Copy Markdown
Contributor

Probably related to this PR: #21761

Can you rebase on master and push? Just to get a fresh incremental

…ceback

This follows up on root-project#18163 which broke test `test_fragile:test10_documentation` on the compiler-research forks since we lose a C++ side error.
This patch resolves the issue by preserving C++ errors when that information can be actually useful, either when the PyException is uninformative, or there is no Python traceback.
We completely preserve the intended behaviour of the original commit:

```py
import cppyy

def inner_func(x):
    raise ValueError("hello")

def func(x):
    return inner_func(x)

cpp_wrapper = cppyy.gbl.std.function["double(double)"](func)

cpp_wrapper(3)
```

resulting in something like:

```
Traceback (most recent call last):
  File "/home/ajomy/cppyy-interop-dev/CppInterOp/cppyy/test/test_err.py", line 11, in <module>
    cpp_wrapper(3)
  File "/home/ajomy/cppyy-interop-dev/CppInterOp/cppyy/test/test_err.py", line 7, in func
    return inner_func(x)
           ^^^^^^^^^^^^^
  File "/home/ajomy/cppyy-interop-dev/CppInterOp/cppyy/test/test_err.py", line 4, in inner_func
    raise ValueError("hello")
ValueError: hello
```
instead of also appending `ValueError: double std::function<double(double)>::operator()(double __args) =>
ValueError: hello` to the err message

But also retaining the C++ docstring context in cases as seen in `test_fragile:test10_documentation` where we expect the docstring when calling the following overload:

```cpp
class D {
public:
    virtual int check() { return (int)'D'; }
    virtual int check(int, int) { return (int)'D'; }
    void overload() {}
    void overload(no_such_class*) {}
    void overload(char, int i = 0) {}  // Reflex requires a named arg
    void overload(int, no_such_class* p = 0) {}
};
```

as `d.overload(None)`:

```
    assert "void fragile::D::overload(char, int i = 0)".replace(" ", "") in err_msg
```
@aaronj0
Copy link
Copy Markdown
Contributor Author

aaronj0 commented Apr 22, 2026

cc @dpiparo this is ready to be landed for the release. The recent rebase should make all checks green.

@dpiparo dpiparo merged commit 17b3cab into root-project:master Apr 22, 2026
28 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants