17#ifndef BOOST_ADAPTBX_PYTHON_STREAMBUF_H
18#define BOOST_ADAPTBX_PYTHON_STREAMBUF_H
23#include <nanobind/nanobind.h>
24namespace nb = nanobind;
25using namespace nb::literals;
105class streambuf :
public std::basic_streambuf<char> {
107 typedef std::basic_streambuf<char> base_t;
132 streambuf(nb::object &python_file_obj, std::size_t buffer_size_ = 0)
133 : py_read(nb::getattr(python_file_obj,
"read", nb::none())),
134 py_write(nb::getattr(python_file_obj,
"write", nb::none())),
135 py_seek(nb::getattr(python_file_obj,
"seek", nb::none())),
136 py_tell(nb::getattr(python_file_obj,
"tell", nb::none())),
138 write_buffer(nullptr),
139 pos_of_read_buffer_end_in_py_file(0),
140 pos_of_write_buffer_end_in_py_file(buffer_size),
141 farthest_pptr(nullptr) {
147 if (!py_tell.is_none()) {
149 off_type py_pos = nb::cast<off_type>(py_tell());
150 if (!py_seek.is_none()) {
157 }
catch (nb::python_error &) {
158 py_tell = nb::none();
159 py_seek = nb::none();
167 if (!py_write.is_none()) {
169 write_buffer =
new char[buffer_size + 1];
170 write_buffer[buffer_size] =
'\0';
171 setp(write_buffer, write_buffer + buffer_size);
172 farthest_pptr = pptr();
175 setp(
nullptr,
nullptr);
178 if (!py_tell.is_none()) {
179 off_type py_pos = nb::cast<off_type>(py_tell());
180 pos_of_read_buffer_end_in_py_file = py_pos;
181 pos_of_write_buffer_end_in_py_file = py_pos;
187 std::size_t buffer_size_ = 0)
188 :
streambuf(python_file_obj, buffer_size_) {
190 auto io_mod = nb::module_::import_(
"io");
192 nb::object iobase = nb::getattr(io_mod,
"TextIOBase");
199 static nb::object io_mod = nb::object();
200 static nb::object iobase = nb::object();
201 if (!io_mod) io_mod = nb::import(
"io");
202 if (io_mod && !iobase) iobase = nb::getattr(io_mod,
"TextIOBase");
207 df_isTextMode = PyObject_IsInstance(python_file_obj.ptr(), iobase.ptr());
211 if (!df_isTextMode) {
213 "Need a text mode file object like StringIO or a file opened "
220 "Need a binary mode file object like BytesIO or a file opened "
225 throw std::invalid_argument(
"bad mode character");
232 delete[] write_buffer;
241 int_type const failure = traits_type::eof();
243 if (status == failure) {
246 return egptr() - gptr();
251 int_type const failure = traits_type::eof();
252 if (py_read.is_none()) {
253 throw std::invalid_argument(
254 "That Python file object has no 'read' attribute");
256 read_buffer = py_read(buffer_size);
257 char *read_buffer_data;
258 Py_ssize_t py_n_read;
259 if (PyBytes_AsStringAndSize(read_buffer.ptr(), &read_buffer_data,
261 setg(
nullptr,
nullptr,
nullptr);
262 throw std::invalid_argument(
263 "The method 'read' of the Python file object "
264 "did not return a string.");
267 pos_of_read_buffer_end_in_py_file += n_read;
268 setg(read_buffer_data, read_buffer_data, read_buffer_data + n_read);
273 return traits_type::to_int_type(read_buffer_data[0]);
278 if (py_write.is_none()) {
279 throw std::invalid_argument(
280 "That Python file object has no 'write' attribute");
282 farthest_pptr = std::max(farthest_pptr, pptr());
284 off_type orig_n_written = n_written;
285 const unsigned int STD_ASCII = 0x7F;
286 if (df_isTextMode &&
static_cast<unsigned int>(c) > STD_ASCII) {
290 while (n_written > 0 &&
static_cast<unsigned int>(
291 write_buffer[n_written - 1]) > STD_ASCII) {
295 nb::str chunk(pbase(), n_written);
298 if ((!df_isTextMode ||
static_cast<unsigned int>(c) <= STD_ASCII) &&
299 !traits_type::eq_int_type(c, traits_type::eof())) {
300 py_write(traits_type::to_char_type(c));
304 setp(pbase(), epptr());
306 farthest_pptr = pptr();
308 pos_of_write_buffer_end_in_py_file += n_written;
309 if (df_isTextMode &&
static_cast<unsigned int>(c) > STD_ASCII &&
310 !traits_type::eq_int_type(c, traits_type::eof())) {
311 size_t n_to_copy = orig_n_written - n_written;
313 for (
size_t i = 0; i < n_to_copy; ++i) {
314 sputc(write_buffer[n_written + i]);
321 return traits_type::eq_int_type(c, traits_type::eof())
322 ? traits_type::not_eof(c)
335 farthest_pptr = std::max(farthest_pptr, pptr());
336 if (farthest_pptr && farthest_pptr > pbase()) {
337 off_type delta = pptr() - farthest_pptr;
339 if (traits_type::eq_int_type(status, traits_type::eof())) {
342 if (!py_seek.is_none()) {
345 }
else if (gptr() && gptr() < egptr()) {
346 if (!py_seek.is_none()) {
347 py_seek(gptr() - egptr(), 1);
361 std::ios_base::openmode which =
362 std::ios_base::in | std::ios_base::out)
override {
370 if (py_seek.is_none()) {
371 throw std::invalid_argument(
372 "That Python file object has no 'seek' attribute");
376 if (which == std::ios_base::in && !gptr()) {
377 if (traits_type::eq_int_type(
underflow(), traits_type::eof())) {
385 case std::ios_base::beg:
388 case std::ios_base::cur:
391 case std::ios_base::end:
399 std::optional<off_type> result =
400 seekoff_without_calling_python(off, way, which);
403 if (which == std::ios_base::out) {
406 if (way == std::ios_base::cur) {
407 if (which == std::ios_base::in) {
408 off -= egptr() - gptr();
409 }
else if (which == std::ios_base::out) {
410 off += pptr() - pbase();
413 py_seek(off, whence);
414 result =
off_type(nb::cast<off_type>(py_tell()));
415 if (which == std::ios_base::in) {
424 std::ios_base::openmode which =
425 std::ios_base::in | std::ios_base::out)
override {
430 nb::object py_read, py_write, py_seek, py_tell;
432 std::size_t buffer_size;
439 nb::object read_buffer;
447 off_type pos_of_read_buffer_end_in_py_file,
448 pos_of_write_buffer_end_in_py_file;
453 std::optional<off_type> seekoff_without_calling_python(
454 off_type off, std::ios_base::seekdir way, std::ios_base::openmode which) {
455 std::optional<off_type>
const failure =
off_type(-1);
458 off_type buf_begin, buf_end, buf_cur, upper_bound;
459 off_type pos_of_buffer_end_in_py_file;
460 if (which == std::ios_base::in) {
461 pos_of_buffer_end_in_py_file = pos_of_read_buffer_end_in_py_file;
462 buf_begin =
reinterpret_cast<std::streamsize
>(eback());
463 buf_cur =
reinterpret_cast<std::streamsize
>(gptr());
464 buf_end =
reinterpret_cast<std::streamsize
>(egptr());
465 upper_bound = buf_end;
466 }
else if (which == std::ios_base::out) {
467 pos_of_buffer_end_in_py_file = pos_of_write_buffer_end_in_py_file;
468 buf_begin =
reinterpret_cast<std::streamsize
>(pbase());
469 buf_cur =
reinterpret_cast<std::streamsize
>(pptr());
470 buf_end =
reinterpret_cast<std::streamsize
>(epptr());
471 farthest_pptr = std::max(farthest_pptr, pptr());
472 upper_bound =
reinterpret_cast<std::streamsize
>(farthest_pptr) + 1;
479 if (way == std::ios_base::cur) {
480 buf_sought = buf_cur + off;
481 }
else if (way == std::ios_base::beg) {
482 buf_sought = buf_end + (off - pos_of_buffer_end_in_py_file);
483 }
else if (way == std::ios_base::end) {
490 if (buf_sought < buf_begin || buf_sought >= upper_bound) {
495 if (which == std::ios_base::in) {
496 gbump(buf_sought - buf_cur);
497 }
else if (which == std::ios_base::out) {
498 pbump(buf_sought - buf_cur);
500 return pos_of_buffer_end_in_py_file + (buf_sought - buf_end);
504 class istream :
public std::istream {
507 exceptions(std::ios_base::badbit);
520 class ostream :
public std::ostream {
523 exceptions(std::ios_base::badbit);
528 exceptions(std::ios_base::badbit);
545struct streambuf_capsule {
552struct ostream :
private streambuf_capsule, streambuf::ostream {
553 ostream(nb::object &python_file_obj, std::size_t buffer_size = 0)
#define TEST_ASSERT(expr)
#define CHECK_INVARIANT(expr, mess)
Class to allow us to throw a ValueError from C++ and have it make it back to Python.
A stream buffer getting data from and putting data into a Python file object.
base_t::char_type char_type
~streambuf() override
Mundane destructor freeing the allocated resources.
streambuf(nb::object &python_file_obj, char mode, std::size_t buffer_size_=0)
constructor to enforce a mode (binary or text)
base_t::off_type off_type
static const std::size_t default_buffer_size
The default size of the read and write buffer.
pos_type seekpos(pos_type sp, std::ios_base::openmode which=std::ios_base::in|std::ios_base::out) override
C.f. C++ standard section 27.5.2.4.2.
base_t::pos_type pos_type
static int traits_type_eof()
std::streamsize showmanyc() override
C.f. C++ standard section 27.5.2.4.3.
pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which=std::ios_base::in|std::ios_base::out) override
C.f. C++ standard section 27.5.2.4.2.
streambuf(nb::object &python_file_obj, std::size_t buffer_size_=0)
Construct from a Python file object.
base_t::int_type int_type
int sync() override
Update the python file to reflect the state of this stream buffer.
base_t::traits_type traits_type
int_type overflow(int_type c=traits_type_eof()) override
C.f. C++ standard section 27.5.2.4.5.
int_type underflow() override
C.f. C++ standard section 27.5.2.4.3.
streambuf(bp::object &python_file_obj, std::size_t buffer_size_=0)
Construct from a Python file object.
ostream(bp::object &python_file_obj, std::size_t buffer_size=0)
ostream(nb::object &python_file_obj, std::size_t buffer_size=0)
~ostream() noexcept override
streambuf_capsule(bp::object &python_file_obj, std::size_t buffer_size=0)
streambuf python_streambuf
streambuf_capsule(nb::object &python_file_obj, std::size_t buffer_size=0)