I've found a strange behavior in 1.99.7 (which is the same in 2.2.1 except
for a code cleanup issue). This is either a bug or I'm confused (don't
rule that out). This is a long message -- I thought it was important to
include a detailed demo of the phenomenon.
The idea of generic views is wonderful -- a controller function can return
a dict; and based on the extension used to request the page, data is
returned using the formatting logic of the generic view of that type. This
works well and is demonstrated clearly in section 10.1.1 of the book. A
simple example:
In default.py ----------------
def test():
return dict(status='200 OK')
Then from a unix command line ----------------
$ curl -L 'http://localhost:8000/default/test.load'
200 OK
$ curl -L 'http://localhost:8000/default/test.xml'
<?xml version="1.0" encoding="UTF-8"?><document><status>200 OK</status></
document>
$ curl -L 'http://localhost:8000/default/test.json'
{
"status": "200 OK"
}
## note in JSON, I made a change in gluon / serializers.py to add indents
So far so good. But something bad happens when the dictionary is nested:
In updated default.py ----------------
def test():
return dict(response=dict(status='200 OK'))
requesting ...test.json creates an error file in my app/err folder
----------------
Traceback (most recent call last):
File "/Users/chris/dev/hsmaster/web2py_v1.99.7/gluon/main.py", line 508,
in wsgibase
serve_controller(request, response, session)
File "/Users/chris/dev/hsmaster/web2py_v1.99.7/gluon/main.py", line 207,
in serve_controller
run_view_in(response._view_environment)
File "/Users/chris/dev/hsmaster/web2py_v1.99.7/gluon/compileapp.py", line
596, in run_view_in
badv = \'invalid view (%s)\' % response.view
AttributeError: \'dict\' object has no attribute \'view\'
Observations ----------------
The book says "Any dictionary can be rendered in HTML, XML and JSON as long
as it only contains python primitive types (int, float, string, list,
tuple, dictionary)." so this seems in-bounds. json.dumps (serializer
users) has no problem with nested dicts.
This may be a clue -- if we service-enable the same function and call
differently ...
@service.json
def test():
return dict(response=dict(status='200 OK'))
... the result is fine ...
$ curl -L 'http://localhost:8000/default/call/json/test'
{
"response": {
"status": "200 OK"
}
}
or if we don't service-enable it, call like we did the first time, but
change the name of the top-level key like this:
def test():
return dict(blahblah=dict(status='200 OK'))
... the result is fine too ...
$ curl -L 'http://localhost:8000/default/test.json'
{
"blahblah": {
"status": "200 OK"
}
}
Conclusion ----------------
After a long Wingare debugging session, I think this code is the proximate
cause of the problem:
in gluon / main.py / serve_controller():
...
page = run_controller_in(request.controller, request.function,environment
)
if isinstance(page, dict):
response._vars = page
for key in page:
response._view_environment[key] = page[key] ### AAA
run_view_in(response._view_environment)
page = response.body.getvalue()
...
Basically it appears that the line marked AAA overwrites data in the
response._view_environment structure with data from the dict the test()
function returns. The problem only happens when a key in the top level
(not nested) of the dict matches an existing key in
response._view_environment. response._view_environment has 129 keys: A B
BEAUTIFY ... auth cache crud ... response request service etc.) and when
one of these is in common with the dict being rendered, the value of
dict[key] overwrites it.
The traceback above was from gluon / compileapp.py / run_view_in() -- that
relies on response._view_environment['response'] to have a key 'view',
which is present before line AAA; but gone after
-- response._view_environment['response'] has been clobbered.
Remediation ----------------
In my debugging, I changed line AAA to be a pass statement and nothing bad
happened. I don't know if that is the right approach or not. The same
little block of code I showed above (from main.py) also occurs twice in
compileapp.py. I don't know enough to judge whether eliminating line AAA
as I did is a good idea or not; or if it should be done in some or all of
the three location.
Request ----------------
Can someone say if my solution is on the right track; and why the dict is
being merged with the response? Maybe the right approach for a different
payload -- but dicts can't avoid using these keys. view, title, status, db
-- these are pretty common keys.
Best regards and thanks --
--