Skip to content

Commit 357999e

Browse files
Added type factories to pythonBuffer
1 parent 881a6ae commit 357999e

File tree

7 files changed

+111
-37
lines changed

7 files changed

+111
-37
lines changed

RESTProcessExample/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.SUFFIXES: .c .cc .o .d .h .cd
22
OBJS=RESTProcessExample.o SimpleBufferExample.o pyExample.o
3-
CFLAGS=-g -I.. -I. -I../json5_parser/json5_parser -I/usr/local/include -fPIC -DUSE_UNROLLED -pthread
3+
CFLAGS=-g -I.. -I. -I../json5_parser/json5_parser -I/usr/local/include -fPIC -pthread
44
LIBS+=-L/usr/local/lib64 -lboost_thread -lpthread
55
VPATH=..
66
EXES=RESTProcess SimpleBuffer pyExample.so

RESTProcessExample/RESTProcessExpected.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@
123123
/root/bar1/foo/=>Exception: Command not found: .
124124
/root/bar1/foo/=>Exception: Command not found: .
125125
/root/bar1/sfoop=>null
126-
/root/bar1/recursiveType=>Exception: cannot unpack to char*, please use string instead
126+
/root/bar1/recursiveType=>{"barfoo":"ec","f":{"a":0.1,"af":0.2000000029802322,"b":3,"bf":false,"bt":true,"c":"\r hello & 123 ","c1":["\r","\r"],"ch":"M","d":[0,1,2],"d1":[[0,1],[2,3],[4,5]],"ef":"ea","h":[2,2,2],"l":[2,2,2],"llex":[["hello","hello"],["hello","hello"]],"m":[{"first":0,"second":5},{"first":3,"second":2}],"m_rotation":0,"sef":"ea","sm":{},"ss":[],"um":[],"vs":[" hello"," hello"]},"fp":{"a":0,"af":0,"b":0,"bf":false,"bt":false,"c":"","c1":[],"ch":"\u0000","d":[0,0,0],"d1":[[0,0],[0,0],[0,0]],"ef":"ea","h":[],"l":[],"llex":[],"m":[],"m_rotation":0,"sef":{},"sm":{},"ss":[],"um":[],"vs":[]},"g":2,"vFoo":[{"a":0.1,"af":0.2000000029802322,"b":1,"bf":false,"bt":true,"c":"\r hello & 123 ","c1":["\r","\r"],"ch":"M","d":[0,1,2],"d1":[[0,1],[2,3],[4,5]],"ef":"ea","h":[2,2,2],"l":[2,2,2],"llex":[["hello","hello"],["hello","hello"]],"m":[{"first":0,"second":5},{"first":3,"second":2}],"m_rotation":0,"sef":"ea","sm":{},"ss":[],"um":[],"vs":[" hello"," hello"]},{"a":0.1,"af":0.2000000029802322,"b":1,"bf":false,"bt":true,"c":"\r hello & 123 ","c1":["\r","\r"],"ch":"M","d":[0,1,2],"d1":[[0,1],[2,3],[4,5]],"ef":"ea","h":[2,2,2],"l":[2,2,2],"llex":[["hello","hello"],["hello","hello"]],"m":[{"first":0,"second":5},{"first":3,"second":2}],"m_rotation":0,"sef":"ea","sm":{},"ss":[],"um":[],"vs":[" hello"," hello"]}]}
127127
/root/defaultless/foo=>0
128128
/root/defaultless/bar=>2
129129
/root/getFB1=>{"f":{"a":0.1,"af":0.2000000029802322,"b":0,"bf":false,"bt":true,"c":"\r hello & 123 ","c1":["\r","\r"],"ch":"M","d":[0,1,2],"d1":[[0,1],[2,3],[4,5]],"ef":"ea","h":[2,2,2],"l":[2,2,2],"llex":[["hello","hello"],["hello","hello"]],"m":[{"first":0,"second":5},{"first":3,"second":2}],"m_rotation":0,"sef":"ea","sm":{},"ss":[],"um":[],"vs":[" hello"," hello"]}}
@@ -143,7 +143,7 @@
143143
/root/bar/rotation=>0
144144
/root/bar/rotation=>20
145145
/root/bar/rotation=>20
146-
/root/testString=>Exception: cannot unpack to char*, please use string instead
146+
/root/testString=>"hello"
147147
command doesn't starts with /
148148
/root/testDoubleIntOverload=>0
149149
/root/testDoubleIntOverload=>1

