@@ -53,32 +53,103 @@ struct openPMD_PyMPICommObject
5353using openPMD_PyMPIIntracommObject = openPMD_PyMPICommObject;
5454#endif
5555
56+ struct SeriesIteratorPythonAdaptor : SeriesIterator
57+ {
58+ SeriesIteratorPythonAdaptor (SeriesIterator it)
59+ : SeriesIterator(std::move(it))
60+ {}
61+
62+ /*
63+ * Python iterators are weird and call `__next__()` already for getting the
64+ * first element.
65+ * In that case, no `operator++()` must be called...
66+ */
67+ bool first_iteration = true ;
68+ };
69+
5670void init_Series (py::module &m)
5771{
5872 py::class_<WriteIterations>(m, " WriteIterations" )
5973 .def (
6074 " __getitem__" ,
6175 [](WriteIterations writeIterations, Series::IterationIndex_t key) {
76+ auto lastIteration = writeIterations.currentIteration ();
77+ if (lastIteration.has_value () &&
78+ lastIteration.value ().iterationIndex != key)
79+ {
80+ // this must happen under the GIL
81+ lastIteration.value ().close ();
82+ }
83+ py::gil_scoped_release release;
6284 return writeIterations[key];
6385 },
6486 // copy + keepalive
65- py::return_value_policy::copy);
87+ py::return_value_policy::copy)
88+ .def (
89+ " current_iteration" ,
90+ &WriteIterations::currentIteration,
91+ " Return the iteration that is currently being written to, if it "
92+ " exists." );
6693 py::class_<IndexedIteration, Iteration>(m, " IndexedIteration" )
6794 .def_readonly (" iteration_index" , &IndexedIteration::iterationIndex);
95+
96+ py::class_<SeriesIteratorPythonAdaptor>(m, " SeriesIterator" )
97+ .def (
98+ " __next__" ,
99+ [](SeriesIteratorPythonAdaptor &iterator) {
100+ if (iterator == SeriesIterator::end ())
101+ {
102+ throw py::stop_iteration ();
103+ }
104+ /*
105+ * Closing the iteration must happen under the GIL lock since
106+ * Python buffers might be accessed
107+ */
108+ if (!iterator.first_iteration )
109+ {
110+ if (!(*iterator).closed ())
111+ {
112+ (*iterator).close ();
113+ }
114+ py::gil_scoped_release release;
115+ ++iterator;
116+ }
117+ iterator.first_iteration = false ;
118+ if (iterator == SeriesIterator::end ())
119+ {
120+ throw py::stop_iteration ();
121+ }
122+ else
123+ {
124+ return *iterator;
125+ }
126+ }
127+
128+ );
129+
68130 py::class_<ReadIterations>(m, " ReadIterations" )
69131 .def (
70132 " __iter__" ,
71133 [](ReadIterations &readIterations) {
72- return py::make_iterator (
73- readIterations.begin (), readIterations.end ());
134+ // Simple iterator implementation:
135+ // But we need to release the GIL inside
136+ // SeriesIterator::operator++, so manually it is
137+ // return py::make_iterator(
138+ // readIterations.begin(), readIterations.end());
139+ return SeriesIteratorPythonAdaptor (readIterations.begin ());
74140 },
75141 // keep handle alive while iterator exists
76142 py::keep_alive<0 , 1 >());
77143
78144 py::class_<Series, Attributable>(m, " Series" )
79145
80146 .def (
81- py::init<std::string const &, Access, std::string const &>(),
147+ py::init ([](std::string const &filepath,
148+ Access at,
149+ std::string const &options) {
150+ py::gil_scoped_release release;
151+ return new Series (filepath, at, options);
152+ }),
82153 py::arg (" filepath" ),
83154 py::arg (" access" ),
84155 py::arg (" options" ) = " {}" )
@@ -145,6 +216,7 @@ void init_Series(py::module &m)
145216 " (Mismatched MPI at compile vs. runtime?)" );
146217 }
147218
219+ py::gil_scoped_release release;
148220 return new Series (filepath, at, *mpiCommPtr, options);
149221 }),
150222 py::arg (" filepath" ),
@@ -232,7 +304,13 @@ this method.
232304 py::return_value_policy::reference,
233305 // garbage collection: return value must be freed before Series
234306 py::keep_alive<1 , 0 >())
235- .def (" read_iterations" , &Series::readIterations, py::keep_alive<0 , 1 >())
307+ .def (
308+ " read_iterations" ,
309+ [](Series &s) {
310+ py::gil_scoped_release release;
311+ return s.readIterations ();
312+ },
313+ py::keep_alive<0 , 1 >())
236314 .def (
237315 " write_iterations" ,
238316 &Series::writeIterations,
0 commit comments