app: Fixed an issue with unicode responses in Python 2.7

If a controller callable returns a string, it needs to be wrapped in a
Response object. To determine if this is the case, the Application tests to
see if the returned object is an instance of `basestring`. Since `basestring`
doesn't exist in Python 3, only `str` is a valid return type.

Unfortunately, my way of testing whether the `basestring` type existed was
flawed. Instead of raising `NameError` when it doesn't exist,
`UnboundLocalError` (a subclass `NameError`) is *always* raised. Since the
exception handler sets `basestring` equal to `str` assuming this is Python 3,
most of the time this isn't a problem. If, however, the controller returns a
`unicode` object in Python 2, the `isinstance` call returns `False`, so the
response is not wrapped in a Response object.

Rather than try to reassign the `basestring` name, now we just use `_string`,
which will either be `basestring` (in Python 2) or `str` (in Python 3).

Apparently, the unit tests didn't cover this case...
master
Dustin C. Hatch 2014-02-07 23:22:50 -06:00
parent a2d8f6f098
commit cf94a4d600
2 changed files with 85 additions and 11 deletions

View File

@ -103,12 +103,11 @@ class Application(object):
# but we need to wrap it in a Response object
try:
# In Python 2, it could be a str or a unicode object
basestring = basestring #@UndefinedVariable
_string = basestring
except NameError:
# Python 3 has no unicode objects and thus no need for
# basestring so we, just make it an alias for str
basestring = str
if isinstance(response, basestring) or not response:
# In Python 3, we are only interested in str objects
_string = str
if isinstance(response, _string) or not response:
response = request.ResponseClass(response)
if not start_response_wrapper.called:

View File

@ -3,13 +3,31 @@
:Created: Nov 27, 2012
:Author: dustin
'''
from unittest.case import SkipTest
import functools
import milla.app
import milla.dispatch
import nose.tools
import sys
import wsgiref.util
import webob.exc
def python2_only(test):
@functools.wraps(test)
def wrapper():
if sys.version_info[0] != 2:
raise SkipTest
return test()
return wrapper
def python3_only(test):
@functools.wraps(test)
def wrapper():
if sys.version_info[0] != 3:
raise SkipTest
return test()
return wrapper
class StubResolver(object):
'''Stub resolver for testing purposes'''
@ -145,6 +163,64 @@ def test_emulated_method():
response.finish_response(app_iter)
assert response.headers.startswith('HTTP/1.1 200'), response.headers
def test_return_none():
'''Controllers can return None
'''
def controller(request):
return None
app = milla.app.Application(StubResolver(controller))
environ = environ_for_testing()
response = ResponseMaker()
app_iter = app(environ, response.start_response)
response.finish_response(app_iter)
assert not response.body, response.body
def test_return_str():
'''Controllers can return str objects
'''
def controller(request):
return 'Hello, world'
app = milla.app.Application(StubResolver(controller))
environ = environ_for_testing()
response = ResponseMaker()
app_iter = app(environ, response.start_response)
response.finish_response(app_iter)
assert response.body == b'Hello, world', response.body
@python2_only
def test_return_unicode():
'''Controllers can return unicode objects
'''
def controller(request):
return unicode('Hello, world')
app = milla.app.Application(StubResolver(controller))
environ = environ_for_testing()
response = ResponseMaker()
app_iter = app(environ, response.start_response)
response.finish_response(app_iter)
assert response.body == unicode('Hello, world'), response.body
@nose.tools.raises(AttributeError)
@python3_only
def test_return_bytes():
'''Controllers cannot return bytes objects
'''
def controller(request):
return b'Hello, world'
app = milla.app.Application(StubResolver(controller))
environ = environ_for_testing()
response = ResponseMaker()
app_iter = app(environ, response.start_response)
response.finish_response(app_iter)
@nose.tools.raises(BeforeCalled)
def test_function_before():
'''__before__ attribute is called for controller functions
@ -484,4 +560,3 @@ def test_static_resource_undefined():
app_iter = app(environ, response.start_response)
response.finish_response(app_iter)
assert response.body == b'/image.png', response.body