LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
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
39namespace py = pybind11;
40using namespace py::literals;
41
42namespace lsst {
43namespace afw {
44namespace typehandling {
45
46namespace {
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
50template <typename K>
51class Publicist : public MutableGenericMap<K> {
52public:
54 using typename GenericMap<K>::StorableType;
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.
69template <typename K>
70class Getter {
71public:
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
96template <typename K>
97void 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
151template <typename V, class PyClass>
152void 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
169template <typename K>
170void 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
221void 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)
virtual ConstValueReference unsafeLookup(K key) const =0
Return a reference to the mapped value of the element with key equal to key.
decltype(_typeToConstRef(std::declval< StorableType >())) ConstValueReference
A type-agnostic reference to the value stored inside the map.
Definition: GenericMap.h:369
std::variant< bool, int, long, long long, float, double, std::string, PolymorphicValue, std::shared_ptr< Storable const > > StorableType
The types that can be stored in a map.
Definition: GenericMap.h:360
virtual bool unsafeErase(K key)=0
Remove the mapping for a key from this map, if it exists.
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.