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

View File

@ -3,13 +3,31 @@
:Created: Nov 27, 2012 :Created: Nov 27, 2012
:Author: dustin :Author: dustin
''' '''
from unittest.case import SkipTest
import functools import functools
import milla.app import milla.app
import milla.dispatch import milla.dispatch
import nose.tools import nose.tools
import sys
import wsgiref.util import wsgiref.util
import webob.exc 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): class StubResolver(object):
'''Stub resolver for testing purposes''' '''Stub resolver for testing purposes'''
@ -145,6 +163,64 @@ def test_emulated_method():
response.finish_response(app_iter) response.finish_response(app_iter)
assert response.headers.startswith('HTTP/1.1 200'), response.headers 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) @nose.tools.raises(BeforeCalled)
def test_function_before(): def test_function_before():
'''__before__ attribute is called for controller functions '''__before__ attribute is called for controller functions
@ -484,4 +560,3 @@ def test_static_resource_undefined():
app_iter = app(environ, response.start_response) app_iter = app(environ, response.start_response)
response.finish_response(app_iter) response.finish_response(app_iter)
assert response.body == b'/image.png', response.body assert response.body == b'/image.png', response.body