Well this is embarrassing... I found the issue (in case anybody else is having the same problem(s)). I traced the error through the python source and noticed that the error was being thrown here:
PyObject *
PyCFunction_Call(PyObject *func, PyObject *arg, PyObject *kw)
{
PyCFunctionObject* f = (PyCFunctionObject*)func;
PyCFunction meth = PyCFunction_GET_FUNCTION(func);
PyObject *self = PyCFunction_GET_SELF(func);
Py_ssize_t size;
switch (PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST)) {
case METH_VARARGS:
if (kw == NULL || PyDict_Size(kw) == 0)
return (*meth)(self, arg);
break;
case METH_VARARGS | METH_KEYWORDS:
case METH_OLDARGS | METH_KEYWORDS:
return (*(PyCFunctionWithKeywords)meth)(self, arg, kw);
case METH_NOARGS:
if (kw == NULL || PyDict_Size(kw) == 0) {
size = PyTuple_GET_SIZE(arg);
if (size == 0)
return (*meth)(self, NULL);
PyErr_Format(PyExc_TypeError,
"%.200s() takes no arguments (%zd given)",
f->m_ml->ml_name, size);
return NULL;
}
break;
case METH_O:
if (kw == NULL || PyDict_Size(kw) == 0) {
size = PyTuple_GET_SIZE(arg);
if (size == 1)
return (*meth)(self, PyTuple_GET_ITEM(arg, 0));
PyErr_Format(PyExc_TypeError,
"%.200s() takes exactly one argument (%zd given)",
f->m_ml->ml_name, size);
return NULL;
}
break;
case METH_OLDARGS:
/* the really old style */
if (kw == NULL || PyDict_Size(kw) == 0) {
size = PyTuple_GET_SIZE(arg);
if (size == 1)
arg = PyTuple_GET_ITEM(arg, 0);
else if (size == 0)
arg = NULL;
return (*meth)(self, arg);
}
break;
default:
PyErr_BadInternalCall();
return NULL;
}
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
f->m_ml->ml_name);
return NULL;
}
So I thought, "Maybe my PyMethodDef methd is out of scope (or something) by the time Python takes a look at it" (idk if Python does different processing on functions that contain arguments that are variables than on functions that have const arguments which cause the former to be delayed somewhere up the pipeline...).
So I pulled methd out of the function and declared it as static (this sounds familiar...) and voila! It works beautifully. Here is the updated code:
static PyMethodDef methd = {"blah",ObjC_Class_msg_send,METH_VARARGS,"blech"};
static PyObject * ObjC_Class_getattro(ObjC_Class *self, PyObject *name)
{
NSString *attrName = [NSString stringWithCString:PyString_AsString(name) encoding:NSUTF8StringEncoding];
NSLog(@"Calling Object: %@", self->object);
if([self->object respondsToSelector:NSSelectorFromString(attrName)])
{
methodName = attrName;
PyObject* pyName = PyString_FromString(methd.ml_name);
PyObject* pyfoo = PyCFunction_NewEx(&methd,(PyObject*)self,pyName);
Py_DECREF(name);
return pyfoo;
}
else
{
return name;
}
}
Now, if you'll excuse me, I'm going to go study 'static'.