LCOV - code coverage report
Current view: top level - python - bind_data_array.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 202 221 91.4 %
Date: 2024-11-17 01:47:58 Functions: 195 246 79.3 %

          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 : }

Generated by: LCOV version 1.14