-
Notifications
You must be signed in to change notification settings - Fork 14
Python support using converter nodes #45
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
Merged
Merged
Changes from 42 commits
Commits
Show all changes
71 commits
Select commit
Hold shift + click to select a range
a198f3d
proof-of-concept Python support using converter nodes
wlav 947f842
Merge branch 'main' into python-support
wlav 4541abd
add wrapper codes to the library
wlav 1a86bec
code cleanup (clang-format) and simplifications
wlav 1d2936a
clang-format fixes and disable it for the Python Type definitions
wlav 61e895d
clang-format fixes of the header files
wlav 7fcb88b
extend supported types to a couple more builins and retrieve informat…
wlav 6343575
fix cmake formatting
wlav 86f5b30
move py:phlex property underneath the HAS_CPPYY block
wlav 0e5c8f9
add missing registration helper module
wlav be99c00
Python exception -> std::runtime_error
wlav 10b8af1
observer to check adder algorithm output
wlav bbf3d1b
move initial GIL release later and make sure it only happens once
wlav 136da63
a function with no configured output becomes an observer
wlav c4a80c9
remove spurious printout
wlav 135df83
change configuration lookup failures into Python exceptions
wlav 7ff5946
make sure that the adder sum result is non-zero
wlav 6554826
improve testing by adding an observer that asserts the algorithm output
wlav 10e8040
move the GIL RAII to the common wrapper header file for reuse
wlav f108c2d
Merge branch 'main' into python-support
wlav 08c1ec4
update to new registration API
wlav 1aa6a57
fix vector indexing error if no outputs provided
wlav a3075a7
add error helper to pymodule.so
wlav 21cf613
fix cmake formatting to conform to the rules
wlav 5173c8c
add a way to pass configuration to python modules
wlav 6714ef0
support callable instances
wlav 59c13ae
trivial demonstrator of std::vector<int> input to a Python algorithm
wlav 242c856
add vector test files
wlav 993d42d
make python module registration resemble the C++ one more closely
wlav 43a090e
simplify life-times by letting the node take a reference to the regis…
wlav f6586e3
rename "pymodule" property to "pyplugin"
wlav bbab231
formatting fixes (clang-format getting confused by PyObject_HEAD)
wlav 0d16fea
vector support goes through Numpy views for now, so add that depedency
wlav 5e36597
add a lifeline object to tie life times of handles and views onto them
wlav 818051c
use numpy views instead of array copies to handle std::vector
wlav 6360b8f
explicitly collect tests based on activation before setting properties
wlav c214ec6
protect all of import_numpy to prevent an "unused variable" warning
wlav 9ae87a9
clang format remove empty line
wlav 4d0a5a4
another hiding of unused variables attempt to make coverage happy
wlav 7bcb611
one more attempt to compile w/o errors if numpy isn't installed
wlav 93161f5
add additional vector types and use numpy.typing in the annotations
wlav c4a8651
move python support module from test to plugins directory
wlav 73fc25e
split up the generic register into transform and observe registration
wlav c80ce98
fix typos and errors/missing comments
wlav 3d04386
clang-format fixes
wlav 6731f34
enable the "python" subdirectory in "plugins"
wlav 268438b
simplify retrievel of configuration values in python by using the dec…
wlav 014ec6e
expose kind() for the held json values to simplify conversion to Python
wlav a78d905
Range of cosmetic fixes.
wlav c057274
clang-format fixes
wlav a91c716
return false in initalize on failure to initialize custom types
wlav 9761843
add an internal cache for python-side configuration
wlav 0fd6947
rename kind -> prototype_internal_kind
wlav 5aa3f34
clang format fixes
wlav 951de8c
allow an annotation of `None` as "no output"
wlav a6c10aa
add a test to check mapping of configuration types
wlav 20e6968
uniquely tie converter nodes to algorithms for lifetime management
wlav a7c42de
Merge branch 'main' into python-support
wlav 1504fe7
cleanup
wlav e3b18f6
upgrade to new registration interface (note: DOES NOT WORK)
wlav 9b762a3
upgrade to new interface
wlav c888e1f
clang-format fix
wlav 3b8fbe4
cmake formatting fixes
wlav 44601a2
add module-level docstrings to all python modules
wlav 0f12e69
fix Phlex plugin search path
wlav f3a5828
add support for numpy -> std::vector returns (through copying)
wlav 16771f2
use the "job" layer as a default for now until a python representatio…
wlav 5911ff1
ruff fixes
wlav b341ebc
fix formatting for the case where numpy isn't installed
wlav b90701c
add a way for testing failures/error paths for code coverage
wlav 976cdd4
Catch exceptions in main to ensure graceful exit and coverage data ge…
greenc-FNAL File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| #include <string> | ||
|
|
||
| #include "phlex/configuration.hpp" | ||
| #include "wrap.hpp" | ||
|
|
||
| using namespace phlex::experimental; | ||
|
|
||
| // Create a dict-like access to the configuration from Python. | ||
| // clang-format off | ||
| struct phlex::experimental::py_config_map { | ||
| PyObject_HEAD | ||
| phlex::experimental::configuration const* ph_config; | ||
| }; | ||
| // clang-format on | ||
|
|
||
| PyObject* phlex::experimental::wrap_configuration(configuration const* config) | ||
| { | ||
| if (!config) { | ||
| PyErr_SetString(PyExc_ValueError, "provided configuration is null"); | ||
| return nullptr; | ||
| } | ||
|
|
||
| py_config_map* pyconfig = PyObject_New(py_config_map, &PhlexConfig_Type); | ||
| pyconfig->ph_config = config; | ||
|
|
||
| return (PyObject*)pyconfig; | ||
| } | ||
|
|
||
| static PyObject* pcm_subscript(py_config_map* pycmap, PyObject* args) | ||
| { | ||
| // Retrieve a named configuration setting. | ||
| // | ||
| // Configuration is only accessible through templated lookups and it is up to | ||
| // the caller to figure the correct one. Here, conversion is attempted in order, | ||
| // unless an optional type is provided. On failure, the setting is returned as | ||
| // a string. | ||
| // | ||
| // Since the configuration is read-only, values are cached in the implicit | ||
| // dictionary such that they can continue to be inspected. | ||
| // | ||
| // Python arguments expected: | ||
| // name: the property to retrieve | ||
| // type: the type to cast to (optional) and one of: int, float (for C++ | ||
| // double), or str (for C++ std::string) | ||
| // tuple as standin for std::vector | ||
| // coll: boolean, set to True if this is a collection of <type> | ||
|
|
||
| PyObject *pyname = nullptr, *type = nullptr; | ||
| int coll = 0; | ||
| if (PyTuple_Check(args)) { | ||
| if (!PyArg_ParseTuple(args, "U|Op:__getitem__", &pyname, &type, &coll)) | ||
| return nullptr; | ||
| } else | ||
| pyname = args; | ||
|
|
||
| // cached lookup | ||
| #if PY_VERSION_HEX >= 0x030d0000 | ||
| PyObject* pyvalue = nullptr; | ||
| PyObject_GetOptionalAttr((PyObject*)pycmap, pyname, &pyvalue); | ||
| #else | ||
| PyObject* pyvalue = PyObject_GetAttr((PyObject*)pycmap, pyname); | ||
| if (!pyvalue) | ||
| PyErr_Clear(); | ||
| #endif | ||
| if (pyvalue) | ||
| return pyvalue; | ||
|
|
||
| std::string cname = PyUnicode_AsUTF8(pyname); | ||
|
|
||
| // typed conversion if provided | ||
| if (type == (PyObject*)&PyUnicode_Type) { | ||
| if (!coll) { | ||
beojan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| try { | ||
| auto const& cvalue = pycmap->ph_config->get<std::string>(cname); | ||
| pyvalue = PyUnicode_FromString(cvalue.c_str()); | ||
| } catch (...) { | ||
| PyErr_Format(PyExc_TypeError, "property \"%s\" is not a string", cname.c_str()); | ||
| } | ||
| } else { | ||
| try { | ||
| auto const& cvalue = pycmap->ph_config->get<std::vector<std::string>>(cname); | ||
| pyvalue = PyTuple_New(cvalue.size()); | ||
| for (Py_ssize_t i = 0; i < (Py_ssize_t)cvalue.size(); ++i) { | ||
| PyObject* item = PyUnicode_FromString(cvalue[i].c_str()); | ||
| PyTuple_SetItem(pyvalue, i, item); | ||
| } | ||
| } catch (...) { | ||
| PyErr_Format( | ||
| PyExc_TypeError, "property \"%s\" is not a collection of strings", cname.c_str()); | ||
| } | ||
| } | ||
| } else if (type == (PyObject*)&PyLong_Type) { | ||
| if (!coll) { | ||
| try { | ||
| auto const& cvalue = pycmap->ph_config->get<long>(cname); | ||
| pyvalue = PyLong_FromLong(cvalue); | ||
| } catch (...) { | ||
| PyErr_Format(PyExc_TypeError, "property \"%s\" is not an integer", cname.c_str()); | ||
| } | ||
| } else { | ||
| try { | ||
| auto const& cvalue = pycmap->ph_config->get<std::vector<std::string>>(cname); | ||
wlav marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| pyvalue = PyTuple_New(cvalue.size()); | ||
| for (Py_ssize_t i = 0; i < (Py_ssize_t)cvalue.size(); ++i) { | ||
| PyObject* item = PyLong_FromString(cvalue[i].c_str(), nullptr, 10); | ||
| PyTuple_SetItem(pyvalue, i, item); | ||
| } | ||
| } catch (...) { | ||
| PyErr_Format( | ||
| PyExc_TypeError, "property \"%s\" is not a collection of integers", cname.c_str()); | ||
| } | ||
| } | ||
| } else if (type) { | ||
wlav marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| PyErr_SetString(PyExc_TypeError, "requested type not supported"); | ||
| } | ||
|
|
||
| if (type) | ||
| return pyvalue; // may be nullptr | ||
|
|
||
| // untyped (guess) conversion | ||
wlav marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (!pyvalue) { | ||
| try { | ||
| auto const& cvalue = pycmap->ph_config->get<long>(cname); | ||
| pyvalue = PyLong_FromLong(cvalue); | ||
| } catch (std::runtime_error const&) { | ||
| } | ||
| } | ||
| if (!pyvalue) { | ||
| try { | ||
| auto const& cvalue = pycmap->ph_config->get<std::string>(cname); | ||
| pyvalue = PyUnicode_FromStringAndSize(cvalue.c_str(), cvalue.size()); | ||
| } catch (std::runtime_error const&) { | ||
| } | ||
| } | ||
|
|
||
| // cache if found | ||
|
|
||
| return pyvalue; | ||
| } | ||
|
|
||
| static PyMappingMethods pcm_as_mapping = {nullptr, (binaryfunc)pcm_subscript, nullptr}; | ||
|
|
||
| // clang-format off | ||
| PyTypeObject phlex::experimental::PhlexConfig_Type = { | ||
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | ||
| (char*) "pyphlex.configuration", // tp_name | ||
| sizeof(py_config_map), // tp_basicsize | ||
| 0, // tp_itemsize | ||
| 0, // tp_dealloc | ||
| 0, // tp_vectorcall_offset / tp_print | ||
| 0, // tp_getattr | ||
| 0, // tp_setattr | ||
| 0, // tp_as_async / tp_compare | ||
| 0, // tp_repr | ||
| 0, // tp_as_number | ||
| 0, // tp_as_sequence | ||
| &pcm_as_mapping, // tp_as_mapping | ||
| 0, // tp_hash | ||
| 0, // tp_call | ||
| 0, // tp_str | ||
| 0, // tp_getattro | ||
| 0, // tp_setattro | ||
| 0, // tp_as_buffer | ||
| Py_TPFLAGS_DEFAULT, // tp_flags | ||
| (char*)"phlex configuration object-as-dictionary", // tp_doc | ||
| 0, // tp_traverse | ||
| 0, // tp_clear | ||
| 0, // tp_richcompare | ||
| 0, // tp_weaklistoffset | ||
| 0, // tp_iter | ||
| 0, // tp_iternext | ||
| 0, // tp_methods | ||
| 0, // tp_members | ||
| 0, // tp_getset | ||
| 0, // tp_base | ||
| 0, // tp_dict | ||
| 0, // tp_descr_get | ||
| 0, // tp_descr_set | ||
| 0, // tp_dictoffset | ||
| 0, // tp_init | ||
| 0, // tp_alloc | ||
| 0, // tp_new | ||
| 0, // tp_free | ||
| 0, // tp_is_gc | ||
| 0, // tp_bases | ||
| 0, // tp_mro | ||
| 0, // tp_cache | ||
| 0, // tp_subclasses | ||
| 0 // tp_weaklist | ||
| #if PY_VERSION_HEX >= 0x02030000 | ||
| , 0 // tp_del | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x02060000 | ||
| , 0 // tp_version_tag | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x03040000 | ||
| , 0 // tp_finalize | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x03080000 | ||
| , 0 // tp_vectorcall | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x030c0000 | ||
| , 0 // tp_watched | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x030d0000 | ||
| , 0 // tp_versions_used | ||
| #endif | ||
| }; | ||
| // clang-format on | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| #include "wrap.hpp" | ||
|
|
||
| #include <exception> | ||
| #include <string> | ||
|
|
||
| using namespace phlex::experimental; | ||
|
|
||
| void phlex::experimental::throw_runtime_error_from_py_error(bool check_error) | ||
| { | ||
| PyGILRAII g; | ||
|
|
||
| if (check_error) { | ||
| if (!PyErr_Occurred()) | ||
| return; | ||
| } | ||
|
|
||
| std::string msg; | ||
|
|
||
| #if PY_VERSION_HEX < 0x30c000000 | ||
| PyObject *type = nullptr, *value = nullptr, *traceback = nullptr; | ||
| PyErr_Fetch(&type, &value, &traceback); | ||
| if (value) { | ||
| PyObject* pymsg = PyObject_Str(value); | ||
| msg = PyUnicode_AsUTF8(pymsg); | ||
| Py_DECREF(pymsg); | ||
| } else { | ||
| msg = "unknown Python error occurred"; | ||
| } | ||
| Py_XDECREF(traceback); | ||
| Py_XDECREF(value); | ||
| Py_XDECREF(type); | ||
| #else | ||
| PyObject* exc = PyErr_GetRaisedException(); | ||
| if (exc) { | ||
| PyObject* pymsg = PyObject_Str(exc); | ||
| msg = PyUnicode_AsString(pymsg); | ||
| Py_DECREF(pymsg); | ||
| Py_DECREF(exc); | ||
| } | ||
| #endif | ||
|
|
||
| throw std::runtime_error(msg.c_str()); | ||
wlav marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| #include <memory> | ||
| #include <string> | ||
|
|
||
| #include "wrap.hpp" | ||
|
|
||
| using namespace phlex::experimental; | ||
|
|
||
| static py_lifeline_t* ll_new(PyTypeObject* pytype, PyObject*, PyObject*) | ||
| { | ||
| py_lifeline_t* pyobj = (py_lifeline_t*)pytype->tp_alloc(pytype, 0); | ||
| if (!pyobj) | ||
| PyErr_Print(); | ||
| pyobj->m_view = nullptr; | ||
| new (&pyobj->m_source) std::shared_ptr<void>{}; | ||
|
|
||
| return pyobj; | ||
| } | ||
|
|
||
| static int ll_traverse(py_lifeline_t* pyobj, visitproc visit, void* args) | ||
| { | ||
| if (pyobj->m_view) | ||
| visit(pyobj->m_view, args); | ||
| return 0; | ||
| } | ||
|
|
||
| static int ll_clear(py_lifeline_t* pyobj) | ||
| { | ||
| Py_CLEAR(pyobj->m_view); | ||
| return 0; | ||
| } | ||
|
|
||
| static void ll_dealloc(py_lifeline_t* pyobj) | ||
| { | ||
| Py_CLEAR(pyobj->m_view); | ||
| typedef std::shared_ptr<void> generic_shared_t; | ||
| pyobj->m_source.~generic_shared_t(); | ||
| } | ||
|
|
||
| // clang-format off | ||
| PyTypeObject phlex::experimental::PhlexLifeline_Type = { | ||
| PyVarObject_HEAD_INIT(&PyType_Type, 0) | ||
| (char*) "pyphlex.lifeline", // tp_name | ||
| sizeof(py_lifeline_t), // tp_basicsize | ||
| 0, // tp_itemsize | ||
| (destructor)ll_dealloc, // tp_dealloc | ||
| 0, // tp_vectorcall_offset / tp_print | ||
| 0, // tp_getattr | ||
| 0, // tp_setattr | ||
| 0, // tp_as_async / tp_compare | ||
| 0, // tp_repr | ||
| 0, // tp_as_number | ||
| 0, // tp_as_sequence | ||
| 0, // tp_as_mapping | ||
| 0, // tp_hash | ||
| 0, // tp_call | ||
| 0, // tp_str | ||
| 0, // tp_getattro | ||
| 0, // tp_setattro | ||
| 0, // tp_as_buffer | ||
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // tp_flags | ||
| (char*)"internal", // tp_doc | ||
| (traverseproc)ll_traverse, // tp_traverse | ||
| (inquiry)ll_clear, // tp_clear | ||
| 0, // tp_richcompare | ||
| 0, // tp_weaklistoffset | ||
| 0, // tp_iter | ||
| 0, // tp_iternext | ||
| 0, // tp_methods | ||
| 0, // tp_members | ||
| 0, // tp_getset | ||
| 0, // tp_base | ||
| 0, // tp_dict | ||
| 0, // tp_descr_get | ||
| 0, // tp_descr_set | ||
| 0, // tp_dictoffset | ||
| 0, // tp_init | ||
| 0, // tp_alloc | ||
| (newfunc)ll_new, // tp_new | ||
| 0, // tp_free | ||
| 0, // tp_is_gc | ||
| 0, // tp_bases | ||
| 0, // tp_mro | ||
| 0, // tp_cache | ||
| 0, // tp_subclasses | ||
| 0 // tp_weaklist | ||
| #if PY_VERSION_HEX >= 0x02030000 | ||
| , 0 // tp_del | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x02060000 | ||
| , 0 // tp_version_tag | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x03040000 | ||
| , 0 // tp_finalize | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x03080000 | ||
| , 0 // tp_vectorcall | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x030c0000 | ||
| , 0 // tp_watched | ||
| #endif | ||
| #if PY_VERSION_HEX >= 0x030d0000 | ||
| , 0 // tp_versions_used | ||
| #endif | ||
| }; | ||
| // clang-format on |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.