Commit ebdc702f by Chris Jerdonek

Improved byte-string handling in Python 3.

parent 3e23a0c1
...@@ -9,6 +9,7 @@ History ...@@ -9,6 +9,7 @@ History
* Bugfix: exceptions raised from a property are no longer swallowed when * Bugfix: exceptions raised from a property are no longer swallowed when
getting a key from a context stack (issue #110). getting a key from a context stack (issue #110).
* Bugfix: lambda section values can now return non-ascii, non-unicode strings (issue #118). * Bugfix: lambda section values can now return non-ascii, non-unicode strings (issue #118).
* More robust handling of byte strings in Python 3.
0.5.2 (2012-05-03) 0.5.2 (2012-05-03)
------------------ ------------------
......
...@@ -5,6 +5,33 @@ Exposes functionality needed throughout the project. ...@@ -5,6 +5,33 @@ Exposes functionality needed throughout the project.
""" """
from sys import version_info
def _get_string_types():
# TODO: come up with a better solution for this. One of the issues here
# is that in Python 3 there is no common base class for unicode strings
# and byte strings, and 2to3 seems to convert all of "str", "unicode",
# and "basestring" to Python 3's "str".
if version_info < (3, ):
return basestring
# The latter evaluates to "bytes" in Python 3 -- even after conversion by 2to3.
return (unicode, type(u"a".encode('utf-8')))
_STRING_TYPES = _get_string_types()
def is_string(obj):
"""
Return whether the given object is a byte string or unicode string.
This function is provided for compatibility with both Python 2 and 3
when using 2to3.
"""
return isinstance(obj, _STRING_TYPES)
# This function was designed to be portable across Python versions -- both # This function was designed to be portable across Python versions -- both
# with older versions and with Python 3 after applying 2to3. # with older versions and with Python 3 after applying 2to3.
def read(path): def read(path):
......
...@@ -7,6 +7,7 @@ Defines a class responsible for rendering logic. ...@@ -7,6 +7,7 @@ Defines a class responsible for rendering logic.
import re import re
from pystache.common import is_string
from pystache.parser import Parser from pystache.parser import Parser
...@@ -97,7 +98,7 @@ class RenderEngine(object): ...@@ -97,7 +98,7 @@ class RenderEngine(object):
# Return because _render_value() is already a string. # Return because _render_value() is already a string.
return self._render_value(val(), context) return self._render_value(val(), context)
if not isinstance(val, basestring): if not is_string(val):
return str(val) return str(val)
return val return val
...@@ -190,7 +191,7 @@ class RenderEngine(object): ...@@ -190,7 +191,7 @@ class RenderEngine(object):
# Then the value does not support iteration. # Then the value does not support iteration.
data = [data] data = [data]
else: else:
if isinstance(data, (basestring, dict)): if is_string(data) or isinstance(data, dict):
# Do not treat strings and dicts (which are iterable) as lists. # Do not treat strings and dicts (which are iterable) as lists.
data = [data] data = [data]
# Otherwise, treat the value as a list. # Otherwise, treat the value as a list.
...@@ -245,7 +246,7 @@ class RenderEngine(object): ...@@ -245,7 +246,7 @@ class RenderEngine(object):
Render an arbitrary value. Render an arbitrary value.
""" """
if not isinstance(val, basestring): if not is_string(val):
# In case the template is an integer, for example. # In case the template is an integer, for example.
val = str(val) val = str(val)
if type(val) is not unicode: if type(val) is not unicode:
......
...@@ -8,7 +8,7 @@ This module provides a Renderer class to render templates. ...@@ -8,7 +8,7 @@ This module provides a Renderer class to render templates.
import sys import sys
from pystache import defaults from pystache import defaults
from pystache.common import TemplateNotFoundError, MissingTags from pystache.common import TemplateNotFoundError, MissingTags, is_string
from pystache.context import ContextStack, KeyNotFoundError from pystache.context import ContextStack, KeyNotFoundError
from pystache.loader import Loader from pystache.loader import Loader
from pystache.renderengine import context_get, RenderEngine from pystache.renderengine import context_get, RenderEngine
...@@ -16,18 +16,6 @@ from pystache.specloader import SpecLoader ...@@ -16,18 +16,6 @@ from pystache.specloader import SpecLoader
from pystache.template_spec import TemplateSpec from pystache.template_spec import TemplateSpec
# TODO: come up with a better solution for this. One of the issues here
# is that in Python 3 there is no common base class for unicode strings
# and byte strings, and 2to3 seems to convert all of "str", "unicode",
# and "basestring" to Python 3's "str".
if sys.version_info < (3, ):
_STRING_TYPES = basestring
else:
# The latter evaluates to "bytes" in Python 3 -- even after conversion by 2to3.
_STRING_TYPES = (unicode, type(u"a".encode('utf-8')))
class Renderer(object): class Renderer(object):
""" """
...@@ -411,7 +399,7 @@ class Renderer(object): ...@@ -411,7 +399,7 @@ class Renderer(object):
all items in the *context list. all items in the *context list.
""" """
if isinstance(template, _STRING_TYPES): if is_string(template):
return self._render_string(template, *context, **kwargs) return self._render_string(template, *context, **kwargs)
# Otherwise, we assume the template is an object. # Otherwise, we assume the template is an object.
......
...@@ -296,6 +296,16 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin): ...@@ -296,6 +296,16 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
context = {'section': item, attr_name: 7} context = {'section': item, attr_name: 7}
self._assert_render(u'7', template, context) self._assert_render(u'7', template, context)
# This test is also important for testing 2to3.
def test_interpolation__nonascii_nonunicode(self):
"""
Test a tag whose value is a non-ascii, non-unicode string.
"""
template = '{{nonascii}}'
context = {'nonascii': u'abcdé'.encode('utf-8')}
self._assert_render(u'abcdé', template, context)
def test_implicit_iterator__literal(self): def test_implicit_iterator__literal(self):
""" """
Test an implicit iterator in a literal tag. Test an implicit iterator in a literal tag.
...@@ -354,6 +364,28 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin): ...@@ -354,6 +364,28 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
self._assert_render(u'unescaped: < escaped: &lt;', template, context, engine=engine, partials=partials) self._assert_render(u'unescaped: < escaped: &lt;', template, context, engine=engine, partials=partials)
## Test cases related specifically to lambdas.
# This test is also important for testing 2to3.
def test_section__nonascii_nonunicode(self):
"""
Test a section whose value is a non-ascii, non-unicode string.
"""
template = '{{#nonascii}}{{.}}{{/nonascii}}'
context = {'nonascii': u'abcdé'.encode('utf-8')}
self._assert_render(u'abcdé', template, context)
# This test is also important for testing 2to3.
def test_lambda__returning_nonascii_nonunicode(self):
"""
Test a lambda tag value returning a non-ascii, non-unicode string.
"""
template = '{{lambda}}'
context = {'lambda': lambda: u'abcdé'.encode('utf-8')}
self._assert_render(u'abcdé', template, context)
## Test cases related specifically to sections. ## Test cases related specifically to sections.
def test_section__end_tag_with_no_start_tag(self): def test_section__end_tag_with_no_start_tag(self):
...@@ -472,22 +504,15 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin): ...@@ -472,22 +504,15 @@ class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
context = {'test': (lambda text: 'Hi %s' % text)} context = {'test': (lambda text: 'Hi %s' % text)}
self._assert_render(u'Hi Mom', template, context) self._assert_render(u'Hi Mom', template, context)
# This test is also important for testing 2to3.
def test_section__lambda__returning_nonascii_nonunicode(self): def test_section__lambda__returning_nonascii_nonunicode(self):
""" """
Test a lambda section value returning a non-ascii, non-unicode string. Test a lambda section value returning a non-ascii, non-unicode string.
""" """
def literal(s):
if isinstance(s, unicode):
return s
return unicode(s, encoding='utf8')
engine = self._engine()
engine.literal = literal
template = '{{#lambda}}{{/lambda}}' template = '{{#lambda}}{{/lambda}}'
context = {'lambda': lambda text: u'abcdé'.encode('utf-8')} context = {'lambda': lambda text: u'abcdé'.encode('utf-8')}
self._assert_render(u'abcdé', template, context, engine=engine) self._assert_render(u'abcdé', template, context)
def test_section__lambda__returning_nonstring(self): def test_section__lambda__returning_nonstring(self):
""" """
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment