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 --

-- 



Reply via email to