diff --git a/falcon/api_helpers.py b/falcon/api_helpers.py index 3f66323..466f858 100644 --- a/falcon/api_helpers.py +++ b/falcon/api_helpers.py @@ -19,8 +19,9 @@ limitations under the License. import re from functools import wraps -from falcon import responders, HTTP_METHODS +import six +from falcon import responders, HTTP_METHODS import falcon.status_codes as status @@ -100,7 +101,8 @@ def get_body(resp): resp: Instance of falcon.Response Returns: - * If resp.body is not None, returns [resp.body], encoded as UTF-8. + * If resp.body is not None, returns [resp.body], encoded as UTF-8 if + it is a Unicode string. Bytestrings are returned as-is. * If resp.data is not None, returns [resp.data] * If resp.stream is not None, returns resp.stream * Otherwise, returns [] @@ -110,9 +112,9 @@ def get_body(resp): body = resp.body if body is not None: - try: + if isinstance(body, six.text_type): return [body.encode('utf-8')] - except UnicodeDecodeError: + else: return [body] elif resp.data is not None: diff --git a/falcon/http_error.py b/falcon/http_error.py index b65dbd4..059bcad 100644 --- a/falcon/http_error.py +++ b/falcon/http_error.py @@ -21,7 +21,7 @@ import sys if sys.version_info < (2, 7): # pragma: no cover from ordereddict import OrderedDict -else: +else: # pragma: no cover from collections import OrderedDict diff --git a/falcon/request.py b/falcon/request.py index 291392e..93ce3e6 100644 --- a/falcon/request.py +++ b/falcon/request.py @@ -24,8 +24,8 @@ from falcon.exceptions import HTTPBadRequest from falcon import util from falcon import request_helpers as helpers -DEFAULT_ERROR_LOG_FORMAT = ('{0:%Y-%m-%d %H:%M:%S} [FALCON] [ERROR]' - ' {1} {2}?{3} => {4}\n') +DEFAULT_ERROR_LOG_FORMAT = (u'{0:%Y-%m-%d %H:%M:%S} [FALCON] [ERROR]' + u' {1} {2}?{3} => ') class InvalidHeaderValueError(HTTPBadRequest): @@ -106,29 +106,32 @@ class Request(object): self._headers = helpers.parse_headers(env) - def log_error(self, message): + def log_error(self, message): # pragma: no cover """Log an error to wsgi.error Prepends timestamp and request info to message, and writes the result out to the WSGI server's error stream (wsgi.error). Args: - message: A string describing the problem. If a byte-string and - running under Python 2, the string is assumed to be encoded - as UTF-8. + message: A string describing the problem. If a byte-string it is + simply written out as-is. Unicode strings will be converted + to UTF-8. """ - if not six.PY3 and isinstance(message, unicode): - message = message.encode('utf-8') - log_line = ( DEFAULT_ERROR_LOG_FORMAT. - format(datetime.now(), self.method, self.path, - self.query_string, message) + format(datetime.now(), self.method, self.path, self.query_string) ) - self._wsgierrors.write(log_line) + if six.PY3: + self._wsgierrors.write(log_line + message + '\n') + else: + if isinstance(message, unicode): + message = message.encode('utf-8') + + self._wsgierrors.write(log_line.encode('utf-8')) + self._wsgierrors.write(message + '\n') @property def client_accepts_json(self): diff --git a/falcon/tests/test_hello.py b/falcon/tests/test_hello.py index fef8b33..7fba379 100644 --- a/falcon/tests/test_hello.py +++ b/falcon/tests/test_hello.py @@ -101,17 +101,14 @@ class TestHelloWorld(testing.TestBase): self.assertEquals(resp.body, self.resource.sample_unicode) self.assertEquals(body, [self.resource.sample_utf8]) - if not six.PY3: - # On Python 3, strings are always Unicode, - # so only perform this test under Python 2. - def test_body_bytes(self): - body = self.simulate_request('/bytes') - resp = self.bytes_resource.resp + def test_body_bytes(self): + body = self.simulate_request('/bytes') + resp = self.bytes_resource.resp - self.assertEquals(self.srmock.status, self.resource.sample_status) - self.assertEquals(resp.status, self.resource.sample_status) - self.assertEquals(resp.body, self.resource.sample_utf8) - self.assertEquals(body, [self.resource.sample_utf8]) + self.assertEquals(self.srmock.status, self.resource.sample_status) + self.assertEquals(resp.status, self.resource.sample_status) + self.assertEquals(resp.body, self.resource.sample_utf8) + self.assertEquals(body, [self.resource.sample_utf8]) def test_data(self): body = self.simulate_request('/data') diff --git a/falcon/tests/test_wsgi_errors.py b/falcon/tests/test_wsgi_errors.py index 9657aa4..7f93500 100644 --- a/falcon/tests/test_wsgi_errors.py +++ b/falcon/tests/test_wsgi_errors.py @@ -5,11 +5,6 @@ import six unicode_message = u'Unicode: \x80' -if six.PY3: - str_message = 'Unicode all the way: \x80' -else: - str_message = 'UTF-8: \xc2\x80' - class LoggerResource: @@ -17,7 +12,7 @@ class LoggerResource: req.log_error(unicode_message) def on_head(self, req, resp): - req.log_error(str_message) + req.log_error(unicode_message.encode('utf-8')) class TestWSGIError(testing.TestBase): @@ -38,19 +33,19 @@ class TestWSGIError(testing.TestBase): # with undefined encoding, so do the encoding manually. self.wsgierrors = self.wsgierrors_buffer - def test_responder_logged_unicode(self): + def test_responder_logged_bytestring(self): self.simulate_request('/logger', wsgierrors=self.wsgierrors) log = self.wsgierrors_buffer.getvalue() + self.assertIn(unicode_message.encode('utf-8'), log) - def test_responder_logged_str(self): + def test_responder_logged_unicode(self): + if six.PY3: + self.skipTest('Test only applies to Python 2') + self.simulate_request('/logger', wsgierrors=self.wsgierrors, method='HEAD') log = self.wsgierrors_buffer.getvalue() - - if six.PY3: - self.assertIn(str_message.encode('utf-8'), log) - else: - self.assertIn(str_message, log) + self.assertIn(unicode_message, log.decode('utf-8')) diff --git a/setup.cfg b/setup.cfg index 3e17842..dae1e30 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,7 @@ with-coverage=true cover-min-percentage=100 cover-package=falcon cover-html=true +cover-html-dir=htmlcov cover-erase=true cover-inclusive=true cover-branches=true