Skip to content

Commit 293bf3e

Browse files
committed
wip
1 parent 57211c0 commit 293bf3e

File tree

6 files changed

+357
-389
lines changed

6 files changed

+357
-389
lines changed

test/CMakeLists.txt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,17 @@ add_custom_target(run_tests_with_junit_report
8585

8686
set_target_properties(run_tests_with_junit_report PROPERTIES FOLDER "Tests utilities")
8787

88-
# Polars integration test helper library
89-
# =======================================
90-
add_library(test_polars_helper SHARED test_polars_helper.cpp test_polars_helper.hpp)
88+
# Polars integration test helper - Native Python Extension Module
89+
# ===============================================================
90+
# This builds a proper Python extension module (.cpython-*.so) that shares
91+
# the same Python runtime as the interpreter, avoiding dual-runtime issues.
92+
93+
Python_add_library(test_polars_helper MODULE test_polars_helper_module.cpp)
9194

9295
target_link_libraries(test_polars_helper
93-
PUBLIC
96+
PRIVATE
9497
sparrow-pycapsule
9598
sparrow::sparrow
96-
Python::Python
9799
)
98100

99101
target_compile_features(test_polars_helper PRIVATE cxx_std_20)
@@ -107,6 +109,8 @@ endif()
107109
set_target_properties(test_polars_helper PROPERTIES
108110
FOLDER tests
109111
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}
112+
# Python modules must not have a debug suffix - Python won't find them
113+
DEBUG_POSTFIX ""
110114
)
111115

112116
# Python integration test

test/test_library_load.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ def main():
2121
print("="*60)
2222

2323
try:
24-
# Step 1: Check environment variables
2524
step(1, "Checking environment variables")
2625
helper_path = os.environ.get('TEST_POLARS_HELPER_LIB_PATH')
2726
main_path = os.environ.get('SPARROW_PYCAPSULE_LIB_PATH')
@@ -37,7 +36,6 @@ def main():
3736
print("ERROR: SPARROW_PYCAPSULE_LIB_PATH not set")
3837
return 1
3938

40-
# Step 2: Check files exist
4139
step(2, "Checking library files exist")
4240
helper_file = Path(helper_path)
4341
main_file = Path(main_path)
@@ -53,21 +51,18 @@ def main():
5351
print(f"ERROR: Main library not found at {main_file}")
5452
return 1
5553

56-
# Step 3: Test ctypes import
5754
step(3, "Testing ctypes module")
5855
print("ctypes imported successfully")
5956
print(f"ctypes.CDLL: {ctypes.CDLL}")
6057

61-
# Step 4: Try loading main library
6258
step(4, "Loading sparrow-pycapsule library")
6359
try:
6460
main_lib = ctypes.CDLL(str(main_file))
6561
print("✓ Main library loaded successfully")
6662
except Exception as e:
6763
print(f"✗ Failed to load main library: {e}")
6864
return 1
69-
70-
# Step 5: Try loading helper library
65+
7166
step(5, "Loading test_polars_helper library")
7267
try:
7368
helper_lib = ctypes.CDLL(str(helper_file))
@@ -76,7 +71,6 @@ def main():
7671
print(f"✗ Failed to load helper library: {e}")
7772
return 1
7873

79-
# Step 6: Check if init_python exists
8074
step(6, "Checking for init_python function")
8175
try:
8276
if hasattr(helper_lib, 'init_python'):
@@ -89,24 +83,6 @@ def main():
8983
print(f"✗ Error checking for init_python: {e}")
9084
return 1
9185

92-
# Step 7: Call init_python
93-
step(7, "Calling init_python()")
94-
print("About to call init_python()...")
95-
sys.stdout.flush()
96-
97-
try:
98-
helper_lib.init_python()
99-
print("✓ init_python() called successfully")
100-
except Exception as e:
101-
print(f"✗ init_python() failed: {e}")
102-
return 1
103-
104-
# Step 8: Check Python state
105-
step(8, "Checking Python interpreter state")
106-
import sys as sys2
107-
print(f"Python version: {sys2.version}")
108-
print(f"Python initialized: {sys2.version_info}")
109-
11086
print("\n" + "="*60)
11187
print("✓ ALL STEPS COMPLETED SUCCESSFULLY")
11288
print("="*60)

test/test_polars_helper.cpp

Lines changed: 50 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* @brief C++ helper library for Polars integration tests.
44
*
55
* This library provides C functions that can be called from Python via ctypes
6-
* to test the bidirectional data exchange between Polars and sparrow.
6+
* to test the bidirectional data exchange between Polars and sparrow using
7+
* the sparrow::pycapsule interface.
78
*/
89

