LSST Applications g180d380827+107df2c2fa,g2079a07aa2+86d27d4dc4,g2305ad1205+27ad5d03fa,g2bbee38e9b+c6a8a0fb72,g337abbeb29+c6a8a0fb72,g33d1c0ed96+c6a8a0fb72,g3a166c0a6a+c6a8a0fb72,g3d1719c13e+cc4c7597ef,g487adcacf7+4b0be3ae0a,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+14d443d837,g62aa8f1a4b+588cc723a1,g674612935b+7a21c67e12,g858d7b2824+cc4c7597ef,g991b906543+cc4c7597ef,g99cad8db69+b90e8bab37,g9c22b2923f+e2510deafe,g9ddcbc5298+9a081db1e4,g9fbc5161f1+1dc7c5428e,ga1e77700b3+03d07e1c1f,gb0e22166c9+60f28cb32d,gb23b769143+cc4c7597ef,gba4ed39666+c2a2e4ac27,gbb8dafda3b+52e28152d7,gbd998247f1+585e252eca,gbeadb96d05+a322446fe6,gc120e1dc64+6a70fcc921,gc28159a63d+c6a8a0fb72,gc3e9b769f7+48c1343504,gcf0d15dbbd+7a21c67e12,gdaeeff99f8+f9a426f77a,ge6526c86ff+9349653ccd,ge79ae78c31+c6a8a0fb72,gee10cc3b42+585e252eca,w.2024.18
LSST Data Management Base Package
Loading...
Searching...
No Matches
_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;
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.
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
T c_str(T... args)
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:359
decltype(_typeToConstRef(std::declval< StorableType >())) ConstValueReference
A type-agnostic reference to the value stored inside the map.
Definition GenericMap.h:369
py::class_< PixelAreaBoundedField, std::shared_ptr< PixelAreaBoundedField >, BoundedField > PyClass
void wrapGenericMap(utils::python::WrapperCollection &wrappers)
std::string declareGenericMapRestrictions(std::string const &className, std::string const &keyName)
Definition python.cc:37