Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix numba tests with Python 3.13+ #17972

Merged
merged 2 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions bindings/pyroot/pythonizations/python/ROOT/_numbadeclare.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,22 @@ def pywrapper({SIGNATURE}):

# Execute the pywrapper code and generate the wrapper function
# which calls the jitted C function
exec(pywrappercode, glob, locals()) in {}

if not 'pywrapper' in locals():
# Python 3.13 changes the semantics of the `locals()` builtin function
# such that in optimized scopes (e.g. at function scope as it is
# happening right now) the dictionary is not updated when changed. Bind
# the `locals()` dictionary to a temporary object. This way, the call
# to `exec()` will actually change the dictionary and the pywrapper
# function will be found. Note that this change is backwards-compatible.
local_objects = locals()
exec(pywrappercode, glob, local_objects)

if not 'pywrapper' in local_objects:
raise Exception('Failed to create Python wrapper function:\n{}'.format(pywrappercode))

# Jit the Python wrapper code
c_return_type, c_input_types = get_c_signature(input_types, return_type)
try:
nbcfunc = nb.cfunc(c_return_type(*c_input_types), nopython=True)(locals()['pywrapper'])
nbcfunc = nb.cfunc(c_return_type(*c_input_types), nopython=True)(local_objects['pywrapper'])
except:
raise Exception('Failed to jit Python wrapper with numba.cfunc')
func.__py_wrapper__ = pywrappercode
Expand Down
10 changes: 4 additions & 6 deletions bindings/pyroot/pythonizations/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,10 @@ if (dataframe)
# std::string_view in CPyCppyy
ROOT_ADD_PYUNITTEST(pyroot_string_view string_view.py)
if(NOT MSVC OR win_broken_tests)
if(NOT DEFINED ENV{ROOTTEST_IGNORE_NUMBA_PY3})
# Test wrapping Python callables for use in C++ using numba
ROOT_ADD_PYUNITTEST(pyroot_numbadeclare numbadeclare.py PYTHON_DEPS numba)
ROOT_ADD_PYUNITTEST(pyroot_rdf_filter_pyz rdf_filter_pyz.py PYTHON_DEPS numba)
ROOT_ADD_PYUNITTEST(pyroot_rdf_define_pyz rdf_define_pyz.py PYTHON_DEPS numba)
endif()
# Test wrapping Python callables for use in C++ using numba
ROOT_ADD_PYUNITTEST(pyroot_numbadeclare numbadeclare.py PYTHON_DEPS numba)
ROOT_ADD_PYUNITTEST(pyroot_rdf_filter_pyz rdf_filter_pyz.py PYTHON_DEPS numba)
ROOT_ADD_PYUNITTEST(pyroot_rdf_define_pyz rdf_define_pyz.py PYTHON_DEPS numba)
endif()
endif()

Expand Down
50 changes: 1 addition & 49 deletions bindings/pyroot/pythonizations/test/numbadeclare.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import unittest
import ROOT
import sys
import os
import numpy as np
import gc


# Check whether these tests should be skipped
skip = False
skip_reason = ""
if "ROOTTEST_IGNORE_NUMBA_PY3" in os.environ:
skip = True
skip_reason = "Running python3 and ROOTTEST_IGNORE_NUMBA_PY3 was set"

if not skip:
import numba as nb
import numba as nb

default_test_inputs = [-1.0, 0.0, 100.0]

Expand All @@ -28,7 +18,6 @@ class NumbaDeclareSimple(unittest.TestCase):


# Test refcounts
@unittest.skipIf(skip, skip_reason)
def test_refcount_decorator(self):
"""
Test refcount of decorator
Expand All @@ -37,7 +26,6 @@ def test_refcount_decorator(self):
gc.collect()
self.assertEqual(sys.getrefcount(x), 2)

@unittest.skipIf(skip, skip_reason)
def test_refcount_pycallable(self):
"""
Test refcount of decorated callable
Expand All @@ -52,7 +40,6 @@ def f2(x):
self.assertEqual(sys.getrefcount(f1), sys.getrefcount(f2) + 2)

# Test optional name
@unittest.skipIf(skip, skip_reason)
def test_optional_name(self):
"""
Test optional name of wrapper function
Expand All @@ -64,7 +51,6 @@ def f(x):
self.assertTrue(hasattr(ROOT.Numba, optname))

# Test attributes
@unittest.skipIf(skip, skip_reason)
def test_additional_attributes(self):
"""
Test additional attributes
Expand All @@ -87,7 +73,6 @@ def fn1(x):
self.assertEqual(sys.getrefcount(fn1.numba_func), 3)

# Test cling integration
@unittest.skipIf(skip, skip_reason)
def test_cling(self):
"""
Test function call in cling
Expand All @@ -99,7 +84,6 @@ def fn12(x):
self.assertEqual(fn12(42.0), ROOT.y12)

# Test RDataFrame integration
@unittest.skipIf(skip, skip_reason)
def test_rdataframe(self):
"""
Test function call as part of RDataFrame
Expand All @@ -113,7 +97,6 @@ def fn13(x):
self.assertEqual(mean_x.GetValue(), 1.5)
self.assertEqual(mean_y.GetValue(), 3.0)

