Line data Source code
1 : // SPDX-License-Identifier: BSD-3-Clause
2 : // Copyright (c) 2023 Scipp contributors (https://github.com/scipp)
3 : /// @file
4 : /// @author Simon Heybrock
5 : #pragma once
6 :
7 : #include <pybind11/typing.h>
8 :
9 : #include "scipp/dataset/dataset.h"
10 : #include "scipp/variable/variable_factory.h"
11 :
12 : #include "bind_operators.h"
13 : #include "pybind11.h"
14 : #include "view.h"
15 :
16 : namespace py = pybind11;
17 : using namespace scipp;
18 :
19 : template <template <class> class View, class T>
20 45 : void bind_helper_view(py::module &m, const std::string &name) {
21 45 : std::string suffix;
22 : if constexpr (std::is_same_v<View<T>, items_view<T>> ||
23 : std::is_same_v<View<T>, str_items_view<T>>)
24 15 : suffix = "_items_view";
25 : if constexpr (std::is_same_v<View<T>, values_view<T>>)
26 15 : suffix = "_values_view";
27 : if constexpr (std::is_same_v<View<T>, keys_view<T>> ||
28 : std::is_same_v<View<T>, str_keys_view<T>>)
29 15 : suffix = "_keys_view";
30 45 : auto cls =
31 90 : py::class_<View<T>>(m, (name + suffix).c_str())
32 45 : .def("__len__", &View<T>::size)
33 203 : .def("__repr__", [](const View<T> &self) { return self.tostring(); })
34 75 : .def("__str__", [](const View<T> &self) { return self.tostring(); })
35 45 : .def(
36 : "__iter__",
37 14988 : [](const View<T> &self) {
38 : return py::make_iterator(self.begin(), self.end(),
39 14988 : py::return_value_policy::move);
40 : },
41 90 : py::return_value_policy::move, py::keep_alive<0, 1>());
42 : if constexpr (!std::is_same_v<View<T>, values_view<T>>)
43 1474 : cls.def("__eq__", [](const View<T> &self, const View<T> &other) {
44 1444 : return self == other;
45 : });
46 45 : }
47 :
48 26082 : template <class D> auto cast_to_dict_key(const py::handle &obj) {
49 : using key_type = typename D::key_type;
50 : if constexpr (std::is_same_v<key_type, std::string>) {
51 71 : return obj.cast<std::string>();
52 : } else {
53 26011 : return key_type{obj.cast<std::string>()};
54 : }
55 : }
56 :
57 142 : template <class D> auto cast_to_dict_value(const py::handle &obj) {
58 : using val_type = typename D::mapped_type;
59 142 : return obj.cast<val_type>();
60 : }
61 :
62 : template <class T, class... Ignored>
63 18 : void bind_common_mutable_view_operators(pybind11::class_<T, Ignored...> &view) {
64 18 : view.def("__len__", &T::size)
65 0 : .def(
66 : "__getitem__",
67 43685 : [](const T &self, const std::string &key) {
68 82930 : return self[typename T::key_type{key}];
69 : },
70 18 : py::return_value_policy::copy)
71 18 : .def("__setitem__",
72 27703 : [](T &self, const std::string &key, const Variable &var) {
73 13867 : self.set(typename T::key_type{key}, var);
74 : })
75 18 : .def(
76 : "__delitem__",
77 5088 : [](T &self, const std::string &key) {
78 2549 : self.erase(typename T::key_type{key});
79 : },
80 0 : py::call_guard<py::gil_scoped_release>())
81 25958 : .def("__contains__", [](const T &self, const py::handle &key) {
82 : try {
83 25940 : return self.contains(cast_to_dict_key<T>(key));
84 0 : } catch (py::cast_error &) {
85 0 : return false; // if `key` is not a string, it cannot be contained
86 : }
87 : });
88 18 : }
89 :
90 : template <class T, class... Ignored, class Set>
91 15 : void bind_dict_update(pybind11::class_<T, Ignored...> &view, Set &&set_item) {
92 15 : view.def(
93 : "update",
94 290 : [set_item](T &self, const py::object &other, const py::kwargs &kwargs) {
95 : // Piggyback on dict to implement argument handling.
96 290 : py::dict args;
97 290 : if (!other.is_none()) {
98 281 : args.attr("update")(other, **kwargs);
99 : } else {
100 : // Cannot call dict.update(None, **kwargs) because dict.update
101 : // expects either an iterable as the positional argument or nothing
102 : // at all (nullptr). But we cannot express 'nothing' here.
103 : // The best we can do it pass None, which does not work.
104 9 : args.attr("update")(**kwargs);
105 : }
106 :
107 432 : for (const auto &[key, val] : args) {
108 142 : set_item(self, cast_to_dict_key<T>(key), cast_to_dict_value<T>(val));
109 : }
110 290 : },
111 30 : py::arg("other") = py::none(), py::pos_only(),
112 : R"doc(Update items from dict-like or iterable.
113 :
114 : If ``other`` has a .keys() method, then update does:
115 : ``for k in other.keys(): self[k] = other[k]``.
116 :
117 : If ``other`` is given but does not have a .keys() method, then update does:
118 : ``for k, v in other: self[k] = v``.
119 :
120 : In either case, this is followed by:
121 : ``for k in kwargs: self[k] = kwargs[k]``.
122 :
123 : See Also
124 : --------
125 : dict.update
126 : )doc");
127 15 : }
128 :
129 : template <class T, class... Ignored>
130 21 : void bind_pop(pybind11::class_<T, Ignored...> &view) {
131 21 : view.def(
132 : "_pop",
133 203 : [](T &self, const std::string &key) {
134 406 : return self.extract(typename T::key_type{key});
135 : },
136 21 : py::arg("k"));
137 21 : }
138 :
139 : template <class T, class... Ignored>
140 6 : void bind_set_aligned(pybind11::class_<T, Ignored...> &view) {
141 6 : view.def(
142 : "set_aligned",
143 : // cppcheck-suppress constParameter # False positive.
144 1168 : [](T &self, const std::string &key, const bool aligned) {
145 584 : self.set_aligned(typename T::key_type{key}, aligned);
146 : },
147 0 : py::arg("key"), py::arg("aligned"));
148 6 : }
149 :
150 : template <class T, class... Ignored>
151 12 : void bind_dict_clear(pybind11::class_<T, Ignored...> &view) {
152 19 : view.def("clear", [](T &self) {
153 7 : std::vector<typename T::key_type> keys;
154 19 : for (const auto &key : keys_view(self))
155 12 : keys.push_back(key);
156 19 : for (const auto &key : keys)
157 12 : self.erase(key);
158 7 : });
159 12 : }
160 :
161 : template <class T, class... Ignored>
162 12 : void bind_dict_popitem(pybind11::class_<T, Ignored...> &view) {
163 20 : view.def("popitem", [](T &self) {
164 2 : typename T::key_type key;
165 20 : for (const auto &k : keys_view(self))
166 12 : key = k;
167 8 : const auto item = py::cast(self.extract(key));
168 :
169 : using Pair =
170 : py::typing::Tuple<py::str, std::decay_t<decltype(self.extract(key))>>;
171 8 : Pair result(2);
172 : if constexpr (std::is_same_v<typename T::key_type, Dim>)
173 6 : result[0] = key.name();
174 : else
175 2 : result[0] = key;
176 8 : result[1] = item;
177 16 : return result;
178 8 : });
179 12 : }
180 :
181 : template <class T, class... Ignored>
182 12 : void bind_dict_copy(pybind11::class_<T, Ignored...> &view) {
183 12 : view.def(
184 : "copy",
185 8 : [](const T &self, const bool deep) {
186 8 : return deep ? copy(self) : self;
187 : },
188 12 : py::arg("deep") = true, py::call_guard<py::gil_scoped_release>(),
189 : R"(
190 : Return a (by default deep) copy.
191 :
192 : If `deep=True` (the default), a deep copy is made. Otherwise, a shallow
193 : copy is made, and the returned data (and meta data) values are new views
194 : of the data and meta data values of this object.)")
195 12 : .def(
196 0 : "__copy__", [](const T &self) { return self; },
197 0 : py::call_guard<py::gil_scoped_release>(), "Return a (shallow) copy.")
198 12 : .def(
199 : "__deepcopy__",
200 0 : [](const T &self, const py::typing::Dict<py::object, py::object> &) {
201 0 : return copy(self);
202 : },
203 12 : py::call_guard<py::gil_scoped_release>(), "Return a (deep) copy.");
204 12 : }
205 :
206 : template <class T, class... Ignored>
207 12 : void bind_is_edges(py::class_<T, Ignored...> &view) {
208 12 : view.def(
209 : "is_edges",
210 4966 : [](const T &self, const std::string &key,
211 : const std::optional<std::string> &dim) {
212 9654 : return self.is_edges(typename T::key_type{key},
213 14617 : dim.has_value() ? std::optional{Dim(*dim)}
214 9932 : : std::optional<Dim>{});
215 : },
216 24 : py::arg("key"), py::arg("dim") = std::nullopt,
217 : R"(Return True if the given key contains bin-edges in the given dim.)");
218 12 : }
219 :
220 : template <class T>
221 6 : void bind_mutable_view(py::module &m, const std::string &name,
222 : const std::string &docs) {
223 6 : py::class_<T> view(m, name.c_str(), docs.c_str());
224 6 : bind_common_mutable_view_operators(view);
225 6 : bind_inequality_to_operator<T>(view);
226 6 : bind_dict_update(view, [](T &self, const std::string &key,
227 20 : const Variable &value) { self.set(key, value); });
228 6 : bind_pop(view);
229 6 : bind_dict_clear(view);
230 6 : bind_dict_popitem(view);
231 6 : bind_dict_copy(view);
232 6 : bind_is_edges(view);
233 6 : view.def(
234 : "__iter__",
235 230 : [](const T &self) {
236 : return py::make_iterator(self.keys_begin(), self.keys_end(),
237 230 : py::return_value_policy::move);
238 : },
239 0 : py::keep_alive<0, 1>())
240 6 : .def(
241 1315 : "keys", [](T &self) { return keys_view(self); },
242 0 : py::keep_alive<0, 1>(), R"(view on self's keys)")
243 6 : .def(
244 2520 : "values", [](T &self) { return values_view(self); },
245 0 : py::keep_alive<0, 1>(), R"(view on self's values)")
246 0 : .def(
247 3279 : "items", [](T &self) { return items_view(self); },
248 6 : py::return_value_policy::move, py::keep_alive<0, 1>(),
249 : R"(view on self's items)")
250 6 : .def("_ipython_key_completions_",
251 1 : [](const T &self) {
252 1 : py::typing::List<py::str> out;
253 1 : const auto end = self.keys_end();
254 2 : for (auto it = self.keys_begin(); it != end; ++it) {
255 1 : out.append(*it);
256 : }
257 2 : return out;
258 0 : })
259 24 : .def("__repr__", [name](const T &self) { return to_string(self); })
260 8 : .def("__str__", [name](const T &self) { return to_string(self); });
261 6 : }
262 :
263 : template <class T>
264 6 : void bind_mutable_view_no_dim(py::module &m, const std::string &name,
265 : const std::string &docs) {
266 6 : py::class_<T> view(m, name.c_str(), docs.c_str());
267 6 : bind_common_mutable_view_operators(view);
268 6 : bind_inequality_to_operator<T>(view);
269 6 : bind_dict_update(view, [](T &self, const units::Dim &key,
270 111 : const Variable &value) { self.set(key, value); });
271 6 : bind_pop(view);
272 6 : bind_set_aligned(view);
273 6 : bind_dict_clear(view);
274 6 : bind_dict_popitem(view);
275 6 : bind_dict_copy(view);
276 6 : bind_is_edges(view);
277 6 : view.def(
278 : "__iter__",
279 455 : [](T &self) {
280 455 : auto keys_view = str_keys_view(self);
281 : return py::make_iterator(keys_view.begin(), keys_view.end(),
282 455 : py::return_value_policy::move);
283 : },
284 0 : py::keep_alive<0, 1>())
285 6 : .def(
286 3199 : "keys", [](T &self) { return str_keys_view(self); },
287 0 : py::keep_alive<0, 1>(), R"(view on self's keys)")
288 6 : .def(
289 92 : "values", [](T &self) { return values_view(self); },
290 0 : py::keep_alive<0, 1>(), R"(view on self's values)")
291 0 : .def(
292 6795 : "items", [](T &self) { return str_items_view(self); },
293 6 : py::return_value_policy::move, py::keep_alive<0, 1>(),
294 : R"(view on self's items)")
295 6 : .def("_ipython_key_completions_",
296 3 : [](const T &self) {
297 3 : py::typing::List<py::str> out;
298 3 : const auto end = self.keys_end();
299 11 : for (auto it = self.keys_begin(); it != end; ++it) {
300 8 : out.append(it->name());
301 : }
302 6 : return out;
303 0 : })
304 57 : .def("__repr__", [name](const T &self) { return to_string(self); })
305 13 : .def("__str__", [name](const T &self) { return to_string(self); });
306 6 : }
307 :
308 : template <class T, class... Ignored>
309 6 : void bind_data_array_properties(py::class_<T, Ignored...> &c) {
310 : if constexpr (std::is_same_v<T, DataArray>)
311 3 : c.def_property("name", &T::name, &T::setName,
312 : R"(The name of the held data.)");
313 : else
314 3 : c.def_property_readonly("name", &T::name, R"(The name of the held data.)");
315 6 : c.def_property(
316 : "data",
317 28888 : py::cpp_function([](T &self) { return self.data(); },
318 6 : py::return_value_policy::copy),
319 206 : [](T &self, const Variable &data) { self.setData(data); },
320 : R"(Underlying data item.)");
321 6 : c.def_property_readonly(
322 85427 : "coords", [](T &self) -> decltype(auto) { return self.coords(); },
323 : R"(Dict of coords.)");
324 6 : c.def_property_readonly(
325 7867 : "deprecated_meta", [](T &self) -> decltype(auto) { return self.meta(); },
326 : R"(Dict of coords and attrs.)");
327 6 : c.def_property_readonly(
328 : "deprecated_attrs",
329 4497 : [](T &self) -> decltype(auto) { return self.attrs(); },
330 : R"(Dict of attrs.)");
331 6 : c.def_property_readonly(
332 9595 : "masks", [](T &self) -> decltype(auto) { return self.masks(); },
333 : R"(Dict of masks.)");
334 6 : c.def("drop_coords", [](T &self, const std::string &coord_name) {
335 5 : std::vector<scipp::Dim> coord_names_c = {scipp::Dim{coord_name}};
336 10 : return self.drop_coords(coord_names_c);
337 5 : });
338 6 : c.def("drop_coords",
339 244 : [](T &self, const std::vector<std::string> &coord_names) {
340 244 : std::vector<scipp::Dim> coord_names_c;
341 244 : std::transform(coord_names.begin(), coord_names.end(),
342 : std::back_inserter(coord_names_c),
343 268 : [](const auto &name) { return scipp::Dim{name}; });
344 488 : return self.drop_coords(coord_names_c);
345 244 : });
346 6 : c.def("drop_masks", [](T &self, const std::string &mask_name) {
347 4 : return self.drop_masks(std::vector({mask_name}));
348 : });
349 6 : c.def("drop_masks", [](T &self, std::vector<std::string> &mask_names) {
350 5 : return self.drop_masks(mask_names);
351 : });
352 6 : c.def("deprecated_drop_attrs", [](T &self, std::string &attr_name) {
353 2 : std::vector<scipp::Dim> attr_names_c = {scipp::Dim{attr_name}};
354 4 : return self.drop_attrs(attr_names_c);
355 2 : });
356 6 : c.def("deprecated_drop_attrs",
357 4 : [](T &self, std::vector<std::string> &attr_names) {
358 4 : std::vector<scipp::Dim> attr_names_c;
359 4 : std::transform(attr_names.begin(), attr_names.end(),
360 : std::back_inserter(attr_names_c),
361 7 : [](const auto &name) { return scipp::Dim{name}; });
362 8 : return self.drop_attrs(attr_names_c);
363 4 : });
364 6 : }
|