GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: bindings/python/crocoddyl/utils/map-converter.hpp Lines: 8 45 17.8 %
Date: 2024-02-13 11:12:33 Branches: 4 76 5.3 %

Line Branch Exec Source
1
///////////////////////////////////////////////////////////////////////////////
2
// BSD 3-Clause License
3
//
4
// Copyright (C) 2019-2022, LAAS-CNRS, University of Edinburgh, INRIA
5
// Copyright note valid unless otherwise stated in individual files.
6
// All rights reserved.
7
///////////////////////////////////////////////////////////////////////////////
8
9
#ifndef BINDINGS_PYTHON_CROCODDYL_UTILS_MAP_CONVERTER_HPP_
10
#define BINDINGS_PYTHON_CROCODDYL_UTILS_MAP_CONVERTER_HPP_
11
12
#include <boost/python/stl_iterator.hpp>
13
#include <boost/python/suite/indexing/map_indexing_suite.hpp>
14
#include <boost/python/to_python_converter.hpp>
15
#include <map>
16
17
#include "python/crocoddyl/utils/vector-converter.hpp"
18
19
namespace crocoddyl {
20
namespace python {
21
22
namespace bp = boost::python;
23
24
/**
25
 * @brief Create a pickle interface for the std::map
26
 *
27
 * @param[in] Container  Map type to be pickled
28
 * \sa Pickle
29
 */
30
template <typename Container>
31
struct PickleMap : public eigenpy::PickleVector<Container> {
32
  static void setstate(bp::object op, bp::tuple tup) {
33
    Container& o = bp::extract<Container&>(op)();
34
    bp::stl_input_iterator<typename Container::value_type> begin(tup[0]), end;
35
    o.insert(begin, end);
36
  }
37
};
38
39
/// Conversion from dict to map solution proposed in
40
/// https://stackoverflow.com/questions/6116345/boostpython-possible-to-automatically-convert-from-dict-stdmap
41
/// This template encapsulates the conversion machinery.
42
template <typename Container>
43
struct dict_to_map {
44
160
  static void register_converter() {
45
160
    bp::converter::registry::push_back(&dict_to_map::convertible,
46
                                       &dict_to_map::construct,
47
                                       bp::type_id<Container>());
48
160
  }
49
50
  /// Check if conversion is possible
51
  static void* convertible(PyObject* object) {
52
    // Check if it is a list
53
    if (!PyObject_GetIter(object)) return 0;
54
    return object;
55
  }
56
57
  /// Perform the conversion
58
  static void construct(PyObject* object,
59
                        bp::converter::rvalue_from_python_stage1_data* data) {
60
    // convert the PyObject pointed to by `object` to a bp::dict
61
    bp::handle<> handle(bp::borrowed(object));  // "smart ptr"
62
    bp::dict dict(handle);
63
64
    // get a pointer to memory into which we construct the map
65
    // this is provided by the Python runtime
66
    typedef bp::converter::rvalue_from_python_storage<Container> storage_type;
67
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
68
69
    // placement-new allocate the result
70
    new (storage) Container();
71
72
    // iterate over the dictionary `dict`, fill up the map `map`
73
    Container& map(*(static_cast<Container*>(storage)));
74
    bp::list keys(dict.keys());
75
    int keycount(static_cast<int>(bp::len(keys)));
76
    for (int i = 0; i < keycount; ++i) {
77
      // get the key
78
      bp::object keyobj(keys[i]);
79
      bp::extract<typename Container::key_type> keyproxy(keyobj);
80
      if (!keyproxy.check()) {
81
        PyErr_SetString(PyExc_KeyError, "Bad key type");
82
        bp::throw_error_already_set();
83
      }
84
      typename Container::key_type key = keyproxy();
85
86
      // get the corresponding value
87
      bp::object valobj(dict[keyobj]);
88
      bp::extract<typename Container::mapped_type> valproxy(valobj);
89
      if (!valproxy.check()) {
90
        PyErr_SetString(PyExc_ValueError, "Bad value type");
91
        bp::throw_error_already_set();
92
      }
93
      typename Container::mapped_type val = valproxy();
94
      map[key] = val;
95
    }
96
97
    // remember the location for later
98
    data->convertible = storage;
99
  }
100
101
  static bp::dict todict(Container& self) {
102
    bp::dict dict;
103
    typename Container::const_iterator it;
104
    for (it = self.begin(); it != self.end(); ++it) {
105
      dict.setdefault(it->first, it->second);
106
    }
107
    return dict;
108
  }
109
};
110
111
/**
112
 * @brief Expose an std::map from a type given as template argument.
113
 *
114
 * @param[in] T          Type to expose as std::map<T>.
115
 * @param[in] Compare    Type for the Compare in std::map<T,Compare,Allocator>.
116
 * @param[in] Allocator  Type for the Allocator in
117
 * std::map<T,Compare,Allocator>.
118
 * @param[in] NoProxy    When set to false, the elements will be copied when
119
 * returned to Python.
120
 */
121
template <class Key, class T, class Compare = std::less<Key>,
122
          class Allocator = std::allocator<std::pair<const Key, T> >,
123
          bool NoProxy = false>
124
struct StdMapPythonVisitor
125
    : public bp::map_indexing_suite<
126
          typename std::map<Key, T, Compare, Allocator>, NoProxy>,
127
      public dict_to_map<std::map<Key, T, Compare, Allocator> > {
128
  typedef std::map<Key, T, Compare, Allocator> Container;
129
  typedef dict_to_map<Container> FromPythonDictConverter;
130
131
160
  static void expose(const std::string& class_name,
132
                     const std::string& doc_string = "") {
133
    namespace bp = bp;
134
135
    bp::class_<Container>(class_name.c_str(), doc_string.c_str())
136
        .def(StdMapPythonVisitor())
137
160
        .def("todict", &FromPythonDictConverter::todict, bp::arg("self"),
138
             "Returns the std::map as a Python dictionary.")
139

160
        .def_pickle(PickleMap<Container>());
140
    // Register conversion
141
160
    FromPythonDictConverter::register_converter();
142
160
  }
143
};
144
145
}  // namespace python
146
}  // namespace crocoddyl
147
148
#endif  // BINDINGS_PYTHON_CROCODDYL_UTILS_MAP_CONVERTER_HPP_