LSSTApplications  18.1.0
LSSTDataManagementBasePackage
_GenericMap.cc
Go to the documentation of this file.
1 /*
2  * This file is part of afw.
3  *
4  * Developed for the LSST Data Management System.
5  * This product includes software developed by the LSST Project
6  * (https://www.lsst.org).
7  * See the COPYRIGHT file at the top-level directory of this distribution
8  * for details of code ownership.
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program. If not, see <https://www.gnu.org/licenses/>.
22  */
23 
24 #include "pybind11/pybind11.h"
25 #include "pybind11/stl.h"
26 
27 #include <cstdint>
28 #include <exception>
29 #include <iostream>
30 #include <utility>
31 
32 #include "lsst/utils/python.h"
33 #include "lsst/pex/exceptions.h"
34 
37 
38 // From https://pybind11.readthedocs.io/en/stable/advanced/cast/stl.html#c-17-library-containers
39 // Not needed once we can use std::variant
40 namespace pybind11 {
41 namespace detail {
42 template <typename... Ts>
43 struct type_caster<boost::variant<Ts...>> : variant_caster<boost::variant<Ts...>> {};
44 template <>
45 struct visit_helper<boost::variant> {
46  template <typename... Args>
47  static auto call(Args&&... args) -> decltype(boost::apply_visitor(args...)) {
48  return boost::apply_visitor(args...);
49  }
50 };
51 } // namespace detail
52 } // namespace pybind11
53 
54 namespace py = pybind11;
55 using namespace py::literals;
56 
57 namespace lsst {
58 namespace afw {
59 namespace typehandling {
60 
61 namespace {
62 
63 // Type safety pointless in Python, use unsafe methods to avoid manual type checking
64 // https://pybind11.readthedocs.io/en/stable/advanced/classes.html#binding-protected-member-functions
65 template <typename K>
66 class Publicist : public MutableGenericMap<K> {
67 public:
68  using typename GenericMap<K>::ConstValueReference;
69  using GenericMap<K>::unsafeLookup;
70  using MutableGenericMap<K>::unsafeErase;
71 };
72 
73 template <typename K>
74 py::object get(GenericMap<K>& self, K const& key) {
75  auto callable = static_cast<typename Publicist<K>::ConstValueReference (GenericMap<K>::*)(K) const>(
76  &Publicist<K>::unsafeLookup);
77  return py::cast((self.*callable)(key));
78 };
79 
80 template <typename K>
81 void declareGenericMap(utils::python::WrapperCollection& wrappers, std::string const& suffix,
82  std::string const& key) {
83  using Class = GenericMap<K>;
84  using PyClass = py::class_<Class, std::shared_ptr<Class>>;
85 
86  std::string className = "GenericMap" + suffix;
87  // Give the class a custom docstring to avoid confusing Python users
88  std::string docstring =
89  "An abstract `~collections.abc.Mapping` for use when sharing a map between C++ and Python.\n" +
90  declareGenericMapRestrictions(className, key);
91  wrappers.wrapType(PyClass(wrappers.module, className.c_str(), docstring.c_str()), [](auto& mod,
92  auto& cls) {
93  // __str__ easier to implement in Python
94  // __repr__ easier to implement in Python
95  // __eq__ easier to implement in Python
96  // __ne__ easier to implement in Python
97  // For unknown reasons, overload_cast doesn't work
98  // cls.def("__contains__", py::overload_cast<K const&>(&Class::contains, py::const_), "key"_a);
99  cls.def("__contains__", static_cast<bool (Class::*)(K const&) const>(&Class::contains), "key"_a);
100 
101  cls.def("__getitem__",
102  [](Class& self, K const& key) {
103  try {
104  return get(self, key);
105  } catch (pex::exceptions::OutOfRangeError const& e) {
106  // pybind11 doesn't seem to recognize chained exceptions
107  std::stringstream buffer;
108  buffer << "Unknown key: " << key;
109  std::throw_with_nested(py::key_error(buffer.str()));
110  }
111  },
112  // Prevent segfaults when assigning a key<Storable> to Python variable, then deleting from map
113  // No existing code depends on being able to modify an item stored by value
114  "key"_a, py::return_value_policy::copy);
115  cls.def("get",
116  [](Class& self, K const& key, py::object const& def) {
117  try {
118  return get(self, key);
119  } catch (pex::exceptions::OutOfRangeError const& e) {
120  return def;
121  }
122  },
123  // Prevent segfaults when assigning a key<Storable> to Python variable, then deleting from map
124  // No existing code depends on being able to modify an item stored by value
125  "key"_a, "default"_a = py::none(), py::return_value_policy::copy);
126  cls.def("__iter__",
127  [](Class const& self) { return py::make_iterator(self.keys().begin(), self.keys().end()); },
128  py::keep_alive<0, 1>());
129  cls.def("__len__", &Class::size);
130  cls.def("__bool__", [](Class const& self) { return !self.empty(); });
131  // Can't wrap keys directly because pybind11 always copies vectors, so it won't be a view
132  // items easier to implement in Python
133  // values easier to implement in Python
134  });
135 }
136 
137 template <typename V, class PyClass>
138 void declareMutableGenericMapTypedMethods(PyClass& cls) {
139  using Class = typename PyClass::type;
140  cls.def("__setitem__",
141  [](Class& self, typename Class::key_type const& key, V const& value) {
142  // Need to delete previous key, which may not be of type V
143  // TODO: this method provides only basic exception safety
144  if (self.contains(key)) {
145  auto callable = &Publicist<typename Class::key_type>::unsafeErase;
146  (self.*callable)(key);
147  }
148  self.insert(key, value);
149  },
150  "key"_a, "value"_a);
151  // setdefault easier to implement in Python
152  // pop easier to implement in Python
153 }
154 
155 template <typename K>
156 void declareMutableGenericMap(utils::python::WrapperCollection& wrappers, std::string const& suffix,
157  std::string const& key) {
158  using Class = MutableGenericMap<K>;
159  using PyClass = py::class_<Class, std::shared_ptr<Class>, GenericMap<K>>;
160 
161  std::string className = "MutableGenericMap" + suffix;
162  // Give the class a custom docstring to avoid confusing Python users
163  std::string docstring =
164  "An abstract `~collections.abc.MutableMapping` for use when sharing a map between C++ and "
165  "Python.\n" +
166  declareGenericMapRestrictions(className, key);
167  wrappers.wrapType(PyClass(wrappers.module, className.c_str(), docstring.c_str()),
168  [](auto& mod, auto& cls) {
169  // Don't rewrap members of GenericMap
170  declareMutableGenericMapTypedMethods<std::shared_ptr<Storable>>(cls);
171  declareMutableGenericMapTypedMethods<bool>(cls);
172  declareMutableGenericMapTypedMethods<std::int32_t>(cls);
173  declareMutableGenericMapTypedMethods<std::int64_t>(cls);
174  declareMutableGenericMapTypedMethods<float>(cls);
175  declareMutableGenericMapTypedMethods<double>(cls);
176  declareMutableGenericMapTypedMethods<std::string>(cls);
177  cls.def("__delitem__",
178  [](Class& self, K const& key) {
179  if (self.contains(key)) {
180  auto callable = &Publicist<K>::unsafeErase;
181  (self.*callable)(key);
182  } else {
183  std::stringstream buffer;
184  buffer << "Unknown key: " << key;
185  throw py::key_error(buffer.str());
186  }
187  },
188  "key"_a);
189  cls.def("popitem", [](Class& self) {
190  if (!self.empty()) {
191  K key = self.keys().back();
192  auto result = std::make_pair(key, get(self, key));
193  auto callable = &Publicist<K>::unsafeErase;
194  (self.*callable)(key);
195  return result;
196  } else {
197  throw py::key_error("Cannot pop from empty GenericMap.");
198  }
199  });
200  cls.def("clear", &Class::clear);
201  // cls.def("update",, "other"_a);
202  });
203 }
204 } // namespace
205 
207  declareGenericMap<std::string>(wrappers, "S", "strings");
208  declareMutableGenericMap<std::string>(wrappers, "S", "strings");
209 }
210 
211 } // namespace typehandling
212 } // namespace afw
213 } // namespace lsst
Definition: Polygon.cc:25
bool contains(VertexIterator const begin, VertexIterator const end, UnitVector3d const &v)
py::object result
Definition: schema.cc:418
static auto call(Args &&... args) -> decltype(boost::apply_visitor(args...))
Definition: _GenericMap.cc:47
STL class.
A base class for image defects.
table::Key< int > type
Definition: Detector.cc:167
T throw_with_nested(T... args)
T str(T... args)
T make_pair(T... args)
Key< U > key
Definition: Schema.cc:281
T c_str(T... args)
A helper class for subdividing pybind11 module across multiple translation units (i.e.
Definition: python.h:242
std::string declareGenericMapRestrictions(std::string const &className, std::string const &keyName)
Definition: python.cc:32
int end
void wrapGenericMap(utils::python::WrapperCollection &wrappers)
Definition: _GenericMap.cc:206