RESTProcessExample/example.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def expectThrow(f):
171171

172172
assert root.bar1.sfoop()==None
173173

174-
expectThrow(lambda: root.bar1.recursiveType('hello'))
174+
assert root.bar1.recursiveType('hello')._properties['f']['b']==3
175175

176176
assert root.defaultless.foo()==0
177177
assert root.defaultless.bar()==2
@@ -199,11 +199,19 @@ def expectThrow(f):
199199
assert root.bar.rotation()==20
200200

201201

202-
expectThrow(lambda: root.testString('hello'))
202+
assert root.testString('hello')=='hello'
203203

204204
assert root.testDoubleIntOverload(1)==0
205205
assert root.testDoubleIntOverload(1,1)==1
206206
assert root.testDoubleIntOverload(1,1.1)==1
207207
expectThrow(lambda: root.testDoubleIntOverload(1.1,1.1))
208208
expectThrow(lambda: root.dummy({a:1}))
209209
assert root.voidReturn()==None
210+
211+
# test type factories
212+
from pyExample import *
213+
Foo("foo",2)
214+
Root("root2")
215+
from pyExample import *
216+
assert foo.b()==2
217+
assert root2.bar.b()==3

RESTProcessExample/pyExample.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,9 @@ Root root;
66
const int Foo::csi;
77
int Foo::si;
88

9-
CLASSDESC_PYTHON_MODULE(pyExample,root)
9+
10+
CLASSDESC_ADD_GLOBAL(root);
11+
CLASSDESC_DECLARE_TYPE(Foo,int);
12+
CLASSDESC_DECLARE_TYPE(Root);
13+
CLASSDESC_PYTHON_MODULE(pyExample);
14+

RESTProcess_base.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,23 @@ namespace classdesc
244244

245245
REST_PROCESS_BUFFER process(const std::string& query, const REST_PROCESS_BUFFER& jin);
246246

247-
/// define all arguments of \a F
247+
/// define all arguments of \a F
248248
template <class F> void defineFunctionArgTypes()
249249
{
250250
DefineFunctionArgTypes<F,functional::Arity<F>::value>::define(*this);
251251
}
252+
253+
/// adds a factory function for creating objects of type \a
254+
/// T.
255+
/// @tparam T type of objects this factory will create
256+
/// @tparam Args are the types of arguments to be passed to T's constructor
257+
/// @param typeName alias for the factory - usually the unqualified type name
258+
/// @param callback an optional callback that is run whenever a new object is created
259+
/// The factory gets called with a string argument giving the new
260+
/// objects name in the registry, followed by the arguments for
261+
/// T's constructor, if any
262+
template <class T, class... Args> void addFactory
263+
(const std::string& typeName,const std::function<void(const std::string& objName)>& callback=nullptr);
252264
};
253265

254266

RESTProcess_epilogue.h

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,6 @@ namespace classdesc
233233
r<<array;
234234
return r;
235235
}
236-
// else if (tail==".@list")
237-
// return REST_PROCESS_BUFFER(REST_PROCESS_BUFFER::Array());
238-
// else if (tail==".@type")
239-
// return REST_PROCESS_BUFFER("overloaded function");
240236
// sort function overloads by best match
241237
auto cmp=[&](RESTProcessFunctionBase*x, RESTProcessFunctionBase*y)
242238
{return x->matchScore(jin)<y->matchScore(jin);};
@@ -275,6 +271,27 @@ namespace classdesc
275271
{}
276272

