diff --git a/cbits/python.c b/cbits/python.c index 4a76cb6..5530be7 100644 --- a/cbits/python.c +++ b/cbits/python.c @@ -212,17 +212,6 @@ void inline_py_Integer_FromPy( } - -static PyObject* AsyncError = 0; - -PyObject* inline_py_AsyncError() { - if( AsyncError == 0 ) { - AsyncError = PyErr_NewException("inline_py.AsyncError", PyExc_BaseException, 0); - } - return AsyncError; -} - - static Py_tss_t key_py_async = Py_tss_NEEDS_INIT; void inline_py_init_state(void *stack) { @@ -253,3 +242,50 @@ void inline_py_initialize(void) { exit(1); } } + +// ================================================================ +// inline_python module + +PyObject* inline_py_AsyncCancelled() { + static PyObject* AsyncCancelled = 0; + if( AsyncCancelled == 0 ) { + AsyncCancelled = PyErr_NewException("inline_python.AsyncCancelled", PyExc_BaseException, 0); + } + return AsyncCancelled; +} + +static PyMethodDef inline_python_methods[] = { + {NULL, NULL, 0, NULL} +}; + +static int inline_python_module_exec(PyObject *m) { + // Initialize module only once + static int initialized = 0; + if( initialized ) { + return 0; + } + initialized = 1; + // + if (PyModule_AddObjectRef(m, "AsyncCancelled", inline_py_AsyncCancelled()) < 0) { + return -1; + } + return 0; +} + +static PyModuleDef_Slot inline_python_module_slots[] = { + {Py_mod_exec, inline_python_module_exec}, + {0, NULL} +}; + +static struct PyModuleDef inline_python_module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "inline_python", + .m_size = 0, + .m_slots = inline_python_module_slots, + .m_methods = inline_python_methods, +}; + +PyMODINIT_FUNC PyInit_inline_python(void) +{ + return PyModuleDef_Init(&inline_python_module); +} diff --git a/include/inline-python.h b/include/inline-python.h index 801baba..b3ec015 100644 --- a/include/inline-python.h +++ b/include/inline-python.h @@ -101,5 +101,11 @@ void* inline_py_get_state(void); +// ================================================================ +// inline_python module +// ================================================================ + +PyMODINIT_FUNC PyInit_inline_python(void); + // Obtain class for async exception -PyObject* inline_py_AsyncError(); +PyObject* inline_py_AsyncCancelled(); diff --git a/inline-python.cabal b/inline-python.cabal index bd72c08..f1949db 100644 --- a/inline-python.cabal +++ b/inline-python.cabal @@ -135,6 +135,7 @@ test-suite inline-python-tests TST.Callbacks TST.Roundtrip TST.Util + TST.Module test-suite inline-python-tests1 import: language, tests @@ -148,6 +149,7 @@ test-suite inline-python-tests1 TST.Callbacks TST.Roundtrip TST.Util + TST.Module benchmark pysmall import: language diff --git a/src/Python/Internal/Eval.hs b/src/Python/Internal/Eval.hs index d5e8d5e..a4dded7 100644 --- a/src/Python/Internal/Eval.hs +++ b/src/Python/Internal/Eval.hs @@ -426,6 +426,8 @@ doInitializePythonIO = do if( PyStatus_Exception(status) ) { goto error; }; + // Make builting module visible to python + PyImport_AppendInittab("inline_python", PyInit_inline_python); // Initialize interpreter status = Py_InitializeFromConfig(&cfg); if( PyStatus_Exception(status) ) { @@ -651,7 +653,7 @@ cancelPy PyAsync{asyncTID=tid, asyncTidStack, asyncPyTID, asyncAlive} = do False -> return 1 True -> [C.block| int { int gil = PyGILState_Ensure(); - int n = PyThreadState_SetAsyncExc($(uint64_t py_tid), inline_py_AsyncError()); + int n = PyThreadState_SetAsyncExc($(uint64_t py_tid), inline_py_AsyncCancelled()); PyGILState_Release(gil); return n; }|] @@ -787,10 +789,10 @@ convertPy2Haskell = runProgram $ do p_type <- peekElemOff p_errors 0 -- NOTE: When we set exception using PyThreadState_SetAsyncExc -- this field remains NULL on python<=3.11. In this case we - -- assume it's our AsyncError: + -- assume it's our AsyncCancelled: p_value <- peekElemOff p_errors 1 >>= \case NULL -> [CU.block| PyObject* { - PyObject *err_class = inline_py_AsyncError(); + PyObject *err_class = inline_py_AsyncCancelled(); PyObject *tuple = PyTuple_New(0); PyObject *err = PyObject_Call(err_class, tuple, NULL); Py_DECREF(tuple); diff --git a/test/TST/Module.hs b/test/TST/Module.hs new file mode 100644 index 0000000..4f5b208 --- /dev/null +++ b/test/TST/Module.hs @@ -0,0 +1,29 @@ +-- | +module TST.Module where + +import Test.Tasty +import Test.Tasty.HUnit +import Python.Inline +import Python.Inline.QQ + + +tests :: TestTree +tests = testGroup "Builtin module" + [ testCase "Module exists" $ runPy [py_| import inline_python |] + , testCase "AsyncCancelled" $ runPy [py_| + import inline_python + assert issubclass(inline_python.AsyncCancelled, BaseException) + assert not issubclass(inline_python.AsyncCancelled, Exception) + |] + -- We want to check that inline_python types are stable under + -- reload using importlib. + , testCase "importlib.reload stable" $ runPy [py_| + import inline_python + import importlib + ty = inline_python.AsyncCancelled + err = ty() + importlib.reload(inline_python) + assert ty is inline_python.AsyncCancelled + assert isinstance(err, inline_python.AsyncCancelled) + |] + ] diff --git a/test/exe/main.hs b/test/exe/main.hs index fb69716..aa59c9b 100644 --- a/test/exe/main.hs +++ b/test/exe/main.hs @@ -7,6 +7,7 @@ import TST.FromPy import TST.ToPy import TST.Callbacks import TST.Roundtrip +import TST.Module import Python.Inline main :: IO () @@ -16,4 +17,5 @@ main = withPython $ defaultMain $ testGroup "PY" , TST.ToPy.tests , TST.Roundtrip.tests , TST.Callbacks.tests + , TST.Module.tests ]