Classdesc 3.44
pythonBuffer.h
1/*
2 @copyright Russell Standish 2024
3 @author Russell Standish
4 This file is part of Classdesc
5
6 Open source licensed under the MIT license. See LICENSE for details.
7*/
8
9/* A buffer implementation for marshalling function arguments from python onjects
10*/
11
12#ifndef CLASSDESC_PYTHON_BUFFER_H
13#define CLASSDESC_PYTHON_BUFFER_H
14
15#include <json_pack_base.h>
16#include <RESTProcess_base.h>
17#include "signature.h"
18#include <deque>
19#include <numeric>
20#ifdef MXE
21#include "pythonCAPI.h"
22#else
23#include <Python.h>
24#endif
25
26#define CLASSDESC_PY_EXCEPTION_ABSORB(ret) \
27 catch (const std::exception& ex) \
28 { \
29 PyErr_SetString(PyExc_RuntimeError, ex.what()); \
30 return ret; \
31 } \
32 catch (...) \
33 { \
34 PyErr_SetString(PyExc_RuntimeError, "Unknown exception"); \
35 return ret; \
36 }
37
38namespace classdesc
39{
40 // map of registries, one per module
41 inline std::map<std::string, RESTProcess_t*>& registries()
42 {
43 static std::map<std::string, RESTProcess_t*> registries;
44 return registries;
45 }
46
47 namespace
48 {
49 // per compile unit registry
50 RESTProcess_t registry;
51 PyObject* pythonModule=nullptr;
52 }
53
55 inline PyObject* newPyObject(const bool& x) {if (x) Py_RETURN_TRUE; Py_RETURN_FALSE;}
56 template <class T> inline typename enable_if<And<is_integral<T>, Not<is_same<T,bool>>>, PyObject*>::T
57 newPyObject(const T& x) {return PyLong_FromLong(x);}
58 template <class T> inline typename enable_if<is_floating_point<T>,PyObject*>::T
59 newPyObject(const T& x) {return PyFloat_FromDouble(x);}
60 inline PyObject* newPyObject(const string& x) {return PyUnicode_FromString(x.c_str());}
61 inline PyObject* newPyObject(const char* x) {return PyUnicode_FromString(x);}
62
63 // objects
64 inline PyObject* newPyObjectJson(const json5_parser::mValue& j) {
65 switch (j.type())
66 {
67 case json5_parser::obj_type:
68 {
69 auto pyObject=PyDict_New();
70 for (auto& i: j.get_obj())
71 PyDict_SetItemString(pyObject, i.first.c_str(), newPyObjectJson(i.second));
72 return pyObject;
73 }
74 case json5_parser::array_type:
75 {
76 auto arr=j.get_array();
77 auto pyObject=PyList_New(arr.size());
78 for (size_t i=0; i<arr.size(); ++i)
79 PyList_SetItem(pyObject, i, newPyObjectJson(arr[i]));
80 return pyObject;
81 }
82 case json5_parser::str_type:
83 return newPyObject(j.get_str());
84 case json5_parser::bool_type:
85 return newPyObject(j.get_bool());
86 case json5_parser::int_type:
87 return newPyObject(j.get_int64());
88 case json5_parser::real_type:
89 return newPyObject(j.get_real());
90 case json5_parser::null_type:
91 Py_RETURN_NONE;
92 }
93 assert(false); // silly compiler cannot figure out this is unreachable code
94 Py_RETURN_NONE;
95 }
96
97 inline PyObject* newPyObject(const json_pack_t& j) {return newPyObjectJson(j);}
98
100 template <class T> inline typename enable_if<is_sequence<T>, PyObject*>::T
101 newPyObject(const T& x) {
102 auto pyObject=PyList_New(x.size());
103 for (size_t i=0; i<x.size(); ++i)
104 PyList_SetItem(pyObject, i, newPyObject(x[i]));
105 return pyObject;
106 }
107
108 class PythonBuffer;
109 inline PyObject* newPyObject(const PythonBuffer& x);
110
111 template <class T> inline typename enable_if<
112 And<
113 Not<is_arithmetic<T>>,
114 Not<is_sequence<T>>
115 >, PyObject*>::T
116 newPyObject(const T& x) {
117 json_pack_t tmp; // go via JSON serialisation for arbitrary objects
118 tmp<<x;
119 return newPyObjectJson(tmp);
120 }
122
123 // container for handling PyObject lifetimes in an RAII fashion
124 class PyObjectRef
125 {
126 PyObject* ref=nullptr;
127 public:
128 PyObjectRef()=default;
129 PyObjectRef(PyObject* ref): ref(ref) {
130 assert(!ref || Py_REFCNT(ref));
131 }
132 ~PyObjectRef() {Py_XDECREF(ref);}
133 PyObjectRef(const PyObjectRef& x)=delete;
134 PyObjectRef& operator=(const PyObjectRef& x)=delete;
135 PyObjectRef(PyObjectRef&& x): ref(x.ref) {
136 x.ref=nullptr;
137 assert(!ref || Py_REFCNT(ref));
138 }
139 PyObjectRef& operator=(PyObjectRef&& x) {
140 Py_XDECREF(ref);
141 ref=x.ref;
142 assert(!ref || Py_REFCNT(ref));
143 x.ref=nullptr;
144 return *this;
145 }
146 operator PyObject*() const {return ref;}
147 operator bool() const {return ref;}
148 PyObject* release() {auto tmp=ref; ref=nullptr; return tmp;}
149 };
150
151 class PythonBuffer
152 {
153 public:
154 using Array=std::deque<PythonBuffer>;
155 PythonBuffer() {Py_INCREF(pyObject);}
156
157 explicit PythonBuffer(RESTProcessType::Type type) {
158 switch (type)
159 {
160 case RESTProcessType::null: pyObject=Py_None; Py_INCREF(pyObject); return;
161 case RESTProcessType::boolean: pyObject=Py_False; Py_INCREF(pyObject); return;
162 case RESTProcessType::int_number: pyObject=PyLong_FromLong(0); return;
163 case RESTProcessType::float_number: pyObject=PyFloat_FromDouble(0.0); return;
164 case RESTProcessType::string: pyObject=PyUnicode_FromString(""); return;
165 case RESTProcessType::object: pyObject=PyDict_New(); return;
166 case RESTProcessType::array: pyObject=PyList_New(0); return;
167 }
168 }
169 template <class T>
170 explicit PythonBuffer(const T& x): pyObject(newPyObject(x)) {}
171 explicit PythonBuffer(const json_pack_t& x): pyObject(newPyObjectJson(x)) {}
172 explicit PythonBuffer(const json5_parser::mArray& x): pyObject(newPyObjectJson(json_pack_t(x))) {}
173 explicit PythonBuffer(PyObject* x) {
174 if (x)
175 pyObject=x;
176 else
177 pyObject=Py_None;
178 Py_XINCREF(x);
179 }
180
181 ~PythonBuffer() {Py_DECREF(pyObject);}
182
183 PythonBuffer(const PythonBuffer& x): pyObject(x.pyObject) {Py_INCREF(pyObject);}
184 PythonBuffer& operator=(const PythonBuffer& x)
185 {pyObject=x.pyObject; Py_INCREF(pyObject); return *this;}
186
188 Array array() const {
189 Array r;
190 if (PySequence_Check(pyObject))
191 for (Py_ssize_t i=0; i<PySequence_Size(pyObject); ++i)
192 r.push_back(PythonBuffer(PySequence_GetItem(pyObject,i)));
193 else
194 r.push_back(*this);
195 return r;
196 }
197 RESTProcessType::Type type() const {
198 if (pyObject==Py_None) return RESTProcessType::null;
199 if (PyBool_Check(pyObject)) return RESTProcessType::boolean;
200 if (PyLong_Check(pyObject)) return RESTProcessType::int_number;
201 if (PyFloat_Check(pyObject)) return RESTProcessType::float_number;
202 if (PyUnicode_Check(pyObject)) return RESTProcessType::string;
203 if (PySequence_Check(pyObject)) return RESTProcessType::array;
204 return RESTProcessType::object;
205 }
206
207 //template <class T> T get() const;
208 template <class T> typename enable_if<is_integral<T>, T>::T
209 get() const {return PyLong_AsLongLong(pyObject);}
210 template <class T> typename enable_if<is_floating_point<T>,T>::T
211 get() const {return PyFloat_AsDouble(pyObject);}
212 // template <> bool get<bool>() const {return PyLong_AsLong(pyObject);}
213 template <class T> typename enable_if<is_string<T>, T>::T
214 get() const {return PyUnicode_AsUTF8AndSize(pyObject,nullptr);}
215
216 template <class T> typename enable_if<is_same<T,json_pack_t>, T>::T
217 get() const {
218 switch (type())
219 {
220 case RESTProcessType::null:
221 return json_pack_t(json5_parser::mValue::null);
222 case RESTProcessType::boolean:
223 return json_pack_t(get<bool>());
224 case RESTProcessType::int_number:
225 return json_pack_t(get<int64_t>());
226 case RESTProcessType::float_number:
227 return json_pack_t(get<double>());
228 case RESTProcessType::string:
229 return json_pack_t(get<string>());
230 case RESTProcessType::array:
231 {
232 json_pack_t::Array arr;
233 for (auto& i: array())
234 arr.push_back(i.get<json_pack_t>());
235 return json_pack_t(arr);
236 }
237 case RESTProcessType::object:
238 {
239 json_pack_t::Object obj;
240 if (PyMapping_Check(pyObject))
241 {
242 PyObjectRef keyValues(PyMapping_Items(pyObject));
243 for (Py_ssize_t i=0; i<PySequence_Size(keyValues); ++i)
244 {
245 PyObjectRef keyValue=PySequence_GetItem(keyValues, i);
246 PyObjectRef keyRef(PyObject_Str(PySequence_GetItem(keyValue,0)));
247 string keyStr=PyUnicode_AsUTF8AndSize(keyRef,nullptr);
248 obj[keyStr]=PythonBuffer(PySequence_GetItem(keyValue,1)).get<json_pack_t>();
249 }
250 }
251 else
252 {
253 auto dir(PyObject_Dir(pyObject));
254 for (Py_ssize_t i=0; dir && i<PySequence_Size(dir); ++i)
255 {
256 auto key=PySequence_GetItem(dir, i);
257 PyObjectRef keyRef(PyObject_Str(key));
258 string keyStr=PyUnicode_AsUTF8AndSize(keyRef,nullptr);
259 obj[keyStr]=PythonBuffer(PyObject_GetAttr(pyObject, key)).get<json_pack_t>();
260 }
261 }
262 return json_pack_t(obj);
263 }
264 }
265 assert(false); // silly compiler cannot figure out this is unreachable code
266 return json_pack_t();
267 }
268
269 template <class T> void push_back(const T& x) {
270 if (PyList_Check(pyObject))
271 {
272 PythonBuffer pb(x);
273 if (PyList_Append(pyObject, pb.pyObject))
274 Py_INCREF(pb.pyObject); // TODO - does PySequence_SetItem already do this?
275 }
276 }
277
279 PyObjectRef getPyObject() const {Py_INCREF(pyObject); return pyObject;}
280
281 std::string str() const {
282 PyObjectRef pyStr(PyObject_Str(pyObject));
283 return PyUnicode_AsUTF8AndSize(pyStr,nullptr);
284 }
285 private:
286 PyObject* pyObject=Py_None; // note - this needs to be INCREF'd in constructors, not immortal before 3.12
287
288
289 };
290
291 inline PyObject* newPyObject(const PythonBuffer& x) {return x.getPyObject();}
292
293 inline const PythonBuffer& operator>>(const PythonBuffer& b, char& x)
294 {x=b.get<string>()[0]; return b;}
295
296 inline PythonBuffer& operator<<(PythonBuffer& b, const char& x)
297 {b=PythonBuffer(string{x}); return b;}
298
299 // numbers
300 template <class T>
301 inline typename enable_if<
303 const PythonBuffer&>::T
304 operator>>(const PythonBuffer& b, T& x)
305 {x=b.get<T>(); return b;}
306
307 template <class T>
308 inline typename enable_if<
309 is_arithmetic<T>, PythonBuffer&>::T
310 operator<<(PythonBuffer& b, const T& x)
311 {b=PythonBuffer(x); return b;}
312
313 // strings
314 template <class T>
316 operator>>(const PythonBuffer& b, T& x)
317 {x=b.get<string>(); return b;}
318
319 template <class T>
320 inline typename enable_if<And<is_string<T>>, PythonBuffer&>::T
321 operator<<(PythonBuffer& b, const T& x)
322 {b=PythonBuffer(string(x)); return b;}
323
324 // enums
325 template <class T>
326 inline typename enable_if<And<is_enum<T>,Not<is_const<T>>>, const PythonBuffer&>::T
327 operator>>(const PythonBuffer& b, T& x)
328 {auto tmp=b.get<string>(); x=enum_keys<T>()(tmp); return b;}
329
330 template <class T>
331 inline typename enable_if<And<is_enum<T>>, PythonBuffer&>::T
332 operator<<(PythonBuffer& b, const T& x)
333 {b=PythonBuffer(to_string(x)); return b;}
334
335 // sequences
336 template <class T>
337 inline typename enable_if<And<is_sequence<T>,Not<is_const<T>>>, const PythonBuffer&>::T
338 operator>>(const PythonBuffer& b, T& x)
339 {
340 resize(x,0);
341 if (b.type()!=RESTProcessType::array) return b;
342 auto arr=b.array();
343 for (auto& i: arr)
344 {
345 typename T::value_type e;
346 i>>e;
347 push_back(x,e);
348 }
349 return b;
350 }
351
352 template <class T>
353 inline typename enable_if<is_sequence<T>, PythonBuffer&>::T
354 operator<<(PythonBuffer& b, const T& x)
355 {
356 b=PythonBuffer(RESTProcessType::array);
357 for (auto& i: x)
358 {
359 b.push_back(i);
360 }
361 return b;
362 }
363
364 // const
365 template <class T>
366 inline typename enable_if<is_const<T>, const PythonBuffer&>::T
367 operator>>(const PythonBuffer& b, T& x)
368 {return b;}
369
370
371 inline const PythonBuffer& operator>>(const PythonBuffer& b, const char* x)
372 {
373 throw std::runtime_error("cannot unpack to char*, please use string instead");
374 }
375
376 // everything else
377 template <class T>
378 inline typename enable_if<
379 And<
385 >, const PythonBuffer&>::T
386 operator>>(const PythonBuffer& b, T& x)
387 {
388 b.get<json_pack_t>()>>x;
389 return b;
390 }
391
392 template <class T>
393 inline typename enable_if<
394 And<
399 >, PythonBuffer&>::T
400 operator<<(PythonBuffer& b, const T& x)
401 {
402 json_pack_t tmp; tmp<<x;
403 string s=write(tmp);
404 b=PythonBuffer(s);
405 return b;
406 }
407
409 struct CppPyObject: public PyObject
410 {
411 CppPyObject() {memset(this,0,sizeof(PyObject));}
412 };
413
414
415 struct CppWrapper;
416 void attachRegistryObjects(const RESTProcess_t& registry, CppWrapper& object,const std::string& prefix);
417
418 struct CppWrapper: public CppPyObject
419 {
420 const bool special; // if true, command takes a key as an argument
421 RPPtr command;
422 std::map<string, PyObjectRef> methods;
423 static CppWrapper* create(const RPPtr& command, bool special) {return new CppWrapper(command,special);}
424 CppWrapper(CppWrapper&&)=default;
425
426 static PyObject* returnResult(const RPPtr& result)
427 {
428 PythonBuffer resultBuffer(result->asBuffer());
429 auto pyResult=resultBuffer.getPyObject();
430 switch (resultBuffer.type())
431 {
432 case RESTProcessType::object:
433 case RESTProcessType::array:
434 {
435 auto r=CppWrapper::create(result, false);
436 PyObjectRef ref(r);
437 attachRegistryObjects(result->list(),*r,".");
438 return ref.release();
439 }
440 default: break;
441 }
442 if (PyErr_Occurred())
443 PyErr_Print();
444 return pyResult.release();
445 }
446
447 static PyObject* list(CppWrapper* self, PyObject*)
448 try
449 {
450 auto methods=self->command->list();
451 std::vector<string> methodList;
452 for (auto& i: methods)
453 if (!i.first.empty()) // don't include top level object
454 methodList.push_back(i.first);
455 return PythonBuffer(methodList).getPyObject().release();
456 }
457 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
458
459 static PyObject* properties(CppWrapper* self, PyObject*)
460 try
461 {
462 return PythonBuffer(self->command->asBuffer()).getPyObject().release();
463 }
464 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
465
466 static PyObject* signature(CppWrapper* self, PyObject*)
467 try
468 {
469 return PythonBuffer(self->command->signature()).getPyObject().release();
470 }
471 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
472
473 static PyObject* type(CppWrapper* self, PyObject*)
474 try
475 {
476 return PythonBuffer(self->command->type()).getPyObject().release();
477 }
478 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
479
480 static PyObject* insert(CppWrapper* self, PyObject* value)
481 try
482 {
483 self->command->insert(PythonBuffer(value).get<json_pack_t>());
484 return Py_None;
485 }
486 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
487
488 static PyObject* erase(CppWrapper* self, PyObject* key)
489 try
490 {
491 self->command->erase(PythonBuffer(key).get<json_pack_t>());
492 return Py_None;
493 }
494 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
495
496 static PyObject* contains(CppWrapper* self, PyObject* key)
497 try
498 {
499 return PythonBuffer(self->command->contains(PythonBuffer(key).get<json_pack_t>()))
500 .getPyObject().release();
501 }
502 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
503
504 static PyObject* keys(CppWrapper* self, PyObject*)
505 try
506 {
507 return PythonBuffer(self->command->keys()->asBuffer()).getPyObject().release();
508 }
509 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
510
511 private:
512 CppWrapper(const RPPtr& command,bool special); // private to force creation on heap
513 CppWrapper(const CppWrapper&)=delete;
514 void operator=(const CppWrapper&)=delete;
515 };
516
517 static PyMethodDef cppWrapperMethods[]={
518 {"_list",(PyCFunction)CppWrapper::list,METH_NOARGS,"List of members/methods of this object"},
519 {"_properties",(PyCFunction)CppWrapper::properties,METH_NOARGS,"Return current state of this object"},
520 {"_signature",(PyCFunction)CppWrapper::signature,METH_NOARGS,"Signatures of all method overloads"},
521 {"_type",(PyCFunction)CppWrapper::type,METH_NOARGS,"Object type"},
522 {"insert",(PyCFunction)CppWrapper::insert,METH_O,"Insert element into container"},
523 {"erase",(PyCFunction)CppWrapper::erase,METH_O,"Remove element from container"},
524 {"keys",(PyCFunction)CppWrapper::keys,METH_NOARGS,"List keys of a map/set"},
525 {"contains",(PyCFunction)CppWrapper::contains,METH_O,"Retrun true/false whether map/set contains a key"},
526 {nullptr, nullptr, 0, nullptr}
527 };
528
529 struct CppWrapperType: public PyTypeObject
530 {
531 // container commands that take a key as as an argument
532 static bool containerSpecialCommand(const std::string& command)
533 {
534 static const std::set<std::string> specialCommands{".@elem",".@elemNoThrow"};
535 auto n=command.rfind('.');
536 return n!=string::npos && specialCommands.count(command.substr(n));
537 }
538
539 static PyObject* call(PyObject* self, PyObject* args, PyObject *kwargs)
540 {
541 auto cppWrapper=static_cast<CppWrapper*>(self);
542 auto command=cppWrapper->command;
543 if (command->isObject() && PySequence_Size(args)==0)
544 return CppWrapper::properties(cppWrapper,nullptr);
545 PythonBuffer arguments(PySequence_Size(args)? RESTProcessType::array: RESTProcessType::null);
546 std::string remainder;
547 if (cppWrapper->special && PySequence_Size(args))
548 // handle special commands which embed the argument in the path string
549 remainder=write(PythonBuffer(PySequence_GetItem(args,0)).get<json_pack_t>());
550 else
551 for (Py_ssize_t i=0; i<PySequence_Size(args); ++i)
552 arguments.push_back(PySequence_GetItem(args,i));
553 if (PyErr_Occurred())
554 PyErr_Print();
555 try
556 {
557 return CppWrapper::returnResult(command->process(remainder,arguments.get<json_pack_t>()));
558 }
559 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
560 }
561
562 static void deleteCppWrapper(PyObject* x) {
563 delete static_cast<CppWrapper*>(x);
564 }
565
566 static PyObject* getAttro(PyObject* self, PyObject* attr)
567 try
568 {
569 auto cppWrapper=static_cast<CppWrapper*>(self);
570 auto i=cppWrapper->methods.find(PyUnicode_AsUTF8AndSize(attr,nullptr));
571 if (i!=cppWrapper->methods.end())
572 {
573 Py_INCREF(i->second);
574 return i->second;
575 }
576 else
577 {
578 auto methods=cppWrapper->command->list();
579 auto attribute=methods.find(string(".")+PyUnicode_AsUTF8AndSize(attr,nullptr));
580 if (attribute!=methods.end())
581 return CppWrapper::create(attribute->second, false);
582 }
583 return PyObject_GenericGetAttr(self,attr);
584 }
585 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
586
587 static int setAttro(PyObject* self, PyObject* name, PyObject* attr)
588 {
589 auto cppWrapper=static_cast<CppWrapper*>(self);
590 auto key=PyUnicode_AsUTF8AndSize(name,nullptr);
591 if (attr)
592 {
593 cppWrapper->methods[key]=PyObjectRef(attr);
594 assert(Py_REFCNT(cppWrapper->methods[key]));
595 }
596 else
597 cppWrapper->methods.erase(key);
598 return 0;
599 }
600
601 struct MappingMethods: PyMappingMethods
602 {
603 static Py_ssize_t size(PyObject* self)
604 {
605 try
606 {
607 auto cppWrapper=static_cast<CppWrapper*>(self);
608 return cppWrapper->command->size();
609 }
610 CLASSDESC_PY_EXCEPTION_ABSORB(0);
611 }
612
613 static PyObject* getElem(PyObject* self, PyObject* key)
614 {
615 auto cppWrapper=static_cast<CppWrapper*>(self);
616 try
617 {
618 return CppWrapper::returnResult(cppWrapper->command->getElem(PythonBuffer(key).get<json_pack_t>()));
619 }
620 CLASSDESC_PY_EXCEPTION_ABSORB(nullptr);
621 }
622
623 static int setElem(PyObject* self, PyObject* key, PyObject* val)
624 {
625 auto cppWrapper=static_cast<CppWrapper*>(self);
626 try
627 {
628 cppWrapper->command->setElem(PythonBuffer(key).get<json_pack_t>(),
629 PythonBuffer(val).get<json_pack_t>());
630 return 0;
631 }
632 catch (const std::exception& ex)
633 {
634 PyErr_SetString(PyExc_RuntimeError, ex.what());
635 return -1;
636 }
637 }
638 MappingMethods() {
639 memset(this,0,sizeof(PyMappingMethods));
640 mp_length=size; // support for len
641 mp_subscript=getElem; // support for []
642 mp_ass_subscript=setElem; // support for []=
643 }
644 } mappingMethods;
645
646 CppWrapperType()
647 {
648 memset(this,0,sizeof(PyTypeObject));
649 Py_INCREF(this);
650 tp_name="CppWrapperType";
651 tp_methods=cppWrapperMethods;
652 tp_call=call;
653 tp_basicsize=sizeof(CppWrapper);
654 tp_dealloc=deleteCppWrapper;
655 tp_getattro=getAttro;
656 tp_setattro=setAttro;
657 tp_as_mapping=&mappingMethods;
658 PyType_Ready(this);
659 }
660 };
661
662 inline CppWrapperType& CppWrapperTypeSingleton() {
663 static CppWrapperType cppWrapperType;
664 return cppWrapperType;
665 }
666
667 inline CppWrapper::CppWrapper(const RPPtr& command, bool special): special(special), command(command) {
668 ob_refcnt=1;
669 ob_type=&CppWrapperTypeSingleton();
670 }
671
672 inline void attachRegistryObjects(const RESTProcess_t& registry, CppWrapper& object,const std::string& prefix)
673 {
674 for (auto& i: registry)
675 {
676 std::string_view key(i.first);
677 if (i.second && key.substr(0,prefix.length())==prefix)
678 {
679 std::string method(key.substr(prefix.length()));
680 if (method.find('.')!=std::string::npos) continue;
681 bool special=method[0]=='@';
682 auto methodObject=CppWrapper::create(i.second, special);
683 attachRegistryObjects(registry, *methodObject, i.first+".");
684 if (special) method[0]='_';
685 PyObject_SetAttrString(&object,method.c_str(),methodObject);
686 }
687 }
688 }
689
690 template <class F>
691 struct RESTProcessFactory: public RESTProcessFunction<F>
692 {
693 RESTProcessFactory(F f): RESTProcessFunction<F>(f) {}
694 RPPtr process(const string& remainder, const REST_PROCESS_BUFFER& arguments) override
695 {
696 JSONBuffer argBuf(arguments);
697 // f is expected to return a unique_ptr
698 return makeRESTProcessHeapObject
699 (std::move(functional::callOnBuffer(argBuf,RESTProcessFunction<F>::f)));
700 }
701 };
702
704 template <class T, class... Args> struct DeclareType
705 {
706 static std::unique_ptr<T> factory(Args... args)
707 {return std::make_unique<T>(std::forward<Args>(args)...);}
708 DeclareType(const string& typeName) {
709 registry.add(typeName, new RESTProcessFactory<decltype(&factory)>(factory));
710 // registry.addFactory<T,Args...>(typeName,[](const std::string& name){
711 // auto object=std::make_shared<RESTProcessValueObject<T>>();
712 // auto cppObject=CppWrapper::create(object,false);
713 // PyObjectRef pyObject(cppObject);
714 // attachRegistryObjects(object->list(), *cppObject, ".");
715 // PyModule_AddObject(pythonModule, name.c_str(), pyObject.release());
716 // });
717 }
718 };
719
720 inline void initModule(PyObject* pythonModule, RESTProcess_t& registry)
721 {
722 assert(pythonModule);
723 for (auto& i: registry)
724 { // loop over toplevel registry objects
725 if (!i.second || i.first.empty() || i.first.find('.')!=std::string::npos || i.first[0]=='@') continue;
726 auto pyObject=CppWrapper::create(i.second, false);
727 attachRegistryObjects(registry, *pyObject, i.first+".");
728 PyModule_AddObject(pythonModule, i.first.c_str(), pyObject);
729 }
730
731 // enum reflection
732 PyObjectRef enummer=PyDict_New();
733 auto enumList=registry.process("@enum.@list",{})->asBuffer();
734 for (auto& i: enumList.array())
735 {
736 string name=i.get_str();
737 PyDict_SetItemString(enummer, name.c_str(),
738 newPyObjectJson(registry.process("@enum."+name,{})->asBuffer()));
739 }
740 PyModule_AddObject(pythonModule, "enum", enummer.release());
741 }
742
743 inline PyObject* registerObject(PyObject* self, PyObject* args) {
744 auto modName=PyModule_GetName(self);
745 if (!modName || !registries().count(modName)) {
746 PyErr_SetString(PyExc_RuntimeError, "Invalid module name");
747 return nullptr;
748 }
749 if (PySequence_Size(args)<2) {
750 PyErr_SetString(PyExc_RuntimeError, "Missing arguments");
751 return nullptr;
752 }
753 auto object=PySequence_GetItem(args,0);
754 if (!object || Py_TYPE(object)!=&CppWrapperTypeSingleton()) {
755 PyErr_SetString(PyExc_RuntimeError, "First argument not a CppWrapper");
756 return nullptr;
757 }
758 auto name=PyUnicode_AsUTF8AndSize(PySequence_GetItem(args,1),nullptr);
759 if (!name) {
760 PyErr_SetString(PyExc_RuntimeError, "Second argument not a string");
761 return nullptr;
762 }
763 (*registries()[modName])[name]=static_cast<CppWrapper*>(object)->command;
764 if (PyModule_AddObject(self,name,object)==0)
765 Py_INCREF(object);
766 return Py_None;
767 }
768}
769
770
771namespace classdesc_access
772{
773 template <>
774 struct access_json_pack<classdesc::PythonBuffer> {
775 template <class U>
776 void operator()(classdesc::json_pack_t& j, const std::string&, U& x) {
777 j<<x;
778 }
779 };
780}
781
782namespace {
783 PyMethodDef defaultMethods[]={
784 {"register",classdesc::registerObject,METH_VARARGS,"Register a C++ wrapped object with the registry"},
785 {nullptr,nullptr,0,nullptr}
786 };
787}
788
792#define CLASSDESC_PYTHON_MODULE(name) \
793 PyMODINIT_FUNC PyInit_##name() \
794 { \
795 static PyModuleDef module_##name = { \
796 PyModuleDef_HEAD_INIT, \
797 #name, \
798 "Python interface to C++ code: "#name, \
799 -1, \
800 defaultMethods, \
801 nullptr, \
802 nullptr, \
803 nullptr, \
804 nullptr \
805 }; \
806 using namespace classdesc; \
807 registries()[#name]=&registry; \
808 pythonModule=PyModule_Create(&module_##name); \
809 if (PyErr_Occurred()) PyErr_Print(); \
810 if (pythonModule) initModule(pythonModule,registry); \
811 return pythonModule; \
812 }
813
817#define CLASSDESC_ADD_GLOBAL(object) \
818 static int add_global_##object=(classdesc::RESTProcess(classdesc::registry,#object,object), 0);
819
820#define CLASSDESC_ADD_FUNCTION(object) \
821 static int add_global_##object=(classdesc::RESTProcess(classdesc::registry,#object,&object), 0);
822
823
829#define CLASSDESC_DECLARE_TYPE(type,...) \
830 static classdesc::DeclareType<type __VA_OPT__(,) __VA_ARGS__> declareType_##type(#type);
831
832#endif
Definition RESTProcess_base.h:1127
Definition pythonBuffer.h:125
Definition pythonBuffer.h:152
PyObjectRef getPyObject() const
returns a copy of the controlled object. Reference counter is incremented.
Definition pythonBuffer.h:279
Array array() const
return type conforms to the sequence concept
Definition pythonBuffer.h:188
Definition json_pack_base.h:99
Definition ref.h:28
Contains definitions related to classdesc functionality.
PyObject * newPyObject(const bool &x)
utility python object constructors
Definition pythonBuffer.h:55
Definition pythonCAPI.h:233
Definition pythonCAPI.h:225
Definition pythonCAPI.h:97
Definition pythonCAPI.h:143
Definition classdesc.h:420
Definition RESTProcess_epilogue.h:159
Definition pythonBuffer.h:419
Definition classdesc.h:405
Definition pythonBuffer.h:692
RPPtr process(const string &remainder, const REST_PROCESS_BUFFER &arguments) override
Definition pythonBuffer.h:694
REST processor registry.
Definition RESTProcess_base.h:351
controlled template specialisation: stolen from boost::enable_if.
Definition classdesc.h:282
Definition json_pack_epilogue.h:92