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