From e42979d8759f8216739eb4686bf2123304a4b0a6 Mon Sep 17 00:00:00 2001 From: Marius S <39998+winding-lines@users.noreply.github.com> Date: Fri, 11 Apr 2025 04:18:41 +0300 Subject: [PATCH 1/4] Add PyCapsule_New and PyCapsule_GetPointer These are critical when interacting with PyCapsules created by other libraries in the system. The current use case is interacting with PyArrow. Signed-off-by: Marius Seritan <39998+winding-lines@users.noreply.github.com> --- mojo/stdlib/src/python/_cpython.mojo | 36 +++++++++++++++++++ .../test/python/test_python_cpython.mojo | 29 +++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/mojo/stdlib/src/python/_cpython.mojo b/mojo/stdlib/src/python/_cpython.mojo index 54051c83f6..2a9585ce0e 100644 --- a/mojo/stdlib/src/python/_cpython.mojo +++ b/mojo/stdlib/src/python/_cpython.mojo @@ -1925,3 +1925,39 @@ struct CPython: self._inc_total_rc() return r + + # ===-------------------------------------------------------------------===# + # Capsules + # ref: https://docs.python.org/3/c-api/capsule.html + # ===-------------------------------------------------------------------===# + + fn PyCapsule_New( + mut self, + pointer: OpaquePointer, + name: StringSlice, + destructor: destructor, + ) -> PyObjectPtr: + """Create a PyCapsule to communicate to another C extension the C API in `pointer`, identified by `name` and with the custom destructor in `destructor`. + + [Reference](https://docs.python.org/3/c-api/capsule.html#c.PyCapsule_New). + """ + # PyObject *PyCapsule_New(void *pointer, const char *name, PyCapsule_Destructor destructor) + var new_capsule = self.lib.call["PyCapsule_New", PyObjectPtr]( + pointer, name.unsafe_ptr().bitcast[c_char](), destructor + ) + self._inc_total_rc() + return new_capsule + + fn PyCapsule_GetPointer( + mut self, + capsule: PyObjectPtr, + name: StringSlice, + ) -> OpaquePointer: + """Extract the pointer to another C extension from a PyCapsule `capsule` with the given `name`. + + [Reference](https://docs.python.org/3/c-api/capsule.html#c.PyCapsule_GetPointer). + """ + # void *PyCapsule_GetPointer(PyObject *capsule, const char *name) + return self.lib.call["PyCapsule_GetPointer", OpaquePointer]( + capsule, name.unsafe_ptr().bitcast[c_char]() + ) diff --git a/mojo/stdlib/test/python/test_python_cpython.mojo b/mojo/stdlib/test/python/test_python_cpython.mojo index 8b51539845..082171b4d0 100644 --- a/mojo/stdlib/test/python/test_python_cpython.mojo +++ b/mojo/stdlib/test/python/test_python_cpython.mojo @@ -14,6 +14,8 @@ # RUN: %mojo %s from python import Python, PythonObject +from python._cpython import PyObjectPtr +from memory import UnsafePointer from testing import assert_equal, assert_false, assert_raises, assert_true @@ -34,7 +36,34 @@ def test_PyObject_HasAttrString(mut python: Python): _ = the_object +fn destructor(capsule: PyObjectPtr) -> None: + pass + + +def test_PyCapsule(mut python: Python): + var Cpython_env = python.impl._cpython + + # Not a PyCapsule, a NULL pointer is expected. + var the_object = PythonObject(0) + var result = Cpython_env[].PyCapsule_GetPointer( + the_object.py_object, "some_name" + ) + var expected = UnsafePointer[NoneType]() + assert_equal(expected, result) + + # Build a capsule. + var capsule_impl = UnsafePointer[UInt64].alloc(1) + var capsule = Cpython_env[].PyCapsule_New( + capsule_impl.bitcast[NoneType](), "some_name", destructor + ) + var capsule_pointer = Cpython_env[].PyCapsule_GetPointer( + capsule, "some_name" + ) + assert_equal(capsule_impl.bitcast[NoneType](), capsule_pointer) + + def main(): # initializing Python instance calls init_python var python = Python() test_PyObject_HasAttrString(python) + test_PyCapsule(python) From b8a79338dc5c3f911badb53c216ef9a9d92262b7 Mon Sep 17 00:00:00 2001 From: Marius S <39998+winding-lines@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:40:30 -0700 Subject: [PATCH 2/4] Update mojo/stdlib/test/python/test_python_cpython.mojo Co-authored-by: Laszlo Kindrat Signed-off-by: Marius Seritan <39998+winding-lines@users.noreply.github.com> --- mojo/stdlib/test/python/test_python_cpython.mojo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mojo/stdlib/test/python/test_python_cpython.mojo b/mojo/stdlib/test/python/test_python_cpython.mojo index 082171b4d0..7d9c64cf79 100644 --- a/mojo/stdlib/test/python/test_python_cpython.mojo +++ b/mojo/stdlib/test/python/test_python_cpython.mojo @@ -41,7 +41,7 @@ fn destructor(capsule: PyObjectPtr) -> None: def test_PyCapsule(mut python: Python): - var Cpython_env = python.impl._cpython + var cpython_env = python.impl.cpython() # Not a PyCapsule, a NULL pointer is expected. var the_object = PythonObject(0) From 45d677ec8b254fce1f7f90f0871414d7df8560e0 Mon Sep 17 00:00:00 2001 From: Marius S <39998+winding-lines@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:40:56 -0700 Subject: [PATCH 3/4] Update mojo/stdlib/test/python/test_python_cpython.mojo Co-authored-by: Laszlo Kindrat Signed-off-by: Marius Seritan <39998+winding-lines@users.noreply.github.com> --- mojo/stdlib/test/python/test_python_cpython.mojo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mojo/stdlib/test/python/test_python_cpython.mojo b/mojo/stdlib/test/python/test_python_cpython.mojo index 7d9c64cf79..0ab035ac4c 100644 --- a/mojo/stdlib/test/python/test_python_cpython.mojo +++ b/mojo/stdlib/test/python/test_python_cpython.mojo @@ -45,7 +45,7 @@ def test_PyCapsule(mut python: Python): # Not a PyCapsule, a NULL pointer is expected. var the_object = PythonObject(0) - var result = Cpython_env[].PyCapsule_GetPointer( + var result = cpython_env.PyCapsule_GetPointer( the_object.py_object, "some_name" ) var expected = UnsafePointer[NoneType]() From affadddbd9b335ec59652646e7176d481f4142cf Mon Sep 17 00:00:00 2001 From: Marius Seritan <39998+winding-lines@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:07:46 -0700 Subject: [PATCH 4/4] Address feedback Signed-off-by: Marius Seritan <39998+winding-lines@users.noreply.github.com> --- .../test/python/test_python_cpython.mojo | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/mojo/stdlib/test/python/test_python_cpython.mojo b/mojo/stdlib/test/python/test_python_cpython.mojo index 0ab035ac4c..6c2e6dad78 100644 --- a/mojo/stdlib/test/python/test_python_cpython.mojo +++ b/mojo/stdlib/test/python/test_python_cpython.mojo @@ -20,16 +20,16 @@ from testing import assert_equal, assert_false, assert_raises, assert_true def test_PyObject_HasAttrString(mut python: Python): - var Cpython_env = python.impl._cpython + var cpython_env = python.impl.cpython() var the_object = PythonObject(0) - var result = Cpython_env[].PyObject_HasAttrString( + var result = cpython_env.PyObject_HasAttrString( the_object.py_object, "__contains__" ) assert_equal(0, result) the_object = Python.list(1, 2, 3) - result = Cpython_env[].PyObject_HasAttrString( + result = cpython_env.PyObject_HasAttrString( the_object.py_object, "__contains__" ) assert_equal(1, result) @@ -48,19 +48,21 @@ def test_PyCapsule(mut python: Python): var result = cpython_env.PyCapsule_GetPointer( the_object.py_object, "some_name" ) - var expected = UnsafePointer[NoneType]() - assert_equal(expected, result) + var expected_none = UnsafePointer[NoneType]() + assert_equal(expected_none, result) # Build a capsule. var capsule_impl = UnsafePointer[UInt64].alloc(1) - var capsule = Cpython_env[].PyCapsule_New( + var capsule = cpython_env.PyCapsule_New( capsule_impl.bitcast[NoneType](), "some_name", destructor ) - var capsule_pointer = Cpython_env[].PyCapsule_GetPointer( - capsule, "some_name" - ) + var capsule_pointer = cpython_env.PyCapsule_GetPointer(capsule, "some_name") assert_equal(capsule_impl.bitcast[NoneType](), capsule_pointer) + # Use a different name. + result = cpython_env.PyCapsule_GetPointer(capsule, "some_other_name") + assert_equal(expected_none, result) + def main(): # initializing Python instance calls init_python