1. Dustin Hatch
  2. Milla

Commits

Dustin Hatch  committed 31737bf

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

  • Participants
  • Parent commits 44107cf
  • Branches default

Comments (0)

Files changed (2)

File src/milla/app.py

View file
         # 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:

File src/milla/tests/test_app.py

View file
 :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'''
 
 
 def test_emulated_method():
     '''Emulated HTTP methods are interpreted correctly
-    
+
     For applications that cannot use the proper HTTP method and instead
     use HTTP POST with an ``_method`` parameter
     '''
     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
 
 def test_static_resource():
     '''Request.static_resource creates valid URL from config'''
-    
+
     def controller(request):
         return request.static_resource('/image.png')
-    
+
     environ = environ_for_testing()
     app = milla.Application(StubResolver(controller))
     app.config['milla.static_root'] = '/static'
 
 def test_static_resource_undefined():
     '''Request.static_resource returns the path unmodified with no root defined'''
-    
+
     def controller(request):
         return request.static_resource('/image.png')
-    
+
     environ = environ_for_testing()
     app = milla.Application(StubResolver(controller))
     response = ResponseMaker()
     app_iter = app(environ, response.start_response)
     response.finish_response(app_iter)
     assert response.body == b'/image.png', response.body
-