@unittest.skipIf(skip, skip_reason)
def test_rdataframe_temporary(self):
"""
Test passing a temporary from an RDataFrame operation
Expand All @@ -131,7 +114,6 @@ def pass_temporary(v):
self.assertTrue(np.array_equal(rvecf, np.array([4.])))

# Test wrappings
@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_void(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -144,7 +126,6 @@ def fn2n():
self.assertEqual(x1, x2)
self.assertEqual(type(x1), type(x2))

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_f(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -158,7 +139,6 @@ def fn2(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x1), type(x2))

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_d(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -173,7 +153,6 @@ def fn2d(x):
# NOTE: There is no double in Python because everything is a double.
self.assertEqual(type(x1), type(x2))

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_i(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -187,7 +166,6 @@ def fn3(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x1), type(x2))

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_l(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -201,7 +179,6 @@ def fn4(x):
self.assertEqual(x1, x2)
self.assertEqual(int, type(x2))

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_u(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -215,7 +192,6 @@ def fn5(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x2), int)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_k(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -229,7 +205,6 @@ def fn6(x):
self.assertEqual(x1, x2)
self.assertEqual(int, type(x2))

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_b(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -243,7 +218,6 @@ def fn6b(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x1), type(x2))

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_b(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -257,7 +231,6 @@ def fn6b2(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x1), type(x2))

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_i(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -270,7 +243,6 @@ def fn7i(x):
x2 = ROOT.Numba.fn7i(v)
self.assertEqual(x1, x2)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_l(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -283,7 +255,6 @@ def fn7l(x):
x2 = ROOT.Numba.fn7l(v)
self.assertEqual(x1, x2)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_ui(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -296,7 +267,6 @@ def fn7ui(x):
x2 = ROOT.Numba.fn7ui(v)
self.assertEqual(x1, x2)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_ul(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -322,7 +292,6 @@ class NumbaDeclareArray(unittest.TestCase):
# Preload the library now.
ROOT.gSystem.Load("libROOTVecOps")

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_vecf(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -337,7 +306,6 @@ def g1(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x2), float)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_vecf_vecd(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -352,7 +320,6 @@ def g1_2vec(x, y):
self.assertEqual(x1, x2)
self.assertEqual(type(x2), float)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_vecd(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -367,7 +334,6 @@ def g1d(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x2), float)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_veci(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -382,7 +348,6 @@ def g1i(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x2), int)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_vecl(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -397,7 +362,6 @@ def g1l(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x2), int)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_vecui(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -412,7 +376,6 @@ def g1ui(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x2), int)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_vecul(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -427,7 +390,6 @@ def g1ul(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x2), int)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_vecb(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -442,7 +404,6 @@ def g1b(x):
self.assertEqual(x1, x2)
self.assertEqual(type(x2), int)

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_vecf(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -456,7 +417,6 @@ def g2f(x):
x2 = ROOT.Numba.g2f(ROOT.VecOps.RVec('float')(v))
self.assertTrue((x1 == x2).all())

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_vecd(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -470,7 +430,6 @@ def g2d(x):
x2 = ROOT.Numba.g2d(ROOT.VecOps.RVec('double')(v))
self.assertTrue((x1 == x2).all())

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_veci(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -484,7 +443,6 @@ def g2i(x):
x2 = ROOT.Numba.g2i(ROOT.VecOps.RVec('int')(v))
self.assertTrue((x1 == x2).all())

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_vecl(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -498,7 +456,6 @@ def g2l(x):
x2 = ROOT.Numba.g2l(ROOT.VecOps.RVec('long')(v))
self.assertTrue((x1 == x2).all())

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_vecul(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -512,7 +469,6 @@ def g2ul(x):
x2 = ROOT.Numba.g2ul(ROOT.VecOps.RVec('unsigned long')(v))
self.assertTrue((x1 == x2).all())

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_vecui(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -526,7 +482,6 @@ def g2ui(x):
x2 = ROOT.Numba.g2ui(ROOT.VecOps.RVec('unsigned int')(v))
self.assertTrue((x1 == x2).all())

@unittest.skipIf(skip, skip_reason)
def test_wrapper_out_vecb(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -541,7 +496,6 @@ def g2b(x):
self.assertEqual(x1[0], bool(x2[0]))
self.assertEqual(x1[1], bool(x2[1]))

@unittest.skipIf(skip, skip_reason)
def test_wrapper_in_vecfb_out_vecf(self):
"""
Test wrapper with different input/output configurations
Expand All @@ -556,7 +510,6 @@ def g2fb(x, y):
self.assertEqual(x1[0], bool(x2[0]))
self.assertEqual(x1[1], bool(x2[1]))

@unittest.skipIf(skip, skip_reason)
def test_const_modifier(self):
"""
Test const modifier in input argument type
Expand All @@ -569,7 +522,6 @@ def const_mod(v):

self.assertTrue(np.array_equal(rvecf, np.array([1.,4.])))

@unittest.skipIf(skip, skip_reason)
def test_reference(self):
"""
Test passing a reference as input argument
Expand Down
5 changes: 0 additions & 5 deletions tutorials/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -784,15 +784,10 @@ if(ROOT_pyroot_FOUND)
)

if(NOT dataframe
OR DEFINED ENV{ROOTTEST_IGNORE_NUMBA_PY3}
OR (MSVC AND NOT win_broken_tests))
list(APPEND pyveto analysis/dataframe/df038_NumbaDeclare.py)
endif()

if(dataframe AND DEFINED ENV{ROOTTEST_IGNORE_PANDAS_PY3})
list(APPEND pyveto analysis/dataframe/df026_AsNumpyArrays.py)
endif()

# Rules specific to distributed RDataFrame
# Disable distributed RDF tutorials if we didn't check dependencies in the environment first
if(NOT test_distrdf_pyspark)
Expand Down
Loading