0

I am checking the types of user input like so:

PyObject *key = PyTuple_GetItem(tuple, 0);
if (!PyObject_TypeCheck(key, &PyBaseString_Type) {
    PyErr_SetString(PyExc_TypeError, "Key must be str");
    return NULL;
}

However, In the exception message, I would like to include the bad type the user has submitted, to make debugging for him easier.

Is there a simple or otherwise idiomatic way to achieve this?


The only way I can think of is the following:

PyObject *key = PyTuple_GetItem(tuple, 0);
if (!PyObject_TypeCheck(key, &PyBaseString_Type) {
    // This returns a new reference which must be Py_DECREFed
    PyObject *bad_type_string = PyObject_Str((PyObject *)key->ob_type);
    char *bad_type_char = PyString_AsString(bad_type_string);
    PyErr_Format(PyExc_TypeError, "Key must be str, not %s", bad_type_char);
    Py_DECREF(bad_type_string);
    return NULL;
}

Which I suppose I could wrap in a macro:

# define CHECK_TYPE(expr, name, input) \
    do {    \
        if (!(expr)) {
            PyObject *bad_type_string = PyObject_Str((PyObject *)input->ob_type);  \
            char *bad_type_char = PyString_AsString(bad_type_string); \
            PyErr_Format(PyExc_TypeError, "%s must be str, not %s", name bad_type_char); \
            Py_DECREF(bad_type_string); \
            goto error; \
        }
    } while (0);

And used like such:

static PyObject *foo(PyObject *self, PyObject *args) {
    // ...
    PyObject *key = PyTuple_GetItem(tuple, 0);
    CHECK_TYPE(PyObject_TypeCheck(key, &PyBaseString_Type, 'key', key);
    // ...
error:
    return NULL;
}
Matthew Moisen
  • 16,701
  • 27
  • 128
  • 231

1 Answers1

0

You could slightly misuse the PyArg_ParseTuple mechanism. The slight bit of non-neatness is that it outputs into a const char*, so you have to get the item from the tuple too.

const char* out;
if (!PyArg_ParseTuple(tpl,"s", &out)) {
    return NULL;
} else {
    key = PyTuple_GetItem(tpl,0); /* borrowed - remember to incref */
}

This generates something like

TypeError: must be str, not int


Note: This is fine for Python 2 since "s" matches both str or unicode object (i.e. effectively basestring). For Python 3 it will only accept str (not bytes) so you might have to follow the documented advice and "to use the O& format with PyUnicode_FSConverter() as converter." This gives an error of

TypeError: Can't convert 'int' object to str implicitly

Additionally, it's a bit nicer because the out is a PyBytesObject that you can use directly instead of having to do PyTuple_GetItem

DavidW
  • 29,336
  • 6
  • 55
  • 86
  • Sorry for the confusion; I meant to ask for a generic solution that could apply to any PyObject, not necessarily a tuple. However you got me thinking, why not continue this like so: `PyArg_ParseTuple(Py_BuildValue("(o)", "s", &out))` - What do you think? – Matthew Moisen Feb 12 '17 at 18:29
  • I think (but I'm not 100% sure) that [`PyArg_Parse`](https://docs.python.org/2/c-api/arg.html#c.PyArg_Parse) can take either a tuple or a single argument (based on the description of the old function call method it was designed to be used on). I haven't tested this though – DavidW Feb 12 '17 at 18:50