277273
template <class T> RESTProcess_t::RESTProcess_t(T&obj) {populateFromObj(*this,obj);}
274+
275+
template <class T> struct RESTProcessHeapObject: public RESTProcessPtr<std::unique_ptr<T>>
276+
{
277+
std::unique_ptr<T> obj;
278+
RESTProcessHeapObject(): RESTProcessPtr<std::unique_ptr<T>>(obj) {}
279+
};
280+
281+
template <class T, class...A> void RESTProcess_t::addFactory
282+
(const string& typeName,const std::function<void(const std::string& objName)>& callback)
283+
{
284+
std::function<void(const std::string& name, A... args)> factory=
285+
[this,callback](const std::string& name, A... args) {
286+
auto rp=std::make_unique<RESTProcessHeapObject<T>>();
287+
rp->obj=std::make_unique<T>(std::forward<A>(args)...);
288+
add(name,rp.release());
289+
callback(name);
290+
};
291+
292+
add(typeName, new RESTProcessFunction<decltype(factory),void>(factory));
293+
}
294+
278295
}
279296

280297
namespace classdesc_access

pythonBuffer.h

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,17 @@
3434
namespace classdesc
3535
{
3636
// map of registries, one per module
37-
inline std::map<std::string, RESTProcess_t>& registries()
37+
inline std::map<std::string, RESTProcess_t*>& registries()
3838
{
39-
static std::map<std::string, RESTProcess_t> registries;
39+
static std::map<std::string, RESTProcess_t*> registries;
4040
return registries;
4141
}
4242

4343
namespace
4444
{
45-
// reference to per compile unit registry
46-
RESTProcess_t* registry=nullptr;
45+
// per compile unit registry
46+
RESTProcess_t registry;
47+
PyObject* pythonModule=nullptr;
4748
}
4849

4950
/// @{ utility python object constructors
@@ -484,7 +485,7 @@ struct CppWrapperType: public PyTypeObject
484485
static Py_ssize_t size(PyObject* self)
485486
{
486487
auto cppWrapper=static_cast<CppWrapper*>(self);
487-
return registry->process(cppWrapper->command+".@size",{}).get_uint64();
488+
return registry.process(cppWrapper->command+".@size",{}).get_uint64();
488489
}
489490

490491
static PyObject* getElem(PyObject* self, PyObject* key)
@@ -545,13 +546,13 @@ struct CppWrapperType: public PyTypeObject
545546
if (!pyObject||command.find('@')!=string::npos) return;
546547
try
547548
{
548-
PyObject_SetAttrString(pyObject, "_signature",newPyObject(registry->process(command+".@signature",{})));
549-
PyObject_SetAttrString(pyObject, "_type", newPyObject(registry->process(command+".@type",{})));
549+
PyObject_SetAttrString(pyObject, "_signature",newPyObject(registry.process(command+".@signature",{})));
550+
PyObject_SetAttrString(pyObject, "_type", newPyObject(registry.process(command+".@type",{})));
550551
}
551552
catch (...) { } // do not log, nor report errors back to python - there are too many
552553
try
553554
{
554-
auto methods=registry->process(command+".@list",{});
555+
auto methods=registry.process(command+".@list",{});
555556
if (methods.type()!=RESTProcessType::array) return;
556557
for (auto& i: methods.array())
557558
{
@@ -585,7 +586,7 @@ struct CppWrapperType: public PyTypeObject
585586
try
586587
{
587588
auto args=arguments.get<json_pack_t>();
588-
const PythonBuffer result(registry->process(command, args));
589+
const PythonBuffer result(registry.process(command, args));
589590

590591
auto pyResult=result.getPyObject();
591592
switch (result.type())
@@ -616,25 +617,43 @@ struct CppWrapperType: public PyTypeObject
616617
}
617618
}
618619

619-
template <class T>
620-
void initModule(PyObject* module, const char* objName, T& object)
620+
///
621+
template <class T, class... Args> struct DeclareType
622+
{
623+
DeclareType(const string& typeName) {
624+
registry.addFactory<T,Args...>(typeName,[](const string& name){
625+
PyObjectRef pyObject=CppWrapper::create(name);
626+
LimitRecursion().attachMethods(pyObject,name);
627+
PyModule_AddObject(pythonModule, name.c_str(), pyObject.release());
628+
});
629+
}
630+
};
631+
632+
void initModule()
621633
{
622-
assert(module);
623-
classdesc::RESTProcess(*registry,objName,object);
624-
PyObjectRef pyObject=CppWrapper::create(objName);
625-
LimitRecursion().attachMethods(pyObject,objName);
626-
PyModule_AddObject(module, objName, pyObject.release());
634+
assert(pythonModule);
635+
// grab all toplevel objects already installed
636+
std::set<std::string> topLevelNames;
637+
for (auto& i: registry)
638+
if (!i.first.empty() && i.first[0]!='@')
639+
topLevelNames.insert(i.first.substr(0,i.first.find('.')));
640+
for (auto& i: topLevelNames)
641+
{
642+
PyObjectRef pyObject=CppWrapper::create(i);
643+
LimitRecursion().attachMethods(pyObject,i);
644+
PyModule_AddObject(pythonModule, i.c_str(), pyObject.release());
645+
}
627646

628647
// enum reflection
629648
PyObjectRef enummer=PyDict_New();
630-
auto enumList=registry->process("@enum.@list",{});
649+
auto enumList=registry.process("@enum.@list",{});
631650
for (auto& i: enumList.array())
632651
{
633652
string name=i.get_str();
634653
PyDict_SetItemString(enummer, name.c_str(),
635-
newPyObjectJson(registry->process("@enum."+name,{})));
654+
newPyObjectJson(registry.process("@enum."+name,{})));
636655
}
637-
PyModule_AddObject(module, "enum", enummer.release());
656+
PyModule_AddObject(pythonModule, "enum", enummer.release());
638657
}
639658
}
640659

