Skip to content

[Python] Move the TTree branch pythonization to Python - #22512

Merged
guitargeek merged 1 commit into
root-project:masterfrom
guitargeek:ttree_pyz
Oct 8, 2026
Merged

guitargeek merged 1 commit into
root-project:masterfrom
guitargeek:ttree_pyz

Conversation

@guitargeek

@guitargeek guitargeek commented Jun 7, 2026 •

Copy link
Copy Markdown
Contributor

TTreePyz.cxx implemented the tree.branch attribute syntax and the TTree::Branch overload dispatch in C++, next to a Python file holding the rest of the TTree pythonization. Both belong with the other pythonizations, where they can be read and changed without a rebuild.

What kept them in C++ is gone: wrapping a leaf needs a converter for a type name and a shape (ll.bind_value), and matching the Branch overloads needs argument inspection, which Python does natively. The GetBranchAttr()/getattr two-tuple protocol is gone with it: getattr now returns the value.

Left in C++ are three helpers, declared through the interpreter on first branch access: GetAddress()/GetObject() return char* and so reach Python as str, and the address the T** overload wants depends on what kind of proxy holds it, a rule only the bindings know. The Declares are serialized on a lock: the interpreter must not see them twice and Declare releases the GIL.

ttree_branch.py covers the T** case by asserting on the branch address before filling, since filling through a branch bound to a temporary proxy is undefined.

🤖 Done with the help of AI

@guitargeek
guitargeek requested a review from aaronj0 June 7, 2026 09:22
@guitargeek guitargeek self-assigned this Jun 7, 2026
@guitargeek
guitargeek requested a review from vepadulano as a code owner June 7, 2026 09:22
@github-actions

github-actions Bot commented Jun 7, 2026 •

Copy link
Copy Markdown

Test Results

    24 files      24 suites   3d 21h 39m 57s ⏱️
 3 885 tests  3 884 ✅ 0 💤 1 ❌
83 158 runs  83 156 ✅ 0 💤 2 ❌

For more details on these failures, see this check.

Results for commit 28d8cb6.

♻️ This comment has been updated with latest results.

Comment thread bindings/pyroot/cppyy/CPyCppyy/src/API.cxx Outdated
Comment thread bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx Outdated
Comment thread bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h Outdated
@guitargeek
guitargeek force-pushed the ttree_pyz branch 2 times, most recently from 9024654 to 858b2fe Compare August 8, 2026 21:44
@guitargeek
guitargeek requested a review from bellenot as a code owner August 8, 2026 21:44
@guitargeek guitargeek changed the title [CPyCppyy] Introduce new public CreatePyValueFromMemory function [Python] Move the TTree branch pythonization to Python Aug 8, 2026
@guitargeek
guitargeek force-pushed the ttree_pyz branch 2 times, most recently from cc12b77 to a990e87 Compare August 8, 2026 22:26
@guitargeek
guitargeek marked this pull request as draft August 8, 2026 22:26
guitargeek added a commit to guitargeek/cppjit that referenced this pull request Sep 3, 2026
LowLevelView.reshape() verified sizes by comparing the sum of the
dimensions instead of the number of elements, their product: reshaping a
five element array to (2, 3) was accepted because 5 == 2 + 3, while
(1, 5) was rejected. The check is skipped for arrays of unknown size,
the ones that typically get reshaped, which is why this went unnoticed.

A correct size check is not enough, though: the strides and the
converter projecting sub-views are chosen for the rank and layout of the
view's C++ type and are not re-derived when reshaping. Any rank change
produced a view reading garbage, and even the identity reshape of a
fixed int[3][5] corrupted its strides.

Reshape therefore now does what it is actually used for: providing the
extent of dimensions the type leaves open, such as the size of an array
behind a pointer. The rank must match, and only an unknown or empty
dimension may be set, with -1 still standing for "unknown"; anything
else raises ValueError, including dimensions whose byte size would
overflow. The byte length is counted in strides of the outermost
dimension as the creators count it, which for views with an itemsize
override (const char*[], notably) differs from the itemsize, and the
strides themselves are left as the creator laid them down.

Also share the "fake max" marking an unknown outermost dimension between
the creators and reshape (it was rederived from the itemsize, mistaking
the unknown size of row-pointer and itemsize-overridden views for a
known one), give the shape property a proper setter (reshape was
installed directly despite its mismatching signature, so assignment
misreported its result and deletion crashed), refuse to reshape a view
without dimensions instead of reading through its null strides, and
check allocations in the shape getter.

Same issue as root-project/root#22512 (159ee7a1); its rework of
rank-changing reshapes is left for a follow-up.
@guitargeek
guitargeek force-pushed the ttree_pyz branch 3 times, most recently from 04c19dc to 54f65af Compare October 7, 2026 17:47
@guitargeek
guitargeek marked this pull request as ready for review October 7, 2026 19:04
TTreePyz.cxx implemented the tree.branch attribute syntax and the
TTree::Branch overload dispatch in C++, next to a Python file holding
the rest of the TTree pythonization. Both belong with the other
pythonizations, where they can be read and changed without a rebuild.

What kept them in C++ is gone: wrapping a leaf needs a converter for a
type name and a shape (ll.bind_value), and matching the Branch overloads
needs argument inspection, which Python does natively. The
GetBranchAttr()/__getattr__ two-tuple protocol is gone with it:
__getattr__ now returns the value.

Left in C++, in ROOT::Internal::TreeUtils next to the other helpers
TTree.h declares for internal callers, are three functions that the
cppyy API cannot replace:

- GetBranchAddress() and GetBranchElementObject(): TBranch::GetAddress()
  and TBranchElement::GetObject() return char* and so reach Python as
  str, with the pointer value gone. Reading the data members instead
  would skip the ValidateAddress() that TBranchElement's getters do.
- BranchPtrToPtr<T>(): the address the T** overloads want is the proxy's
  own pointer, or the caller's pointer behind a reference proxy. That
  rule lives in cppyy's T** converter and no ll function exposes it, so
  the helper takes a T** and lets the converter apply it.

Being compiled into libTree, the first two need no jitting; the template
is instantiated once per proxied type on first use.

ttree_branch.py covers the T** case by asserting on the branch address
before filling, since filling through a branch bound to a temporary
proxy is undefined.

🤖 Done with the help of AI

@pcanal pcanal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM.

@guitargeek
guitargeek merged commit 0155bd4 into root-project:master Oct 8, 2026
31 of 36 checks passed
@guitargeek
guitargeek deleted the ttree_pyz branch October 8, 2026 20:14
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