LSSTApplications  17.0+11,17.0+34,17.0+56,17.0+57,17.0+59,17.0+7,17.0-1-g377950a+33,17.0.1-1-g114240f+2,17.0.1-1-g4d4fbc4+28,17.0.1-1-g55520dc+49,17.0.1-1-g5f4ed7e+52,17.0.1-1-g6dd7d69+17,17.0.1-1-g8de6c91+11,17.0.1-1-gb9095d2+7,17.0.1-1-ge9fec5e+5,17.0.1-1-gf4e0155+55,17.0.1-1-gfc65f5f+50,17.0.1-1-gfc6fb1f+20,17.0.1-10-g87f9f3f+1,17.0.1-11-ge9de802+16,17.0.1-16-ga14f7d5c+4,17.0.1-17-gc79d625+1,17.0.1-17-gdae4c4a+8,17.0.1-2-g26618f5+29,17.0.1-2-g54f2ebc+9,17.0.1-2-gf403422+1,17.0.1-20-g2ca2f74+6,17.0.1-23-gf3eadeb7+1,17.0.1-3-g7e86b59+39,17.0.1-3-gb5ca14a,17.0.1-3-gd08d533+40,17.0.1-30-g596af8797,17.0.1-4-g59d126d+4,17.0.1-4-gc69c472+5,17.0.1-6-g5afd9b9+4,17.0.1-7-g35889ee+1,17.0.1-7-gc7c8782+18,17.0.1-9-gc4bbfb2+3,w.2019.22
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