Skip to content

Commit

Permalink
Add a policy for the ascii literal behavior. Fixes #392
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Jan 7, 2017
1 parent fa80a0d commit 028f058
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Version 2.9
- Ported a modified version of the `tojson` filter from Flask to Jinja2
and hooked it up with the new policy framework.
- Block sets are now marked `safe` by default.
- On Python 2 the asciification of ASCII strings can now be disabled with
the `compiler.ascii_str` policy.

Version 2.8.2
-------------
Expand Down
10 changes: 10 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,16 @@ Example::

env.policies['urlize.rel'] = 'nofollow noopener'

``compiler.ascii_str``:
This boolean controls on Python 2 if Jinja2 should store ASCII only
literals as bytestring instead of unicode strings. This used to be
always enabled for Jinja versions below 2.9 and now can be changed.
Traditionally it was done this way since some APIs in Python 2 failed
badly for unicode strings (for instance the datetime strftime API).
Now however sometimes the inverse is true (for instance str.format).
If this is set to False then all strings are stored as unicode
internally.

``urlize.rel``:
A string that defines the items for the `rel` attribute of generated
links with the `urlize` filter. These items are always added. The
Expand Down
2 changes: 1 addition & 1 deletion jinja2/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1340,7 +1340,7 @@ def visit_Name(self, node, frame):
self.write(ref)

def visit_Const(self, node, frame):
val = node.value
val = node.as_const(frame.eval_ctx)
if isinstance(val, float):
self.write(str(val))
else:
Expand Down
1 change: 1 addition & 0 deletions jinja2/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

# default policies
DEFAULT_POLICIES = {
'compiler.ascii_str': True,
'urlize.rel': 'noopener',
'urlize.target': None,
'json.dumps_function': None,
Expand Down
9 changes: 0 additions & 9 deletions jinja2/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,15 +574,6 @@ def wrap(self, stream, name=None, filename=None):
except Exception as e:
msg = str(e).split(':')[-1].strip()
raise TemplateSyntaxError(msg, lineno, name, filename)
# if we can express it as bytestring (ascii only)
# we do that for support of semi broken APIs
# as datetime.datetime.strftime. On python 3 this
# call becomes a noop thanks to 2to3
if PY2:
try:
value = value.encode('ascii')
except UnicodeError:
pass
elif token == 'integer':
value = int(value)
elif token == 'float':
Expand Down
11 changes: 9 additions & 2 deletions jinja2/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from collections import deque
from jinja2.utils import Markup
from jinja2._compat import izip, with_metaclass, text_type
from jinja2._compat import izip, with_metaclass, text_type, PY2


#: the types we support for context functions
Expand Down Expand Up @@ -470,7 +470,14 @@ class Const(Literal):
fields = ('value',)

def as_const(self, eval_ctx=None):
return self.value
rv = self.value
if PY2 and type(rv) is text_type and \
self.environment.policies['compiler.ascii_str']:
try:
rv = rv.encode('ascii')
except UnicodeError:
pass
return rv

@classmethod
def from_untrusted(cls, value, lineno=None, environment=None):
Expand Down
26 changes: 25 additions & 1 deletion tests/test_features.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
import pytest

from jinja2 import Template
from jinja2 import Template, Environment, contextfilter


@pytest.mark.skipif(sys.version_info < (3, 5),
Expand All @@ -14,3 +14,27 @@ def __getattr__(self, name):
t = Template('a{{ bad.bar() }}b')
with pytest.raises(RuntimeError):
t.render(bad=X())


@pytest.mark.skipif(sys.version_info[0] > 2,
reason='Feature only supported on 2.x')
def test_ascii_str():
@contextfilter
def assert_func(context, value):
assert type(value) is context['expected_type']

env = Environment()
env.filters['assert'] = assert_func

env.policies['compiler.ascii_str'] = False
t = env.from_string('{{ "foo"|assert }}')
t.render(expected_type=unicode)

env.policies['compiler.ascii_str'] = True
t = env.from_string('{{ "foo"|assert }}')
t.render(expected_type=str)

for val in True, False:
env.policies['compiler.ascii_str'] = val
t = env.from_string(u'{{ "\N{SNOWMAN}"|assert }}')
t.render(expected_type=unicode)

0 comments on commit 028f058

Please sign in to comment.