LSSTApplications  20.0.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  auto variant = (self.*callable)(key);
78 
79  // py::cast can't convert PolymorphicValue to Storable; do it by hand
80  PolymorphicValue const* storableHolder = boost::get<PolymorphicValue const&>(&variant);
81  if (storableHolder) {
82  Storable const& value = *storableHolder;
83  // Prevent segfaults when assigning a Key<Storable> to Python variable, then deleting from map
84  // No existing code depends on being able to modify an item stored by value
85  return py::cast(value.cloneStorable());
86  } else {
87  return py::cast(variant);
88  }
89 };
90 
91 template <typename K>
92 void declareGenericMap(utils::python::WrapperCollection& wrappers, std::string const& suffix,
93  std::string const& key) {
94  using Class = GenericMap<K>;
95  using PyClass = py::class_<Class, std::shared_ptr<Class>>;
96 
97  std::string className = "GenericMap" + suffix;
98  // Give the class a custom docstring to avoid confusing Python users
99  std::string docstring =
100  "An abstract `~collections.abc.Mapping` for use when sharing a map between C++ and Python.\n" +
102  wrappers.wrapType(PyClass(wrappers.module, className.c_str(), docstring.c_str()), [](auto& mod,
103  auto& cls) {
104  // __str__ easier to implement in Python
105  // __repr__ easier to implement in Python
106  // __eq__ easier to implement in Python
107  // __ne__ easier to implement in Python
108  // For unknown reasons, overload_cast doesn't work
109  // cls.def("__contains__", py::overload_cast<K const&>(&Class::contains, py::const_), "key"_a);
110  cls.def("__contains__", static_cast<bool (Class::*)(K const&) const>(&Class::contains), "key"_a);
111 
112  cls.def("__getitem__",
113  [](Class& self, K const& key) {
114  try {
115  return get(self, key);
116  } catch (pex::exceptions::OutOfRangeError const& e) {
117  // pybind11 doesn't seem to recognize chained exceptions
118  std::stringstream buffer;
119  buffer << "Unknown key: " << key;
120  std::throw_with_nested(py::key_error(buffer.str()));
121  }
122  },
123  "key"_a);
124  cls.def("get",
125  [](Class& self, K const& key, py::object const& def) {
126  try {
127  return get(self, key);
128  } catch (pex::exceptions::OutOfRangeError const& e) {
129  return def;
130  }
131  },
132  // Prevent segfaults when assigning a key<Storable> to Python variable, then deleting from map
133  // No existing code depends on being able to modify an item stored by value
134  "key"_a, "default"_a = py::none(), py::return_value_policy::copy);
135  cls.def("__iter__",
136  [](Class const& self) { return py::make_iterator(self.keys().begin(), self.keys().end()); },
137  py::keep_alive<0, 1>());
138  cls.def("__len__", &Class::size);
139  cls.def("__bool__", [](Class const& self) { return !self.empty(); });
140  // Can't wrap keys directly because pybind11 always copies vectors, so it won't be a view
141  // items easier to implement in Python
142  // values easier to implement in Python
143  });
144 }
145 
146 template <typename V, class PyClass>
147 void declareMutableGenericMapTypedMethods(PyClass& cls) {
148  using Class = typename PyClass::type;
149  cls.def("__setitem__",
150  [](Class& self, typename Class::key_type const& key, V const& value) {
151  // Need to delete previous key, which may not be of type V
152  // TODO: this method provides only basic exception safety
153  if (self.contains(key)) {
154  auto callable = &Publicist<typename Class::key_type>::unsafeErase;
155  (self.*callable)(key);
156  }
157  self.insert(key, value);
158  },
159  "key"_a, "value"_a);
160  // setdefault easier to implement in Python
161  // pop easier to implement in Python
162 }
163 
164 template <typename K>
165 void declareMutableGenericMap(utils::python::WrapperCollection& wrappers, std::string const& suffix,
166  std::string const& key) {
167  using Class = MutableGenericMap<K>;
168  using PyClass = py::class_<Class, std::shared_ptr<Class>, GenericMap<K>>;
169 
170  std::string className = "MutableGenericMap" + suffix;
171  // Give the class a custom docstring to avoid confusing Python users
172  std::string docstring =
173  "An abstract `~collections.abc.MutableMapping` for use when sharing a map between C++ and "
174  "Python.\n" +
176  wrappers.wrapType(PyClass(wrappers.module, className.c_str(), docstring.c_str()),
177  [](auto& mod, auto& cls) {
178  // Don't rewrap members of GenericMap
179  declareMutableGenericMapTypedMethods<std::shared_ptr<Storable const>>(cls);
180  declareMutableGenericMapTypedMethods<bool>(cls);
181  // TODO: int32 and float are suppressed for now, see DM-21268
182  declareMutableGenericMapTypedMethods<std::int64_t>(cls); // chosen for builtins.int
183  declareMutableGenericMapTypedMethods<std::int32_t>(cls);
184  declareMutableGenericMapTypedMethods<double>(cls); // chosen for builtins.float
185  declareMutableGenericMapTypedMethods<float>(cls);
186  declareMutableGenericMapTypedMethods<std::string>(cls);
187  cls.def("__delitem__",
188  [](Class& self, K const& key) {
189  if (self.contains(key)) {
190  auto callable = &Publicist<K>::unsafeErase;
191  (self.*callable)(key);
192  } else {
193  std::stringstream buffer;
194  buffer << "Unknown key: " << key;
195  throw py::key_error(buffer.str());
196  }
197  },
198  "key"_a);
199  cls.def("popitem", [](Class& self) {
200  if (!self.empty()) {
201  K key = self.keys().back();
202  auto result = std::make_pair(key, get(self, key));
203  auto callable = &Publicist<K>::unsafeErase;
204  (self.*callable)(key);
205  return result;
206  } else {
207  throw py::key_error("Cannot pop from empty GenericMap.");
208  }
209  });
210  cls.def("clear", &Class::clear);
211  // cls.def("update",, "other"_a);
212  });
213 }
214 } // namespace
215 
217  declareGenericMap<std::string>(wrappers, "S", "strings");
218  declareMutableGenericMap<std::string>(wrappers, "S", "strings");
219 }
220 
221 } // namespace typehandling
222 } // namespace afw
223 } // namespace lsst
lsst::afw::typehandling::declareGenericMapRestrictions
std::string declareGenericMapRestrictions(std::string const &className, std::string const &keyName)
Definition: python.cc:32
std::string
STL class.
lsst::meas::algorithms.psfSelectionFromMatchList.args
list args
Definition: psfSelectionFromMatchList.py:27
GenericMap.h
pybind11::detail::visit_helper< boost::variant >::call
static auto call(Args &&... args) -> decltype(boost::apply_visitor(args...))
Definition: _GenericMap.cc:47
lsst::afw
Definition: imageAlgorithm.dox:1
astshim.keyMap.keyMapContinued.keys
def keys(self)
Definition: keyMapContinued.py:6
boost
Definition: Polygon.cc:25
end
int end
Definition: BoundedField.cc:105
lsst::afw::geom.transform.transformContinued.cls
cls
Definition: transformContinued.py:33
astshim.fitsChanContinued.contains
def contains(self, name)
Definition: fitsChanContinued.py:127
std::string::c_str
T c_str(T... args)
PyClass
py::class_< ProductBoundedField, std::shared_ptr< ProductBoundedField >, BoundedField > PyClass
Definition: productBoundedField.cc:32
lsst::utils::python::WrapperCollection
A helper class for subdividing pybind11 module across multiple translation units (i....
Definition: python.h:242
python.h
result
py::object result
Definition: _schema.cc:429
lsst
A base class for image defects.
Definition: imageAlgorithm.dox:1
python.h
std::begin
T begin(T... args)
type
table::Key< int > type
Definition: Detector.cc:163
key
Key< U > key
Definition: Schema.cc:281
pybind11
Definition: _GenericMap.cc:40
std::make_pair
T make_pair(T... args)
lsst::afw::typehandling::wrapGenericMap
void wrapGenericMap(utils::python::WrapperCollection &wrappers)
Definition: _GenericMap.cc:216
exceptions.h