Search code examples
ccpythonpython-3.2

Passing io._BufferedReader to c-function


This question is a follow-up to my previous question, where I tried to compile python-yenc for Python3. After being told there wasn't a quick fix, I decided to take up the challange and rewrite it completely.

The only thing I can't figure out is how to use PyArg_ParseTupleAndKeywords with io-objects. Here is the relevant code:

PyObject *in_file, *out_file;
static char *kwlist[] = { "infile", "outfile", NULL };

if(!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!|l", kwlist,\
                     &PyBytes_Type, &in_file,\
                     &PyBytes_Type, &out_file,\
                     &bytes)) return NULL;

which obviously yields

Traceback (most recent call last):
  File "test.py", line 21, in <module>
    print(_yenc.decode_file(b), outfile=o)
TypeError: argument 1 must be bytes, not _io.BufferedReader

How can I pass _io.BufferedReader-objects to my function?

Thanks, Martijn


Solution

  • Mzialla, you should not be using "O!" in PyArg_ParseTupleAndKeywords. Doing so means that you cannot pass any object other than the specified type. I believe the typical approach to interfacing with files in Python 3.2 extensions is not to assume any particular type, but program against the "protocol".

    So you should do something like:

    if(!PyArg_ParseTupleAndKeywords(args, kwds, "OO|l", kwlist,\
                 &in_file, &out_file, &bytes))
        ...
    

    And after that, you have two options: either you interact with the stream object using its Python methods, or you obtain the stream's file descriptor (PyObject_AsFileDescriptor) and operate on that using the OS-level read/write functions (or equivalent).

    For the Python-method approach, you should obtain the "read" method, and invoke that instead of fread. Something along the following lines (untested):

    PyObject *read = PyObject_GetAttrString(in_file, "read");
    if (!read) handle_error;
    while(encoded < bytes || bytes == 0) {
        PyObject *bytes_obj= PyObject_CallFunction(read, "i", 1);
        if (!bytes || !PyBytes_Check(bytes_obj)) handle_error;
        char *s = PyBytes_AS_STRING(bytes_obj);
        ...
    }
    

    Then you would need to do something similar for the write side.