0

I have seen answers related to converting python.io object to std::istream. Is there anyway, this can be achieved for std::ostream using boost::iostream::sink? In my case I have a C++ function

void writeToStream(std::ostream&)

How can I expose this function to python?

1 Answer 1

1

As was done in this answer, one should consider implementing a device type to be used by Boost.IOStream to perform delegation rather than converting to a std::ostream. In this case, the Boost.IOStream's Device concept will need to support the Sink concept. It can also be extended to support additional functionality, such as flushing the buffer, by modeling the Flushable concept.

A concept specifies what a type must provide. In the case of the Sink concept, a model can be defined as follows:

struct Sink
{
  typedef char      char_type;
  typedef sink_tag  category;
  std::streamsize write(const char* s, std::streamsize n) 
  {
    // Write up to n characters from the buffer
    // s to the output sequence, returning the 
    // number of characters written
  }
};

The Flushable concept is a bit less direct in the document, and can be determined upon examining the flush() function semantics. In this case, the model can be defined as follows:

struct Flushable
{
  typedef flushable_tag category;
  bool flush()
  {
    // Send all buffered characters downstream.  On error,
    // throw an exception.  Otherwise, return true.
  }
};

Here is a basic type that models the Sink and Flushable concept and delegates to a Python object using duck typing. The python object is:

  • Required to have a write(str) method that returns either None or the number of bytes written.
  • An optional flush() method.
/// @brief Type that implements the Boost.IOStream's Sink and Flushable
///        concept for writing data to Python object that support:
///          n = object.write(str) # n = None or bytes written
///          object.flush()        # if object.flush exists, then it is callable
class PythonOutputDevice
{
public:

  // This class models both the Sink and Flushable concepts.
  struct category
    : boost::iostreams::sink_tag,
      boost::iostreams::flushable_tag
  {};

  explicit
  PythonOutputDevice(boost::python::object object)
    : object_(object)
  {}

// Sink concept.
public:

  typedef char char_type;

  std::streamsize write(const char* buffer, std::streamsize buffer_size)
  {
    namespace python = boost::python;
    // Copy the buffer to a python string.
    python::str data(buffer, buffer_size);

    // Invoke write on the python object, passing in the data.  The following
    // is equivalent to:
    //   n = object_.write(data)
    python::extract<std::streamsize> bytes_written(
      object_.attr("write")(data));

    // Per the Sink concept, return the number of bytes written.  If the
    // Python return value provides a numeric result, then use it.  Otherwise,
    // such as the case of a File object, use the buffer_size.
    return bytes_written.check()
      ? bytes_written
      : buffer_size;
  }

// Flushable concept.
public:

  bool flush()
  {
    // If flush exists, then call it.
    boost::python::object flush = object_.attr("flush");
    if (!flush.is_none())
    {
      flush();
    }

    // Always return true.  If an error occurs, an exception should be thrown.
    return true;
  }

private:
  boost::python::object object_;
};

Notice that in order to support multiple concepts, a nested category struct was created that inherits from the multiple category tags that the model implements.

With the Boost.IOStream Device available, the last step is to expose an auxiliary function that will create the stream with a Python object, then invoke the existing writeToStream() function.

/// @brief Use an auxiliary function to adapt the legacy function.
void aux_writeToStream(boost::python::object object)
{
  // Create an ostream that delegates to the python object.
  boost::iostreams::stream<PythonOutputDevice> output(object);
  writeToStream(output);
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::def("writeToStream", &aux_writeToStream);
}

Here is a complete minimal example:

#include <iosfwd> // std::streamsize
#include <iostream>
#include <boost/python.hpp>
#include <boost/iostreams/concepts.hpp>  // boost::iostreams::sink
#include <boost/iostreams/stream.hpp>

/// @brief Legacy function.
void writeToStream(std::ostream& output)
{
  output << "Hello World";
  output.flush();
}

/// @brief Type that implements the Boost.IOStream's Sink and Flushable
///        concept for writing data to Python object that support:
///          n = object.write(str) # n = None or bytes written
///          object.flush()        # if flush exists, then it is callable
class PythonOutputDevice
{
public:

  // This class models both the Sink and Flushable concepts.
  struct category
    : boost::iostreams::sink_tag,
      boost::iostreams::flushable_tag
  {};

  explicit
  PythonOutputDevice(boost::python::object object)
    : object_(object)
  {}

// Sink concept.
public:

  typedef char char_type;

  std::streamsize write(const char* buffer, std::streamsize buffer_size)
  {
    namespace python = boost::python;
    // Copy the buffer to a python string.
    python::str data(buffer, buffer_size);

    // Invoke write on the python object, passing in the data.  The following
    // is equivalent to:
    //   n = object_.write(data)
    python::extract<std::streamsize> bytes_written(
      object_.attr("write")(data));

    // Per the Sink concept, return the number of bytes written.  If the
    // Python return value provides a numeric result, then use it.  Otherwise,
    // such as the case of a File object, use the buffer_size.
    return bytes_written.check()
      ? bytes_written
      : buffer_size;
  }

// Flushable concept.
public:

  bool flush()
  {
    // If flush exists, then call it.
    boost::python::object flush = object_.attr("flush");
    if (!flush.is_none())
    {
      flush();
    }

    // Always return true.  If an error occurs, an exception should be thrown.
    return true;
  }

private:
  boost::python::object object_;
};

/// @brief Use an auxiliary function to adapt the legacy function.
void aux_writeToStream(boost::python::object object)
{
  // Create an ostream that delegates to the python object.
  boost::iostreams::stream<PythonOutputDevice> output(object);
  writeToStream(output);
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::def("writeToStream", &aux_writeToStream);
}

Interactive usage:

>>> import example
>>> import io
>>> with io.BytesIO() as f:
...     example.writeToStream(f)
...     print f.getvalue()
...
Hello World
>>> with open('test_file', 'w') as f:
...     example.writeToStream(f)
...
>>>
$ cat test_file
Hello World
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.