NRP Core  1.4.1
proto_python_bindings.h
Go to the documentation of this file.
1 /* * NRP Core - Backend infrastructure to synchronize simulations
2  *
3  * Copyright 2020-2023 NRP Team
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * This project has received funding from the European Union’s Horizon 2020
18  * Framework Programme for Research and Innovation under the Specific Grant
19  * Agreement No. 945539 (Human Brain Project SGA3).
20  */
21 
22 #ifndef PROTO_PYTHON_BINDINGS_H
23 #define PROTO_PYTHON_BINDINGS_H
24 
25 #include "google/protobuf/message.h"
26 #include <boost/python.hpp>
27 #include <algorithm>
28 
32 
33 
34 namespace bpy = boost::python;
35 namespace gpb = google::protobuf;
36 using namespace proto_field_ops;
37 
54 template<class MSG_TYPE, class ...FIELD_MSG_TYPES>
56 {
57 public:
58 
60  static_assert(std::is_base_of<google::protobuf::Message, MSG_TYPE>(),"Parameter MSG_TYPE must derive from protobuf::Message");
61  static_assert((std::is_base_of_v<google::protobuf::Message, FIELD_MSG_TYPES...>), "Parameter FIELD_MSG_TYPES must derive from protobuf::Message");
62  }
63 
64  static void throw_python_error(PyObject * error, std::string msg) {
65  PyErr_SetString(error, msg.c_str());
66  boost::python::throw_error_already_set();
67  }
68 
72  static bpy::object GetAttribute(MSG_TYPE& m, char const* name)
73  {
74  const gpb::FieldDescriptor *field = m.GetDescriptor()->FindFieldByName(name);
75  if(!field) {
76  std::stringstream s;
77  s << m.GetDescriptor()->name() << "\" object has no attribute \"" << name << "\"";
78  throw_python_error(PyExc_AttributeError, s.str());
79  return bpy::object();
80  }
81 
82  if(field->is_map())
83  return bpy::object();
84  else if(field->is_repeated())
85  {
86  if(field->cpp_type() == gpb::FieldDescriptor::CPPTYPE_MESSAGE)
87  return bpy::object();
88  else
89  return bpy::object(RepeatedScalarFieldProxy(m, field));
90  }
91  else if(field->cpp_type() == gpb::FieldDescriptor::CPPTYPE_MESSAGE) // TYPE_MESSAGE, TYPE_GROUP
92  {
93  if constexpr (sizeof...(FIELD_MSG_TYPES) > 0)
94  return GetMessageField<FIELD_MSG_TYPES...>(m, field);
95  else
96  throw NRPException::logCreate("Can't get composite field with name " + field->name());
97  }
98  else
99  return GetScalarField(m, field);
100  }
101 
105  static void SetAttribute(MSG_TYPE& m, char const* name, const bpy::object& value)
106  {
107  const gpb::FieldDescriptor *field = m.GetDescriptor()->FindFieldByName(name);
108  if(!field) {
109  std::stringstream s;
110  s << m.GetDescriptor()->name() << "\" object has no attribute \"" << name << "\"";
111  throw_python_error(PyExc_AttributeError, s.str());
112  return;
113  }
114 
115  if(field->is_repeated() || field->is_map())
116  throw_python_error(PyExc_AttributeError,
117  "Assignment not allowed to repeated field \"" + field->name() + "\" in protocol message object.");
118  else if(field->cpp_type() == gpb::FieldDescriptor::CPPTYPE_MESSAGE) // TYPE_MESSAGE, TYPE_GROUP
119  throw_python_error(PyExc_AttributeError,
120  "Assignment not allowed to field \"" + field->name() + "\" in protocol message object.");
121  else
122  SetScalarField(m, field, value);
123  }
124 
128  static bpy::object WhichOneof(MSG_TYPE& m, char const* name)
129  {
130  const gpb::OneofDescriptor *fieldOne = m.GetDescriptor()->FindOneofByName(name);
131  if(!fieldOne) {
132  std::stringstream s;
133  s << "Protocol message has no oneof \"" << name << "\" field";
134  PyErr_SetString(PyExc_ValueError, s.str().c_str());
135  boost::python::throw_error_already_set();
136  return bpy::object();
137  }
138 
139  const gpb::FieldDescriptor *field = m.GetReflection()->GetOneofFieldDescriptor(m,fieldOne);
140  if(field)
141  return bpy::object(field->name());
142  else
143  return bpy::object();
144  }
145 
149  static void ClearField(MSG_TYPE& m, char const* name)
150  {
151  const gpb::FieldDescriptor *field = m.GetDescriptor()->FindFieldByName(name);
152  if(field) {
153  m.GetReflection()->ClearField(&m, field);
154  return;
155  }
156 
157  const gpb::OneofDescriptor *fieldOne = m.GetDescriptor()->FindOneofByName(name);
158  if(fieldOne) {
159  m.GetReflection()->ClearOneof(&m, fieldOne);
160  return;
161  }
162 
163  std::stringstream s;
164  s << "Protocol message has no \"" << name << "\" field.";
165  PyErr_SetString(PyExc_ValueError, s.str().c_str());
166  boost::python::throw_error_already_set();
167  }
168 
172  static bool HasField(MSG_TYPE& m, char const* name)
173  {
174  const gpb::FieldDescriptor *field = m.GetDescriptor()->FindFieldByName(name);
175  if(field)
176  return m.GetReflection()->HasField(m, field);
177 
178  std::stringstream s;
179  s << "Unknown field " << name;
180  PyErr_SetString(PyExc_ValueError, s.str().c_str());
181  boost::python::throw_error_already_set();
182  return false;
183  }
184 
188  static bpy::list GetFieldNames(MSG_TYPE& m)
189  {
190  bpy::list fieldNames;
191  for(auto i = 0; i < m.descriptor()->field_count(); ++i)
192  fieldNames.template append(bpy::str(m.descriptor()->field(i)->name()));
193  return fieldNames;
194  }
195 
199  static bpy::str GetFieldTypeName(MSG_TYPE& m, char const* name)
200  {
201  const gpb::FieldDescriptor *field = m.GetDescriptor()->FindFieldByName(name);
202  if(field)
203  return bpy::str(field->type_name());
204 
205  std::stringstream s;
206  s << "Unknown field " << name;
207  PyErr_SetString(PyExc_ValueError, s.str().c_str());
208  boost::python::throw_error_already_set();
209  return "";
210  }
211 
215  static bpy::class_<MSG_TYPE> create() {
216  std::shared_ptr<gpb::Message> m(new MSG_TYPE());
217  const gpb::Descriptor *desc = m->GetDescriptor();
218 
219  auto py_name = desc->full_name();
220  py_name.erase(std::remove(py_name.begin(), py_name.end(), '.'), py_name.end());
221 
222  bpy::class_<MSG_TYPE> binder(py_name.c_str());
223  binder.def(bpy::init<const MSG_TYPE &>(py_name.c_str()));
224  binder.def("__str__", &MSG_TYPE::DebugString);
225  binder.def("__getattr__", GetAttribute);
226  binder.def("__setattr__", SetAttribute);
227  binder.def("ClearField",ClearField);
228  binder.def("HasField", HasField);
229  binder.def("GetFieldNames", GetFieldNames);
230  binder.def("GetFieldTypeName", GetFieldTypeName);
231  binder.def("WhichOneof",WhichOneof);
232  binder.def("IsInitialized", &MSG_TYPE::IsInitialized);
233  binder.def("Clear", &MSG_TYPE::Clear);
234 
235  return binder;
236  }
237 };
238 
239 #endif // PROTO_PYTHON_BINDINGS_H
proto_python_bindings::throw_python_error
static void throw_python_error(PyObject *error, std::string msg)
Definition: proto_python_bindings.h:64
proto_python_bindings::GetAttribute
static bpy::object GetAttribute(MSG_TYPE &m, char const *name)
getattr
Definition: proto_python_bindings.h:72
nrp_exceptions.h
proto_field_ops::SetScalarField
void SetScalarField(gpb::Message &m, const gpb::FieldDescriptor *field, const bpy::object &value)
Set scalar field.
Definition: proto_field_ops.cpp:142
proto_field_ops.h
RepeatedScalarFieldProxy
Proxy class implementing a list-like python wrapper for a protobuf repeated scalar field (ie....
Definition: repeated_field_proxy.h:45
repeated_field_proxy.h
proto_field_ops
Implement single field Get/Set operations using field descriptor and reflection interface.
Definition: proto_field_ops.cpp:25
proto_python_bindings::WhichOneof
static bpy::object WhichOneof(MSG_TYPE &m, char const *name)
WhichOneof.
Definition: proto_python_bindings.h:128
proto_python_bindings::GetFieldNames
static bpy::list GetFieldNames(MSG_TYPE &m)
GetFieldNames.
Definition: proto_python_bindings.h:188
python_grpc_engine.str
str
Definition: python_grpc_engine.py:63
proto_python_bindings::SetAttribute
static void SetAttribute(MSG_TYPE &m, char const *name, const bpy::object &value)
setattr
Definition: proto_python_bindings.h:105
proto_python_bindings::ClearField
static void ClearField(MSG_TYPE &m, char const *name)
ClearField.
Definition: proto_python_bindings.h:149
proto_python_bindings::proto_python_bindings
proto_python_bindings()
Definition: proto_python_bindings.h:59
proto_field_ops::GetScalarField
bpy::object GetScalarField(gpb::Message &m, const gpb::FieldDescriptor *field)
Get scalar field. Returns a copy of the field value.
Definition: proto_field_ops.cpp:27
NRPException::logCreate
static EXCEPTION logCreate(LOG_EXCEPTION_T &exception, const std::string &msg, NRPLogger::spdlog_out_fcn_t spdlogCall=NRPLogger::critical)
Definition: nrp_exceptions.h:73
proto_python_bindings
Class implementing python wrappers for protobuf Message types.
Definition: proto_python_bindings.h:55
proto_python_bindings::HasField
static bool HasField(MSG_TYPE &m, char const *name)
HasField.
Definition: proto_python_bindings.h:172
proto_python_bindings::GetFieldTypeName
static bpy::str GetFieldTypeName(MSG_TYPE &m, char const *name)
GetFieldTypeName.
Definition: proto_python_bindings.h:199
proto_python_bindings::create
static bpy::class_< MSG_TYPE > create()
Creates bindings for Protobuf Message type MSG_TYPE.
Definition: proto_python_bindings.h:215
proto_field_ops::GetMessageField
bpy::object GetMessageField(gpb::Message &m, const gpb::FieldDescriptor *field)
Get message field. Returns a reference of the field value.
Definition: proto_field_ops.h:57