I have been playing around with the form frameworks (form and form2), and I
tried to create a class that wrapped the form2.Form, and allowed
dictionary-style access to the Widget's render() method and the WidgetRow's
title attribute, when I discovered a bug in _c_htmltext.c. Rather, after a
few hours of debugging, I'm pretty sure it's a bug.
The problem is that an instance of htmltext that came from the c version
will not accept a mapping object other than a dict, or a subclass of one,
while the python version of htmltext will allow you to provide any
'mapping-like' object. Specifically, the python version requires that the
mapping implement items().
In a nutshell, the line that produces the error looks like this (in a
modified form2.Form object):
return htmltext(self.body_template) % FormTemplateDict(self)
I know those names won't mean much to you folks, as they aren't part of the
standard form2, so I'll explain briefly. body_template is a string, along
the lines of:
"""\
%(titleof_testInitValue)s |
%(testInitValue)s |
Named |
%(testName)s |
%(submit)s |
"""
And FormTemplateDict (my fake dictionary wrapper) wraps self (the Form
instance) providing __getitem__ (items(), values(), etc...) access to the
widgets by via the format codes in body_template. For example,
FormTemplateDict(self)['titleof_testInitValue'] would return the .title of
the WidgetRow containing the widget named 'testInitValue', while
FormTemplateDict(self)['testInitValue'] would return the results of calling
that Widget's render() method.
Ok. So, If I do:
return self.body_template % FormTemplateDict(self)
it works.
If I do:
return htmltext(self.body_template) % FormTemplateDict(self)
with _c_htmltext.so missing or renamed, causing _py_htmltext.py to be used
instead, it works.
But if I try the desired call:
return htmltext(self.body_template) % FormTemplateDict(self)
while using the _c_htmltext.so file. It blows up with a KeyError.
I'm not much of a C programmer, and I have NO experience with doing python
extensions in C, but I spent some time trying to narrow down the problem
(with the hope of providing a patch), and the best I could conclude is that
the following loop
_c_htmltext.c
323 while (PyDict_Next(args, &pos, &key, &value)) {
324 PyObject *wvalue = wrap_arg(value);
325 if (wvalue == NULL) {
326 Py_DECREF(wargs);
327 return NULL;
328 }
329 if (PyDict_SetItem(wargs, key, wvalue) < 0) {
330 Py_DECREF(wargs);
331 return NULL;
332 }
333 Py_DECREF(wvalue);
334 }
requires a REAL dict (or subclass). It does not call "items()" as the
python implementaion in _py_htmltext.py does. In fact, I provided a
subclass of a dict instead, with values in it, and then put a
__getattribute__ method to log all accesses, and it worked fine, but no
accesses were logged at all (except for the update() in my constructor,
where I initialized the object to match a dictionary passed to the
constructor). My suspicion is that the PyDict_Next loop should be rewritten
to use PyDict_Items, but with my current level of knowledge of writing
Python extensions in C (None), anything I do to make that change would
probably be *very* buggy.
I wonder if a similar bug would be discovered if I was emulating a List type
instead of a dictionary...
In the meantime, I have two ideas for working around this, so it's not that
big of a deal:
- Passing in a real dictionary
- htmlescaping the args inside of my fake
dictionary, so that I can apply the %
operator to a string instance instead
of an htmltext instance
But I still wanted to point this out to those who may be able to fix it with
a reasonable amount of effort.
If anyone wants a test case, I've attached a fairly thorough one that
demonstrates behavior with a dict, a subclass of dict, and something
pretending to be a dict. Everything in the script should work fine until
line 125, at which point you'll get a KeyError (because the dict produced in
_c_htmltext to be passed to the PyString_Format function is empty, since the
PyDict_Next produced no entries.)
125: print c_htmltext % dict_emulator # <--- This will bomb out on a
KeyError!
I don't thinks it's relevant, but for the record, I'm using Python 2.3.3 on
Linux for this.
Jason Sibre