@@ -653,7 +672,7 @@ namespace classdesc_access
653672
/// a convenience macro for creating a python module with a single global object
654673
/// @param name module name
655674
/// @param object C++ object to expose to python
656-
#define CLASSDESC_PYTHON_MODULE(name,object) \
675+
#define CLASSDESC_PYTHON_MODULE(name) \
657676
PyMODINIT_FUNC PyInit_##name() \
658677
{ \
659678
static PyModuleDef module_##name = { \
@@ -668,11 +687,24 @@ namespace classdesc_access
668687
nullptr \
669688
}; \
670689
\
671-
auto module=PyModule_Create(&module_##name); \
672-
registry=&registries()[#name]; \
673-
if (module) initModule(module, #object, object); \
674-
return module; \
675-
}
676-
690+
registries()[#name]=&registry; \
691+
pythonModule=PyModule_Create(&module_##name); \
692+
if (pythonModule) initModule(); \
693+
return pythonModule; \
694+
}
695+
696+
/// Add a global object into the registry. \a object is also used as
697+
/// the name this object will be referred to from Python, so must be
698+
/// unqualified at the macro call.
699+
#define CLASSDESC_ADD_GLOBAL(object) \
700+
static int add_global_##object=(classdesc::RESTProcess(classdesc::registry,#object,object), 0);
701+
702+
/// Add a type foundry or factory into the registry. \a type is also
703+
/// used as the name this object will be referred to from Python, so
704+
/// must be unqualified at the macro call. The following optional
705+
/// arguments are a list of types of arguments passed to the
706+
/// constructor, if any.
707+
#define CLASSDESC_DECLARE_TYPE(type,...) \
708+
static classdesc::DeclareType<type __VA_OPT__(,) __VA_ARGS__> declareType_##type(#type);
677709

678710
#endif

0 commit comments

Comments
 (0)