Belle II Software  release-08-01-10
PyObjConvUtils.h
1 /**************************************************************************
2  * basf2 (Belle II Analysis Software Framework) *
3  * Author: The Belle II Collaboration *
4  * *
5  * See git log for contributors and copyright holders. *
6  * This file is licensed under LGPL-3.0, see LICENSE.md. *
7  **************************************************************************/
8 
9 #pragma once
10 
11 #include <boost/python/object.hpp>
12 #include <boost/python/list.hpp>
13 #include <boost/python/tuple.hpp>
14 #include <boost/python/dict.hpp>
15 #include <boost/python/extract.hpp>
16 #include <boost/variant.hpp>
17 
18 #include <map>
19 #include <optional>
20 #include <set>
21 #include <string>
22 #include <vector>
23 
24 
25 namespace Belle2 {
30  class Path;
31 
40  namespace PyObjConvUtils {
41 
42  /*
43  * We need forward declarations here, because otherwise the compiler ends up calling
44  * the wrong function!
45  */
47  bool checkPythonObject(const boost::python::object& pyObject, bool);
49  bool checkPythonObject(const boost::python::object& pyObject, float);
51  bool checkPythonObject(const boost::python::object& pyObject, double);
53  bool checkPythonObject(const boost::python::object& pyObject, int);
55  bool checkPythonObject(const boost::python::object& pyObject, unsigned int);
57  bool checkPythonObject(const boost::python::object& pyObject, unsigned long int);
59  bool checkPythonObject(const boost::python::object& pyObject, const std::string&);
60  template<typename Key, typename Value>
61  bool checkPythonObject(const boost::python::object& pyObject, const std::map<Key, Value>&);
63  template<typename Value>
64  bool checkPythonObject(const boost::python::object& pyObject, const std::vector<Value>&);
66  template<typename Value>
67  bool checkPythonObject(const boost::python::object& pyObject, const std::set<Value>&);
69  template<typename... Types>
70  bool checkPythonObject(const boost::python::object& pyObject, const std::tuple<Types...>&);
72  template<typename... Types>
73  bool checkPythonObject(const boost::python::object& pyObject, const boost::variant<Types...>&);
75  template<typename Type>
76  bool checkPythonObject(const boost::python::object& pyObject, const std::optional<Type>&);
77 
79  template<typename Scalar>
80  Scalar convertPythonObject(const boost::python::object& pyObject, Scalar);
82  template<typename Key, typename Value>
83  std::map<Key, Value> convertPythonObject(const boost::python::object& pyObject, const std::map<Key, Value>&);
85  template<typename Value>
86  std::vector<Value> convertPythonObject(const boost::python::object& pyObject, const std::vector<Value>&);
88  template<typename Value>
89  std::set<Value> convertPythonObject(const boost::python::object& pyObject, const std::set<Value>&);
91  template<typename... Types>
92  std::tuple<Types...> convertPythonObject(const boost::python::object& pyObject, const std::tuple<Types...>&);
94  template<typename... Types>
95  boost::variant<Types...> convertPythonObject(const boost::python::object& pyObject, const boost::variant<Types...>&);
97  template<typename Type>
98  std::optional<Type> convertPythonObject(const boost::python::object& pyObject, const std::optional<Type>&);
99 
100  template<typename Scalar>
101  boost::python::object convertToPythonObject(const Scalar& value);
102  template<typename Value>
103  boost::python::list convertToPythonObject(const std::vector<Value>& vector);
105  template<typename Value>
106  boost::python::object convertToPythonObject(const std::set<Value>& set);
107  template<typename Key, typename Value>
108  boost::python::dict convertToPythonObject(const std::map<Key, Value>& map);
109  template<typename... Types>
110  boost::python::tuple convertToPythonObject(const std::tuple<Types...>& tuple);
111  template<typename... Types>
112  boost::python::object convertToPythonObject(const boost::variant<Types...>& tuple);
114  template<typename Type>
115  boost::python::object convertToPythonObject(const std::optional<Type>& optional);
116 
117  template<typename T> struct Type;
118  template<typename T> struct Type<std::vector<T> >;
119  template<typename T> struct Type<std::set<T>>;
120  template<typename A, typename B> struct Type<std::map<A, B> >;
121  template<typename... Types> struct Type<std::tuple<Types...> >;
122  template<typename... Types> struct Type<boost::variant<Types...> >;
123  template<> struct Type<int>;
124  template<> struct Type<bool>;
125  template<> struct Type<float>;
126  template<> struct Type<double>;
127  template<> struct Type<std::string>;
128 
129  template< typename T, typename... Types> struct VariadicType;
131  template< typename T> struct VariadicType<T> { static std::string name() { return Type<T>::name(); } };
133  template< typename T, typename... Types> struct VariadicType {
135  static std::string name()
136  {
137  return Type<T>::name() + ", " + VariadicType<Types...>::name();
138  }
139  };
140 
145  template<typename T> struct Type { static std::string name() { return "???";} };
147  template<typename T> struct Type<std::vector<T> > { static std::string name() { return std::string("list(") + Type<T>::name() + ")"; } };
149  template<typename T> struct Type<std::set<T>> { static std::string name() { return std::string("set(") + Type<T>::name() + ")"; }};
151  template<typename A, typename B> struct Type<std::map<A, B> > { static std::string name() { return std::string("dict(") + Type<A>::name() + " -> " + Type<B>::name() + ")"; } };
152 
154  template<> struct Type<unsigned int> { static std::string name() { return "unsigned int"; } };
156  template<> struct Type<unsigned long int> { static std::string name() { return "unsigned long int"; } };
158  template<> struct Type<int> { static std::string name() { return "int"; } };
160  template<> struct Type<bool> { static std::string name() { return "bool"; } };
162  template<> struct Type<float> { static std::string name() { return "float"; } };
164  template<> struct Type<double> { static std::string name() { return "float"; } };
166  template<> struct Type<std::string> { static std::string name() { return "str"; } };
167 
169  template<typename T> struct Type<std::optional<T>> { static std::string name() { return Type<T>::name() + " or None"; } };
170 
172  template<typename... Types> struct Type<std::tuple<Types...> > { static std::string name() { return std::string("tuple(") + VariadicType<Types...>::name() + ")"; } };
173 
175  template<typename... Types> struct Type<boost::variant<Types...> > { static std::string name() { return std::string("variant(") + VariadicType<Types...>::name() + ")"; } };
176 
178  template<> struct Type<std::shared_ptr<Path>> {
180  static std::string name() { return std::string("Path"); }
181  };
182 
183 
185  template < size_t > struct SizeT { };
186 
192  inline bool checkPythonObject(const boost::python::object& pyObject, bool /*dispatch tag*/)
193  {
194  return PyBool_Check(pyObject.ptr());
195  }
196 
198  inline bool checkPythonObject(const boost::python::object& pyObject, int /*dispatch tag*/)
199  {
200  return PyLong_CheckExact(pyObject.ptr());
201  }
203  inline bool checkPythonObject(const boost::python::object& pyObject, unsigned int /*dispatch tag*/)
204  {
205  return PyLong_CheckExact(pyObject.ptr());
206  }
207 
209  inline bool checkPythonObject(const boost::python::object& pyObject, unsigned long int /*dispatch tag*/)
210  {
211  return PyLong_CheckExact(pyObject.ptr());
212  }
213 
215  inline bool checkPythonObject(const boost::python::object& pyObject, float /*dispatch tag*/)
216  {
217  return PyFloat_CheckExact(pyObject.ptr());
218  }
219 
221  inline bool checkPythonObject(const boost::python::object& pyObject, double /*dispatch tag*/)
222  {
223  return PyFloat_CheckExact(pyObject.ptr());
224  }
225 
227  inline bool checkPythonObject(const boost::python::object& pyObject, const std::string& /*dispatch tag*/)
228  {
229  return PyUnicode_Check(pyObject.ptr());
230  }
231 
233  template<typename Key, typename Value>
234  bool checkPythonObject(const boost::python::object& pyObject, const std::map<Key, Value>& /*dispatch tag*/)
235  {
236  if (not PyDict_Check(pyObject.ptr())) return false;
237  const boost::python::dict& pyDict = static_cast<const boost::python::dict&>(pyObject);
238  boost::python::list keys = pyDict.keys();
239  boost::python::list values = pyDict.values();
240  for (int i = 0; i < boost::python::len(keys); ++i) {
241  if (not checkPythonObject(keys[i], Key())) return false;
242  if (not checkPythonObject(values[i], Value())) return false;
243  }
244  return true;
245  }
246 
254  template<class Functor>
255  bool iteratePythonObject(const boost::python::object& pyObject, Functor function)
256  {
257  boost::python::object iterator(boost::python::handle<>(PyObject_GetIter(pyObject.ptr())));
258  PyObject* item{nullptr};
259  // ok, loop over the iterator and check all elements
260  while ((item = PyIter_Next(iterator.ptr()))) {
261  boost::python::object obj{boost::python::handle<>(item)}; // to make sure we properly decref
262  if (not function(obj)) return false;
263  }
264  return true;
265  }
266 
268  template<typename Value>
269  bool checkPythonObject(const boost::python::object& pyObject, const std::vector<Value>& /*dispatch tag*/)
270  {
271  if (not PyList_Check(pyObject.ptr())) return false;
272  return iteratePythonObject(pyObject, [](const boost::python::object & element) {
273  return checkPythonObject(element, Value());
274  });
275  }
276 
277  template<typename Value>
278  bool checkPythonObject(const boost::python::object& pyObject, const std::set<Value>&)
279  {
280  if (not PyAnySet_Check(pyObject.ptr())) return false;
281  return iteratePythonObject(pyObject, [](const boost::python::object & element) {
282  return checkPythonObject(element, Value());
283  });
284  }
285 
287  template<typename... Types, std::size_t ... Is>
288  bool CheckTuple(const std::tuple<Types...>& tuple, const boost::python::tuple& pyTuple,
289  std::index_sequence<Is...>)
290  {
291  return (... && checkPythonObject(pyTuple[Is], std::get<Is>(tuple)));
292  }
293 
295  template<typename... Types>
296  bool checkPythonObject(const boost::python::object& pyObject, const std::tuple<Types...>& tuple)
297  {
298  if (not PyTuple_Check(pyObject.ptr())) return false;
299  const boost::python::tuple& pyTuple = static_cast<const boost::python::tuple&>(pyObject);
300  return CheckTuple(tuple, pyTuple, std::index_sequence_for<Types ...>());
301  }
302 
304  template<typename VariantType>
305  bool CheckVariant(const VariantType&,
306  const boost::python::object&,
307  SizeT<0>)
308  {
309  return false;
310  }
311 
313  template<typename... Types, size_t N>
314  bool CheckVariant(const boost::variant<Types...>& variant,
315  const boost::python::object& pyObject,
316  SizeT<N>)
317  {
318  using Scalar = typename std::tuple_element < N - 1, std::tuple<Types...> >::type;
319  if (checkPythonObject(pyObject, Scalar())) {
320  return true;
321  }
322  return CheckVariant(variant, pyObject, SizeT < N - 1 > ());
323  }
324 
326  template<typename... Types>
327  bool checkPythonObject(const boost::python::object& pyObject, const boost::variant<Types...> variant)
328  {
329  return CheckVariant(variant, pyObject, SizeT<sizeof...(Types)>());
330  }
331 
335  template<typename Type>
336  bool checkPythonObject(const boost::python::object& pyObject, const std::optional<Type>&)
337  {
338  return pyObject.is_none() or checkPythonObject(pyObject, Type());
339  }
340 
351  template<typename Scalar>
352  boost::python::object convertToPythonObject(const Scalar& value)
353  {
354  return boost::python::object(value);
355  }
356 
363  template<typename Value>
364  boost::python::list convertToPythonObject(const std::vector<Value>& vector)
365  {
366  boost::python::list outputList;
367  for (const auto& value : vector) {
368  outputList.append(convertToPythonObject(value));
369  }
370  return outputList;
371  }
372 
377  template<typename Value>
378  boost::python::object convertToPythonObject(const std::set<Value>& set)
379  {
380  boost::python::object result(boost::python::handle<>(PySet_New(nullptr)));
381  for (const auto& value : set) {
382  PySet_Add(result.ptr(), convertToPythonObject(value).ptr());
383  }
384  return result;
385  }
386 
393  template<typename Key, typename Value>
394  boost::python::dict convertToPythonObject(const std::map<Key, Value>& map)
395  {
396  boost::python::dict outputDict;
397  for (auto pair : map) {
398  outputDict[convertToPythonObject(pair.first)] = convertToPythonObject(pair.second);
399  }
400  return outputDict;
401  }
402 
411  template < typename TupleType >
412  inline void GetTuple(const TupleType& tuple, boost::python::list& pyList)
413  {
414  GetTuple(tuple, pyList, SizeT<std::tuple_size<TupleType>::value>());
415  }
416 
418  template < typename TupleType >
419  inline void GetTuple(const TupleType&, boost::python::list&, SizeT<0>) { }
420 
423  template < typename TupleType, size_t N >
424  inline void GetTuple(const TupleType& tuple, boost::python::list& pyList, SizeT<N>)
425  {
426  GetTuple(tuple, pyList, SizeT < N - 1 > ());
427  pyList.append(convertToPythonObject(std::get < N - 1 > (tuple)));
428  }
437  template<typename... Types>
438  boost::python::tuple convertToPythonObject(const std::tuple<Types...>& tuple)
439  {
440  boost::python::list outputList;
441  GetTuple(tuple, outputList);
442  boost::python::tuple outputTuple(outputList);
443  return outputTuple;
444  }
445 
449  class convertToPythonObjectVisitor : public boost::static_visitor<boost::python::object> {
450  public:
452  template<class T>
453  boost::python::object operator()(const T& value) const
454  {
455  return convertToPythonObject(value);
456  }
457  };
458 
465  template<typename... Types>
466  boost::python::object convertToPythonObject(const boost::variant<Types...>& variant)
467  {
468  return boost::apply_visitor(convertToPythonObjectVisitor(), variant);
469  }
470 
478  template<typename Type>
479  boost::python::object convertToPythonObject(const std::optional<Type>& optional)
480  {
481  if (optional) {
482  return convertToPythonObject(*optional);
483  } else {
484  return boost::python::object();
485  }
486  }
487 
499  template<typename Scalar>
500  Scalar convertPythonObject(const boost::python::object& pyObject, const Scalar)
501  {
502 
503  Scalar tmpValue;
504  boost::python::extract<Scalar> valueProxy(pyObject);
505  if (valueProxy.check()) {
506  tmpValue = static_cast<Scalar>(valueProxy);
507  } else {
508  throw std::runtime_error(std::string("Could not convert value: Expected type '") + Type<Scalar>::name() + "' instead of '" +
509  pyObject.ptr()->ob_type->tp_name + "'.");
510  }
511  return tmpValue;
512 
513  }
514 
522  template<typename Value>
523  std::vector<Value> convertPythonObject(const boost::python::object& pyObject, const std::vector<Value>&)
524  {
525 
526  std::vector<Value> tmpVector;
527  if (PyList_Check(pyObject.ptr()) or PyGen_Check(pyObject.ptr())) {
528  iteratePythonObject(pyObject, [&tmpVector](const boost::python::object & element) {
529  tmpVector.emplace_back(convertPythonObject(element, Value()));
530  return true;
531  });
532  } else {
533  tmpVector.emplace_back(convertPythonObject(pyObject, Value()));
534  }
535  return tmpVector;
536  }
537 
539  template<typename Value>
540  std::set<Value> convertPythonObject(const boost::python::object& pyObject, const std::set<Value>&)
541  {
542  std::set<Value> result;
543  if (PyAnySet_Check(pyObject.ptr())) {
544  iteratePythonObject(pyObject, [&result](const boost::python::object & element) {
545  result.emplace(convertPythonObject(element, Value()));
546  return true;
547  });
548  } else {
549  result.emplace(convertPythonObject(pyObject, Value()));
550  }
551  return result;
552  }
553 
554 
561  template<typename Key, typename Value>
562  std::map<Key, Value> convertPythonObject(const boost::python::object& pyObject, const std::map<Key, Value>&)
563  {
564  std::map<Key, Value> tmpMap;
565  const boost::python::dict& pyDict = static_cast<const boost::python::dict&>(pyObject);
566  boost::python::list keys = pyDict.keys();
567 
568  for (int i = 0; i < boost::python::len(keys); ++i) {
569  Key key = convertPythonObject(keys[boost::python::object(i)], Key());
570  Value value = convertPythonObject(pyDict[key], Value());
571  tmpMap.insert(std::pair<Key, Value>(key, value));
572  }
573  return tmpMap;
574  }
575 
576 
585  template < typename TupleType >
586  inline void SetTuple(TupleType& tuple, const boost::python::tuple& pyTuple)
587  {
588  static const unsigned N = std::tuple_size<TupleType>::value;
589  if ((unsigned)boost::python::len(pyTuple) != N) {
590  throw std::runtime_error(std::string("Given python tuple has length ") +
591  std::to_string(boost::python::len(pyTuple)) +
592  ", expected " + std::to_string(N));
593  }
594  SetTuple(tuple, pyTuple, SizeT<N>());
595  }
596 
598  template < typename TupleType >
599  inline void SetTuple(TupleType&, const boost::python::tuple&, SizeT<0>) { }
600 
603  template < typename TupleType, size_t N >
604  inline void SetTuple(TupleType& tuple, const boost::python::tuple& pyTuple, SizeT<N>)
605  {
606  SetTuple(tuple, pyTuple, SizeT < N - 1 > ());
607  std::get < N - 1 > (tuple) = convertPythonObject(pyTuple[N - 1], std::get < N - 1 > (tuple));
608  }
617  template<typename... Types>
618  std::tuple<Types...> convertPythonObject(const boost::python::object& pyObject, const std::tuple<Types...>&)
619  {
620  std::tuple<Types...> tmpTuple;
621  const boost::python::tuple& pyTuple = static_cast<const boost::python::tuple&>(pyObject);
622  SetTuple(tmpTuple, pyTuple);
623  return tmpTuple;
624  }
625 
634  template <typename... Types>
635  inline void SetVariant(boost::variant<Types...>&, const boost::python::object& pyObject, SizeT<0>)
636  {
637  throw std::runtime_error(std::string("Could not set module parameter: Expected type '") +
638  Type<boost::variant<Types...> >::name() + "' instead of '" +
639  pyObject.ptr()->ob_type->tp_name + "'.");
640  }
641 
643  template < typename... Types, size_t N >
644  inline void SetVariant(boost::variant<Types...>& variant, const boost::python::object& pyObject, SizeT<N>)
645  {
646  using Scalar = typename std::tuple_element < N - 1, std::tuple<Types...> >::type;
647  if (checkPythonObject(pyObject, Scalar())) {
648  try {
649  Scalar value = convertPythonObject(pyObject, Scalar());
650  variant = value;
651  return;
652  } catch (std::runtime_error& e) {
653  }
654  }
655  // Conversion failed - try next type
656  SetVariant(variant, pyObject, SizeT < N - 1 > ());
657  }
666  template<typename... Types>
667  boost::variant<Types...> convertPythonObject(const boost::python::object& pyObject, const boost::variant<Types...>&)
668  {
669  boost::variant<Types...> tmpVariant;
670  SetVariant(tmpVariant, pyObject, SizeT<sizeof...(Types)>());
671  return tmpVariant;
672  }
673 
680  template<typename Type>
681  std::optional<Type> convertPythonObject(const boost::python::object& pyObject, const std::optional<Type>&)
682  {
683  std::optional<Type> tmpOptional = std::nullopt;
684  if (!pyObject.is_none()) {
685  tmpOptional = convertPythonObject(pyObject, Type());
686  }
687  return tmpOptional;
688  }
689  }
691 }
Helper function object to unpack a value from a variant to a python object.
boost::python::object operator()(const T &value) const
actually convert a value.
boost::python::object convertToPythonObject(const Scalar &value)
------------— From C++ TO Python Converter ---------------------—
void GetTuple(const TupleType &tuple, boost::python::list &pyList)
TMP (Template Meta Programming ) The given python list is filled, and later converted into a python t...
void SetTuple(TupleType &tuple, const boost::python::tuple &pyTuple)
TMP (Template Meta Programming ) The given python tuple is written into the given c++ tuple.
Scalar convertPythonObject(const boost::python::object &pyObject, Scalar)
Convert from Python to given type.
bool CheckVariant(const VariantType &, const boost::python::object &, SizeT< 0 >)
Recursion sentinal for the case that all former type checks failed for the variant.
void SetVariant(boost::variant< Types... > &, const boost::python::object &pyObject, SizeT< 0 >)
TMP (Template Meta Programming ) The given python object is written into the given c++ boost variant.
bool CheckTuple(const std::tuple< Types... > &tuple, const boost::python::tuple &pyTuple, std::index_sequence< Is... >)
Check if all tuple elements match.
bool checkPythonObject(const boost::python::object &pyObject, bool)
check if the python object can be converted to the given type.
bool iteratePythonObject(const boost::python::object &pyObject, Functor function)
Helper function to loop over a python object that implements the iterator concept and call a functor ...
Abstract base class for different kinds of events.
Helper construct for TMP that provides an index at compile time to recurse through type lists.
static std::string name()
type name.
static std::string name()
type name.
static std::string name()
type name.
static std::string name()
type name.
static std::string name()
type name.
static std::string name()
type name.
Converts a template argument into a string for corresponding Python type.
static std::string name()
type name.
static std::string name()
type name.
Recursively convert multiple types to type names (used for tuples).
static std::string name()
type name.