LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
_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 #include <variant>
32 
33 #include "lsst/utils/python.h"
34 #include "lsst/pex/exceptions.h"
35 
38 
39 namespace py = pybind11;
40 using namespace py::literals;
41 
42 namespace lsst {
43 namespace afw {
44 namespace typehandling {
45 
46 namespace {
47 
48 // Type safety pointless in Python, use unsafe methods to avoid manual type checking
49 // https://pybind11.readthedocs.io/en/stable/advanced/classes.html#binding-protected-member-functions
50 template <typename K>
51 class Publicist : public MutableGenericMap<K> {
52 public:
53  using typename GenericMap<K>::ConstValueReference;
54  using typename GenericMap<K>::StorableType;
55  using GenericMap<K>::unsafeLookup;
56  using MutableGenericMap<K>::unsafeErase;
57 };
58 
59 // A combination std::variant visitor and helper function (apply()) that
60 // invokes it, to implement item lookup for Python.
61 //
62 // We copy/clone because there's no good way to prevent other ++ calls from
63 // destroying the any reference we might give to Python.
64 //
65 // Pybind11 does have its own automatic caster for std::variant, but it doesn't
66 // handle reference_wrapper types within it (let alone polymorphic Storables),
67 // and since we need a visitor to deal with that, there's no point in going
68 // back to std::variant instead of straight to Python.
69 template <typename K>
70 class Getter {
71 public:
72 
73  template <typename T>
74  py::object operator()(std::reference_wrapper<T const> const & value) const {
75  T copy(value);
76  return py::cast(copy);
77  }
78 
79  py::object operator()(std::reference_wrapper<PolymorphicValue const> const & value) const {
80  // Specialization for polymorphic Storables: extract the Storable,
81  // clone it explicitly, and return that (letting pybind11's downcasting
82  // take over from there).
83  Storable const& storable = value.get();
84  return py::cast(storable.cloneStorable());
85  }
86 
87  py::object apply(GenericMap<K>& self, K const& key) const {
88  auto callable = static_cast<typename Publicist<K>::ConstValueReference (GenericMap<K>::*)(K) const>(
89  &Publicist<K>::unsafeLookup);
90  auto variant = (self.*callable)(key);
91  return std::visit(*this, variant);
92  }
93 
94 };
95 
96 template <typename K>
97 void declareGenericMap(utils::python::WrapperCollection& wrappers, std::string const& suffix,
98  std::string const& key) {
99  using Class = GenericMap<K>;
100  using PyClass = py::class_<Class, std::shared_ptr<Class>>;
101 
102  std::string className = "GenericMap" + suffix;
103  // Give the class a custom docstring to avoid confusing Python users
104  std::string docstring =
105  "An abstract `~collections.abc.Mapping` for use when sharing a map between C++ and Python.\n" +
106  declareGenericMapRestrictions(className, key);
107  wrappers.wrapType(PyClass(wrappers.module, className.c_str(), docstring.c_str()), [](auto& mod,
108  auto& cls) {
109  // __str__ easier to implement in Python
110  // __repr__ easier to implement in Python
111  // __eq__ easier to implement in Python
112  // __ne__ easier to implement in Python
113  // For unknown reasons, overload_cast doesn't work
114  // cls.def("__contains__", py::overload_cast<K const&>(&Class::contains, py::const_), "key"_a);
115  cls.def("__contains__", static_cast<bool (Class::*)(K const&) const>(&Class::contains), "key"_a);
116 
117  cls.def("__getitem__",
118  [](Class& self, K const& key) {
119  try {
120  return Getter<K>().apply(self, key);
121  } catch (pex::exceptions::OutOfRangeError const& e) {
122  // pybind11 doesn't seem to recognize chained exceptions
123  std::stringstream buffer;
124  buffer << "Unknown key: " << key;
125  std::throw_with_nested(py::key_error(buffer.str()));
126  }
127  },
128  "key"_a);
129  cls.def("get",
130  [](Class& self, K const& key, py::object const& def) {
131  try {
132  return Getter<K>().apply(self, key);
133  } catch (pex::exceptions::OutOfRangeError const& e) {
134  return def;
135  }
136  },
137  // Prevent segfaults when assigning a key<Storable> to Python variable, then deleting from map
138  // No existing code depends on being able to modify an item stored by value
139  "key"_a, "default"_a = py::none(), py::return_value_policy::copy);
140  cls.def("__iter__",
141  [](Class const& self) { return py::make_iterator(self.keys().begin(), self.keys().end()); },
142  py::keep_alive<0, 1>());
143  cls.def("__len__", &Class::size);
144  cls.def("__bool__", [](Class const& self) { return !self.empty(); });
145  // Can't wrap keys directly because pybind11 always copies vectors, so it won't be a view
146  // items easier to implement in Python
147  // values easier to implement in Python
148  });
149 }
150 
151 template <typename V, class PyClass>
152 void declareMutableGenericMapTypedMethods(PyClass& cls) {
153  using Class = typename PyClass::type;
154  cls.def("__setitem__",
155  [](Class& self, typename Class::key_type const& key, V const& value) {
156  // Need to delete previous key, which may not be of type V
157  // TODO: this method provides only basic exception safety
158  if (self.contains(key)) {
159  auto callable = &Publicist<typename Class::key_type>::unsafeErase;
160  (self.*callable)(key);
161  }
162  self.insert(key, value);
163  },
164  "key"_a, "value"_a);
165  // setdefault easier to implement in Python
166  // pop easier to implement in Python
167 }
168 
169 template <typename K>
170 void declareMutableGenericMap(utils::python::WrapperCollection& wrappers, std::string const& suffix,
171  std::string const& key) {
172  using Class = MutableGenericMap<K>;
173  using PyClass = py::class_<Class, std::shared_ptr<Class>, GenericMap<K>>;
174 
175  std::string className = "MutableGenericMap" + suffix;
176  // Give the class a custom docstring to avoid confusing Python users
177  std::string docstring =
178  "An abstract `~collections.abc.MutableMapping` for use when sharing a map between C++ and "
179  "Python.\n" +
180  declareGenericMapRestrictions(className, key);
181  wrappers.wrapType(PyClass(wrappers.module, className.c_str(), docstring.c_str()),
182  [](auto& mod, auto& cls) {
183  // Don't rewrap members of GenericMap
184  declareMutableGenericMapTypedMethods<std::shared_ptr<Storable const>>(cls);
185  declareMutableGenericMapTypedMethods<bool>(cls);
186  // TODO: int32 and float are suppressed for now, see DM-21268
187  declareMutableGenericMapTypedMethods<std::int64_t>(cls); // chosen for builtins.int
188  declareMutableGenericMapTypedMethods<std::int32_t>(cls);
189  declareMutableGenericMapTypedMethods<double>(cls); // chosen for builtins.float
190  declareMutableGenericMapTypedMethods<float>(cls);
191  declareMutableGenericMapTypedMethods<std::string>(cls);
192  cls.def("__delitem__",
193  [](Class& self, K const& key) {
194  if (self.contains(key)) {
195  auto callable = &Publicist<K>::unsafeErase;
196  (self.*callable)(key);
197  } else {
198  std::stringstream buffer;
199  buffer << "Unknown key: " << key;
200  throw py::key_error(buffer.str());
201  }
202  },
203  "key"_a);
204  cls.def("popitem", [](Class& self) {
205  if (!self.empty()) {
206  K key = self.keys().back();
207  auto result = std::make_pair(key, Getter<K>().apply(self, key));
208  auto callable = &Publicist<K>::unsafeErase;
209  (self.*callable)(key);
210  return result;
211  } else {
212  throw py::key_error("Cannot pop from empty GenericMap.");
213  }
214  });
215  cls.def("clear", &Class::clear);
216  // cls.def("update",, "other"_a);
217  });
218 }
219 } // namespace
220 
221 void wrapGenericMap(utils::python::WrapperCollection& wrappers) {
222  declareGenericMap<std::string>(wrappers, "S", "strings");
223  declareMutableGenericMap<std::string>(wrappers, "S", "strings");
224 }
225 
226 } // namespace typehandling
227 } // namespace afw
228 } // namespace lsst
py::object result
Definition: _schema.cc:429
int end
table::Key< int > type
Definition: Detector.cc:163
T begin(T... args)
T c_str(T... args)
T copy(T... args)
T make_pair(T... args)
py::class_< PixelAreaBoundedField, std::shared_ptr< PixelAreaBoundedField >, BoundedField > PyClass
void wrapGenericMap(utils::python::WrapperCollection &wrappers)
Definition: _GenericMap.cc:221
std::string declareGenericMapRestrictions(std::string const &className, std::string const &keyName)
Definition: python.cc:32
A base class for image defects.