910
#include "test_polars_helper.hpp"
@@ -12,52 +13,18 @@
1213
#include <iostream>
1314
#include <vector>
1415

15-
#include <Python.h>
16-
#include <sparrow-pycapsule/config/config.hpp>
17-
#include <sparrow-pycapsule/pycapsule.hpp>
18-
1916
#include <sparrow/array.hpp>
2017
#include <sparrow/primitive_array.hpp>
2118
#include <sparrow/utils/nullable.hpp>
2219

23-
// Export C API functions for ctypes
20+
#include <sparrow-pycapsule/pycapsule.hpp>
21+
2422
extern "C"
2523
{
26-
/**
27-
* @brief Initialize Python interpreter if not already initialized.
28-
*
29-
* Note: When called from Python (via ctypes), Python is already initialized.
30-
* This function only initializes if called from pure C++ context.
31-
*/
32-
void init_python()
33-
{
34-
// When called from Python via ctypes, Python is already initialized
35-
// So this check should always be true, and we do nothing
36-
if (Py_IsInitialized())
37-
{
38-
// Python already initialized - this is the normal case when called from Python
39-
return;
40-
}
41-
42-
// Only initialize if we're being called from C++ without Python
43-
Py_Initialize();
44-
}
45-
46-
/**
47-
* @brief Create a simple test array and return raw Arrow C pointers.
48-
*
49-
* Instead of creating PyCapsules in C++, we return raw pointers that Python
50-
* will wrap in PyCapsules. This avoids Python C API calls from ctypes libraries.
51-
*
52-
* @param schema_ptr_out Output parameter for ArrowSchema pointer
53-
* @param array_ptr_out Output parameter for ArrowArray pointer
54-
* @return 0 on success, -1 on error
55-
*/
56-
int create_test_array_as_pointers(void** schema_ptr_out, void** array_ptr_out)
24+
int create_test_array_capsules(PyObject** schema_capsule_out, PyObject** array_capsule_out)
5725
{
5826
try
5927
{
60-
// Create a test array with nullable integers
6128
std::vector<sparrow::nullable<int32_t>> values = {
6229
sparrow::make_nullable<int32_t>(10, true),
6330
sparrow::make_nullable<int32_t>(20, true),
@@ -69,120 +36,101 @@ extern "C"
6936
sparrow::primitive_array<int32_t> prim_array(std::move(values));
7037
sparrow::array arr(std::move(prim_array));
7138

72-
// Extract Arrow C structures
73-
auto [arrow_array, arrow_schema] = sparrow::extract_arrow_structures(std::move(arr));
39+
auto [schema_capsule, array_capsule] = sparrow::pycapsule::export_array_to_capsules(arr);
7440

75-
// Allocate on heap and transfer ownership to Python
76-
ArrowSchema* schema_ptr = new ArrowSchema(std::move(arrow_schema));
77-
ArrowArray* array_ptr = new ArrowArray(std::move(arrow_array));
41+
if (schema_capsule == nullptr || array_capsule == nullptr)
42+
{
43+
std::cerr << "Failed to create PyCapsules\n";
44+
Py_XDECREF(schema_capsule);
45+
Py_XDECREF(array_capsule);
46+
return -1;
47+
}
7848

79-
*schema_ptr_out = schema_ptr;
80-
*array_ptr_out = array_ptr;
49+
*schema_capsule_out = schema_capsule;
50+
*array_capsule_out = array_capsule;
8151

8252
return 0;
8353
}
8454
catch (const std::exception& e)
8555
{
86-
std::cerr << "Exception in create_test_array_as_pointers: " << e.what() << std::endl;
56+
std::cerr << "Exception in create_test_array_capsules: " << e.what() << '\n';
8757
return -1;
8858
}
8959
}
9060

91-
/**
92-
* @brief Import array from raw Arrow C pointers and return new pointers.
93-
*
94-
* @param schema_ptr_in Input ArrowSchema pointer
95-
* @param array_ptr_in Input ArrowArray pointer
96-
* @param schema_ptr_out Output ArrowSchema pointer
97-
* @param array_ptr_out Output ArrowArray pointer
98-
* @return 0 on success, -1 on error
99-
*/
100-
int
101-
roundtrip_array_pointers(void* schema_ptr_in, void* array_ptr_in, void** schema_ptr_out, void** array_ptr_out)
61+
int roundtrip_array_capsules(
62+
PyObject* schema_capsule_in,
63+
PyObject* array_capsule_in,
64+
PyObject** schema_capsule_out,
65+
PyObject** array_capsule_out
66+
)
10267
{
10368
try
10469
{
105-
if (schema_ptr_in == nullptr || array_ptr_in == nullptr)
70+
if (schema_capsule_in == nullptr || array_capsule_in == nullptr)
10671
{
107-
std::cerr << "Null input pointers" << std::endl;
72+
std::cerr << "Null input capsules\n";
10873
return -1;
10974
}
11075

111-
ArrowSchema* schema_in = static_cast<ArrowSchema*>(schema_ptr_in);
112-
ArrowArray* array_in = static_cast<ArrowArray*>(array_ptr_in);
113-
114-
// Move the data (mark originals as released to prevent double-free)
115-
ArrowSchema schema_moved = *schema_in;
116-
ArrowArray array_moved = *array_in;
117-
schema_in->release = nullptr;
118-
array_in->release = nullptr;
119-
120-
// Import into sparrow
121-
sparrow::array arr(std::move(array_moved), std::move(schema_moved));
76+
sparrow::array arr = sparrow::pycapsule::import_array_from_capsules(
77+
schema_capsule_in,
78+
array_capsule_in
79+
);
12280

123-
std::cout << "Roundtrip array size: " << arr.size() << std::endl;
81+
std::cout << "Roundtrip array size: " << arr.size() << '\n';
12482

125-
// Export back out
126-
auto [arrow_array_out, arrow_schema_out] = sparrow::extract_arrow_structures(std::move(arr));
83+
auto [schema_capsule, array_capsule] = sparrow::pycapsule::export_array_to_capsules(arr);
12784

128-
ArrowSchema* schema_out = new ArrowSchema(std::move(arrow_schema_out));
129-
ArrowArray* array_out = new ArrowArray(std::move(arrow_array_out));
85+
if (schema_capsule == nullptr || array_capsule == nullptr)
86+
{
87+
std::cerr << "Failed to create output PyCapsules\n";
88+
Py_XDECREF(schema_capsule);
89+
Py_XDECREF(array_capsule);
90+
return -1;
91+
}
13092

131-
*schema_ptr_out = schema_out;
132-
*array_ptr_out = array_out;
93+
*schema_capsule_out = schema_capsule;
94+
*array_capsule_out = array_capsule;
13395

13496
return 0;
13597
}
13698
catch (const std::exception& e)
13799
{
138-
std::cerr << "Exception in roundtrip_array_pointers: " << e.what() << std::endl;
100+
std::cerr << "Exception in roundtrip_array_capsules: " << e.what() << '\n';
139101
return -1;
140102
}
141103
}
142104

143-
/**
144-
* @brief Verify that Arrow C structures have the expected size.
145-
*
146-
* @param schema_ptr ArrowSchema pointer
147-
* @param array_ptr ArrowArray pointer
148-
* @param expected_size Expected array size
149-
* @return 0 if size matches, -1 otherwise
150-
*/
151-
int verify_array_size_from_pointers(void* schema_ptr, void* array_ptr, size_t expected_size)
105+
int verify_array_size_from_capsules(PyObject* schema_capsule, PyObject* array_capsule, size_t expected_size)
152106
{
153107
try
154108
{
155-
if (schema_ptr == nullptr || array_ptr == nullptr)
109+
if (schema_capsule == nullptr || array_capsule == nullptr)
156110
{
157-
std::cerr << "Null pointers provided" << std::endl;
111+
std::cerr << "Null capsules provided\n";
158112
return -1;
159113
}
160114

161-
ArrowSchema* schema = static_cast<ArrowSchema*>(schema_ptr);
162-
ArrowArray* array = static_cast<ArrowArray*>(array_ptr);
163-
164-
// Move the data (mark originals as released)
165-
ArrowSchema schema_moved = *schema;
166-
ArrowArray array_moved = *array;
167-
schema->release = nullptr;
168-
array->release = nullptr;
169-
170-
sparrow::array arr(std::move(array_moved), std::move(schema_moved));
115+
sparrow::array arr = sparrow::pycapsule::import_array_from_capsules(
116+
schema_capsule,
117+
array_capsule
118+
);
171119

172120
if (arr.size() == expected_size)
173121
{
174-
std::cout << "Array size verified: " << arr.size() << std::endl;
122+
std::cout << "Array size verified: " << arr.size() << '\n';
175123
return 0;
176124
}
177125
else
178126
{
179-
std::cerr << "Size mismatch: expected " << expected_size << ", got " << arr.size() << std::endl;
127+
std::cerr << "Size mismatch: expected " << expected_size << ", got " << arr.size() << '\n';
180128
return -1;
181129
}
182130
}
183131
catch (const std::exception& e)
184132
{
185-
std::cerr << "Exception in verify_array_size_from_pointers: " << e.what() << std::endl;
133+
std::cerr << "Exception in verify_array_size_from_capsules: " << e.what() << '\n';
186134
return -1;
187135
}
188136
}

0 commit comments

Comments
 (0)