From 17ff682236512e0b3cf8974d1440382760ea4a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Tue, 25 Oct 2022 16:31:30 +0200 Subject: [PATCH] Extract bind_container to hpp --- include/openPMD/binding/python/Container.hpp | 136 +++++++++++++++++++ src/binding/python/BaseRecord.cpp | 47 +++++-- src/binding/python/Container.cpp | 119 ++-------------- src/binding/python/openPMD.cpp | 10 +- 4 files changed, 188 insertions(+), 124 deletions(-) create mode 100644 include/openPMD/binding/python/Container.hpp diff --git a/include/openPMD/binding/python/Container.hpp b/include/openPMD/binding/python/Container.hpp new file mode 100644 index 0000000000..e04ace2e5c --- /dev/null +++ b/include/openPMD/binding/python/Container.hpp @@ -0,0 +1,136 @@ +/* Copyright 2018-2022 Axel Huebl and Franz Poeschel + * + * This file is part of openPMD-api. + * + * openPMD-api is free software: you can redistribute it and/or modify + * it under the terms of of either the GNU General Public License or + * the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * openPMD-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License and the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * and the GNU Lesser General Public License along with openPMD-api. + * If not, see . + * + * The function `bind_container` is based on std_bind.h in pybind11 + * Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob + * + * BSD-style license, see pybind11 LICENSE file. + */ + +#pragma once + +#include "openPMD/backend/Attributable.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace py = pybind11; + +namespace openPMD::detail +{ +/* based on std_bind.h in pybind11 + * + * Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob + * + * BSD-style license, see pybind11 LICENSE file. + */ +template +Class_ bind_container(Class_ &cl, std::string const &name) +{ + using KeyType = typename Map::key_type; + using MappedType = typename Map::mapped_type; + + // Register stream insertion operator (if possible) + py::detail::map_if_insertion_operator(cl, name); + + cl.def( + "__bool__", + [](const Map &m) -> bool { return !m.empty(); }, + "Check whether the container is nonempty"); + + cl.def( + "__iter__", + [](Map &m) { return py::make_key_iterator(m.begin(), m.end()); }, + // keep container alive while iterator exists + py::keep_alive<0, 1>()); + + cl.def( + "items", + [](Map &m) { return py::make_iterator(m.begin(), m.end()); }, + // keep container alive while iterator exists + py::keep_alive<0, 1>()); + + // keep same policy as Container class: missing keys are created + cl.def( + "__getitem__", + [](Map &m, KeyType const &k) -> MappedType & { return m[k]; }, + // ref + keepalive + py::return_value_policy::reference_internal); + + // Assignment provided only if the type is copyable + py::detail::map_assignment(cl); + + cl.def("__delitem__", [](Map &m, KeyType const &k) { + auto it = m.find(k); + if (it == m.end()) + throw py::key_error(); + m.erase(it); + }); + + cl.def("__len__", &Map::size); + + cl.def("_ipython_key_completions_", [](Map &m) { + auto l = py::list(); + for (const auto &myPair : m) + l.append(myPair.first); + return l; + }); + + return cl; +} + +template < + typename Map, + typename holder_type = std::unique_ptr, + typename... Args> +py::class_ create_and_bind_container( + py::handle scope, std::string const &name, Args &&...args) +{ + using KeyType = typename Map::key_type; + using MappedType = typename Map::mapped_type; + using Class_ = py::class_; + + // If either type is a non-module-local bound type then make the map + // binding non-local as well; otherwise (e.g. both types are either + // module-local or converting) the map will be module-local. + auto tinfo = py::detail::get_type_info(typeid(MappedType)); + bool local = !tinfo || tinfo->module_local; + if (local) + { + tinfo = py::detail::get_type_info(typeid(KeyType)); + local = !tinfo || tinfo->module_local; + } + + Class_ cl( + scope, + name.c_str(), + py::module_local(local), + std::forward(args)...); + + // maybe move this to bind_container + cl.def(py::init()); + return bind_container(cl, name); +} +} // namespace openPMD::detail diff --git a/src/binding/python/BaseRecord.cpp b/src/binding/python/BaseRecord.cpp index b8c9bcc545..865c0ca484 100644 --- a/src/binding/python/BaseRecord.cpp +++ b/src/binding/python/BaseRecord.cpp @@ -26,6 +26,7 @@ #include "openPMD/backend/Container.hpp" #include "openPMD/backend/MeshRecordComponent.hpp" #include "openPMD/backend/PatchRecordComponent.hpp" +#include "openPMD/binding/python/Container.hpp" #include "openPMD/binding/python/UnitDimension.hpp" namespace py = pybind11; @@ -47,19 +48,35 @@ Returns true if this record only contains a single component. // .def_property_readonly( // "scalar", &BaseRecord::scalar, doc_scalar); - py::class_, Container >( - m, "Base_Record_Record_Component") - .def_property_readonly( - "scalar", &BaseRecord::scalar, doc_scalar); - py::class_< - BaseRecord, - Container >(m, "Base_Record_Mesh_Record_Component") - .def_property_readonly( - "scalar", &BaseRecord::scalar, doc_scalar); - py::class_< - BaseRecord, - Container >( - m, "Base_Record_Patch_Record_Component") - .def_property_readonly( - "scalar", &BaseRecord::scalar, doc_scalar); + { + auto cl = + py::class_, RecordComponent>( + m, "Base_Record_Record_Component") + .def_property_readonly( + "scalar", &BaseRecord::scalar, doc_scalar); + detail::bind_container>( + cl, "Base_Record_Record_Component"); + } + { + auto cl = + py::class_, MeshRecordComponent>( + m, "Base_Record_Mesh_Record_Component") + .def_property_readonly( + "scalar", + &BaseRecord::scalar, + doc_scalar); + detail::bind_container>( + cl, "Base_Record_Mesh_Record_Component"); + } + { + auto cl = + py::class_, PatchRecordComponent>( + m, "Base_Record_Patch_Record_Component") + .def_property_readonly( + "scalar", + &BaseRecord::scalar, + doc_scalar); + detail::bind_container>( + cl, "Base_Record_Patch_Record_Component"); + } } diff --git a/src/binding/python/Container.cpp b/src/binding/python/Container.cpp index 357cb0bba2..e8ebf5c27b 100644 --- a/src/binding/python/Container.cpp +++ b/src/binding/python/Container.cpp @@ -25,8 +25,6 @@ */ #include -#include -#include #include "openPMD/Iteration.hpp" #include "openPMD/Mesh.hpp" @@ -39,102 +37,11 @@ #include "openPMD/backend/MeshRecordComponent.hpp" #include "openPMD/backend/PatchRecord.hpp" #include "openPMD/backend/PatchRecordComponent.hpp" - -#include -#include -#include +#include "openPMD/binding/python/Container.hpp" namespace py = pybind11; using namespace openPMD; -namespace detail -{ -/* based on std_bind.h in pybind11 - * - * Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob - * - * BSD-style license, see pybind11 LICENSE file. - */ -template < - typename Map, - typename holder_type = std::unique_ptr, - typename... Args> -py::class_ -bind_container(py::handle scope, std::string const &name, Args &&...args) -{ - using KeyType = typename Map::key_type; - using MappedType = typename Map::mapped_type; - using Class_ = py::class_; - - // If either type is a non-module-local bound type then make the map - // binding non-local as well; otherwise (e.g. both types are either - // module-local or converting) the map will be module-local. - auto tinfo = py::detail::get_type_info(typeid(MappedType)); - bool local = !tinfo || tinfo->module_local; - if (local) - { - tinfo = py::detail::get_type_info(typeid(KeyType)); - local = !tinfo || tinfo->module_local; - } - - Class_ cl( - scope, - name.c_str(), - py::module_local(local), - std::forward(args)...); - - cl.def(py::init()); - - // Register stream insertion operator (if possible) - py::detail::map_if_insertion_operator(cl, name); - - cl.def( - "__bool__", - [](const Map &m) -> bool { return !m.empty(); }, - "Check whether the container is nonempty"); - - cl.def( - "__iter__", - [](Map &m) { return py::make_key_iterator(m.begin(), m.end()); }, - // keep container alive while iterator exists - py::keep_alive<0, 1>()); - - cl.def( - "items", - [](Map &m) { return py::make_iterator(m.begin(), m.end()); }, - // keep container alive while iterator exists - py::keep_alive<0, 1>()); - - // keep same policy as Container class: missing keys are created - cl.def( - "__getitem__", - [](Map &m, KeyType const &k) -> MappedType & { return m[k]; }, - // ref + keepalive - py::return_value_policy::reference_internal); - - // Assignment provided only if the type is copyable - py::detail::map_assignment(cl); - - cl.def("__delitem__", [](Map &m, KeyType const &k) { - auto it = m.find(k); - if (it == m.end()) - throw py::key_error(); - m.erase(it); - }); - - cl.def("__len__", &Map::size); - - cl.def("_ipython_key_completions_", [](Map &m) { - auto l = py::list(); - for (const auto &myPair : m) - l.append(myPair.first); - return l; - }); - - return cl; -} -} // namespace detail - using PyIterationContainer = Container; using PyMeshContainer = Container; using PyPartContainer = Container; @@ -158,19 +65,23 @@ PYBIND11_MAKE_OPAQUE(PyBaseRecordComponentContainer) void init_Container(py::module &m) { - ::detail::bind_container(m, "Iteration_Container"); - ::detail::bind_container(m, "Mesh_Container"); - ::detail::bind_container(m, "Particle_Container"); - ::detail::bind_container(m, "Particle_Patches_Container"); - ::detail::bind_container(m, "Record_Container"); - ::detail::bind_container( + ::detail::create_and_bind_container( + m, "Iteration_Container"); + ::detail::create_and_bind_container(m, "Mesh_Container"); + ::detail::create_and_bind_container( + m, "Particle_Container"); + ::detail::create_and_bind_container( + m, "Particle_Patches_Container"); + ::detail::create_and_bind_container( + m, "Record_Container"); + ::detail::create_and_bind_container( m, "Patch_Record_Container"); - ::detail::bind_container( + ::detail::create_and_bind_container( m, "Record_Component_Container"); - ::detail::bind_container( + ::detail::create_and_bind_container( m, "Mesh_Record_Component_Container"); - ::detail::bind_container( + ::detail::create_and_bind_container( m, "Patch_Record_Component_Container"); - ::detail::bind_container( + ::detail::create_and_bind_container( m, "Base_Record_Component_Container"); } diff --git a/src/binding/python/openPMD.cpp b/src/binding/python/openPMD.cpp index 5133c66d21..1c8c817476 100644 --- a/src/binding/python/openPMD.cpp +++ b/src/binding/python/openPMD.cpp @@ -91,21 +91,21 @@ PYBIND11_MODULE(openpmd_api_cxx, m) init_Chunk(m); init_Container(m); init_Error(m); - init_BaseRecord(m); init_Dataset(m); init_Datatype(m); init_Helper(m); init_Iteration(m); init_IterationEncoding(m); - init_Mesh(m); init_BaseRecordComponent(m); init_RecordComponent(m); init_MeshRecordComponent(m); - init_ParticlePatches(m); - init_PatchRecord(m); init_PatchRecordComponent(m); - init_ParticleSpecies(m); + init_BaseRecord(m); + init_Mesh(m); init_Record(m); + init_PatchRecord(m); + init_ParticlePatches(m); + init_ParticleSpecies(m); init_Series(m); // API runtime version