-
Notifications
You must be signed in to change notification settings - Fork 9
Proof-of-concept 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
base: main
Are you sure you want to change the base?
Changes from 42 commits
a198f3d
947f842
4541abd
1a86bec
1d2936a
61e895d
7fcb88b
6343575
86f5b30
0e5c8f9
be99c00
10b8af1
bbf3d1b
136da63
c4a80c9
135df83
7ff5946
6554826
10e8040
f108c2d
08c1ec4
1aa6a57
a3075a7
21cf613
5173c8c
6714ef0
59c13ae
242c856
993d42d
43a090e
f6586e3
bbab231
0d16fea
5e36597
818051c
6360b8f
c214ec6
9ae87a9
4d0a5a4
7bcb611
93161f5
c4a8651
73fc25e
c80ce98
3d04386
6731f34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
wlav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // 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.
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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Presumably the issue is elsewhere, but why is this read from the configuration as a list of strings and converted to a list of integers, instead of being read as a list of integers to begin with?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copy&paste error, probably. Will fix. |
||
| 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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You seem to have forgotten floats.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The configuration interface isn't finalized; I was trying to get away with the absolute minimum possible. Can add floats (doubles), though, as it's likely someone will try it with the prototype. |
||
| PyErr_SetString(PyExc_TypeError, "requested type not supported"); | ||
| } | ||
|
|
||
| if (type) | ||
| return pyvalue; // may be nullptr | ||
|
|
||
| // untyped (guess) conversion | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we introspect the value (using this, or the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wish. I asked for this. The |
||
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't look like it
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, need to add. |
||
|
|
||
| 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 | ||
| 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()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to have the Python exception type (and maybe even the traceback, if posible) available in the C++ exception.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that can be done. |
||
| } | ||
| 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 |
Uh oh!
There was an error while loading. Please reload this page.