diff --git a/jinja2/_compat.py b/jinja2/_compat.py index 27f8555..5d4fba5 100644 --- a/jinja2/_compat.py +++ b/jinja2/_compat.py @@ -21,55 +21,58 @@ if not PY2: text_type = str string_types = (str,) - _iterkeys = 'keys' - _itervalues = 'values' - _iteritems = 'items' + iterkeys = lambda d: iter(d.keys()) + itervalues = lambda d: iter(d.values()) + iteritems = lambda d: iter(d.items()) + import pickle from io import BytesIO, StringIO NativeStringIO = StringIO - ifilter = filter - imap = map - izip = zip - def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value - Iterator = object + ifilter = filter + imap = map + izip = zip + intern = sys.intern - class UnicodeMixin(object): - __slots__ = () - def __str__(self): - return self.__unicode__() + implements_iterator = lambda x: x + implements_to_string = lambda x: x + get_next = lambda x: x.__next__ else: - text_type = unicode unichr = unichr + text_type = unicode + range_type = xrange string_types = (str, unicode) - _iterkeys = 'iterkeys' - _itervalues = 'itervalues' - _iteritems = 'iteritems' + iterkeys = lambda d: d.iterkeys() + itervalues = lambda d: d.itervalues() + iteritems = lambda d: d.iteritems() - from itertools import imap, izip, ifilter - range_type = xrange - - from cStringIO import StringIO as BytesIO - from StringIO import StringIO + import cPickle as pickle + from cStringIO import StringIO as BytesIO, StringIO NativeStringIO = BytesIO exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') - class UnicodeMixin(object): - __slots__ = () - def __str__(self): - return self.__unicode__().encode('utf-8') + from itertools import imap, izip, ifilter + intern = intern + + def implements_iterator(cls): + cls.next = cls.__next__ + del cls.__next__ + return cls + + def implements_to_string(cls): + cls.__unicode__ = cls.__str__ + cls.__str__ = lambda x: x.__unicode__().encode('utf-8') + return cls + + get_next = lambda x: x.next - class Iterator(object): - __slots__ = () - def next(self): - return self.__next__() try: next = next @@ -79,22 +82,15 @@ except NameError: def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - return meta('NewBase', bases, {}) + class __metaclass__(meta): + __call__ = type.__call__ + __init__ = type.__init__ + def __new__(cls, name, this_bases, d): + if this_bases is None: + return type.__new__(cls, name, (), d) + return meta(name, bases, d) + return __metaclass__('', None, {}) -def iterkeys(d, **kw): - return iter(getattr(d, _iterkeys)(**kw)) - -def itervalues(d, **kw): - return iter(getattr(d, _itervalues)(**kw)) - -def iteritems(d, **kw): - return iter(getattr(d, _iteritems)(**kw)) - -try: - import cPickle as pickle -except ImportError: - import pickle try: from collections import Mapping as mapping_types diff --git a/jinja2/environment.py b/jinja2/environment.py index bf56d0f..ad4f48a 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -28,7 +28,8 @@ from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \ from jinja2.utils import import_string, LRUCache, Markup, missing, \ concat, consume, internalcode, _encode_filename from jinja2._compat import imap, ifilter, string_types, iteritems, \ - text_type, reraise, Iterator, next, UnicodeMixin + text_type, reraise, implements_iterator, implements_to_string, \ + get_next from functools import reduce @@ -1051,7 +1052,8 @@ class Template(object): return '<%s %s>' % (self.__class__.__name__, name) -class TemplateModule(UnicodeMixin): +@implements_to_string +class TemplateModule(object): """Represents an imported template. All the exported names of the template are available as attributes on this object. Additionally converting it into an unicode- or bytestrings renders the contents. @@ -1065,7 +1067,7 @@ class TemplateModule(UnicodeMixin): def __html__(self): return Markup(concat(self._body_stream)) - def __unicode__(self): + def __str__(self): return concat(self._body_stream) def __repr__(self): @@ -1095,7 +1097,8 @@ class TemplateExpression(object): return rv -class TemplateStream(Iterator): +@implements_iterator +class TemplateStream(object): """A template stream works pretty much like an ordinary python generator but it can buffer multiple items to reduce the number of total iterations. Per default the output is unbuffered which means that for every unbuffered @@ -1139,7 +1142,7 @@ class TemplateStream(Iterator): def disable_buffering(self): """Disable the output buffering.""" - self._next = lambda: next(self._gen) + self._next = get_next(self._gen) self.buffered = False def enable_buffering(self, size=5): @@ -1167,7 +1170,7 @@ class TemplateStream(Iterator): c_size = 0 self.buffered = True - self._next = lambda: next(generator(lambda: next(self._gen))) + self._next = get_next(generator(get_next(self._gen))) def __iter__(self): return self diff --git a/jinja2/exceptions.py b/jinja2/exceptions.py index 4d12a47..fce01ed 100644 --- a/jinja2/exceptions.py +++ b/jinja2/exceptions.py @@ -8,7 +8,7 @@ :copyright: (c) 2010 by the Jinja Team. :license: BSD, see LICENSE for more details. """ -from jinja2._compat import imap, text_type, PY2, UnicodeMixin +from jinja2._compat import imap, text_type, PY2, implements_to_string class TemplateError(Exception): @@ -36,7 +36,8 @@ class TemplateError(Exception): return message -class TemplateNotFound(IOError, LookupError, TemplateError, UnicodeMixin): +@implements_to_string +class TemplateNotFound(IOError, LookupError, TemplateError): """Raised if a template does not exist.""" # looks weird, but removes the warning descriptor that just @@ -51,7 +52,7 @@ class TemplateNotFound(IOError, LookupError, TemplateError, UnicodeMixin): self.name = name self.templates = [name] - def __unicode__(self): + def __str__(self): return self.message @@ -71,7 +72,8 @@ class TemplatesNotFound(TemplateNotFound): self.templates = list(names) -class TemplateSyntaxError(UnicodeMixin, TemplateError): +@implements_to_string +class TemplateSyntaxError(TemplateError): """Raised to tell the user that there is a problem with the template.""" def __init__(self, message, lineno, name=None, filename=None): @@ -85,7 +87,7 @@ class TemplateSyntaxError(UnicodeMixin, TemplateError): # function translated the syntax error into a new traceback self.translated = False - def __unicode__(self): + def __str__(self): # for translated errors we only return the message if self.translated: return self.message diff --git a/jinja2/lexer.py b/jinja2/lexer.py index 87e0920..a501285 100644 --- a/jinja2/lexer.py +++ b/jinja2/lexer.py @@ -20,7 +20,8 @@ from operator import itemgetter from collections import deque from jinja2.exceptions import TemplateSyntaxError from jinja2.utils import LRUCache -from jinja2._compat import next, iteritems, Iterator, text_type +from jinja2._compat import next, iteritems, implements_iterator, text_type, \ + intern # cache for the lexers. Exists in order to be able to have multiple @@ -47,12 +48,6 @@ else: float_re = re.compile(r'(? python 2 the special attributes on functions are gone, +# but they remain on methods and generators for whatever reason. +if not PY2: + UNSAFE_FUNCTION_ATTRIBUTES = set() import warnings @@ -137,7 +144,7 @@ def is_internal_attribute(obj, attr): elif isinstance(obj, (code_type, traceback_type, frame_type)): return True elif isinstance(obj, generator_type): - if attr == 'gi_frame': + if attr in UNSAFE_GENERATOR_ATTRIBUTES: return True return attr.startswith('__') diff --git a/jinja2/tests.py b/jinja2/tests.py index 5fff61a..48a3e06 100644 --- a/jinja2/tests.py +++ b/jinja2/tests.py @@ -17,11 +17,7 @@ number_re = re.compile(r'^-?\d+(\.\d+)?$') regex_type = type(number_re) -try: - test_callable = callable -except NameError: - def test_callable(x): - return hasattr(x, '__call__') +test_callable = callable def test_odd(value): diff --git a/jinja2/testsuite/filters.py b/jinja2/testsuite/filters.py index b432c60..8a6ff71 100644 --- a/jinja2/testsuite/filters.py +++ b/jinja2/testsuite/filters.py @@ -12,7 +12,7 @@ import unittest from jinja2.testsuite import JinjaTestCase from jinja2 import Markup, Environment -from jinja2._compat import text_type, UnicodeMixin +from jinja2._compat import text_type, implements_to_string env = Environment() @@ -294,10 +294,11 @@ class FilterTestCase(JinjaTestCase): assert tmpl.render() == "['Bar', 'blah', 'foo']" def test_sort4(self): - class Magic(UnicodeMixin): + @implements_to_string + class Magic(object): def __init__(self, value): self.value = value - def __unicode__(self): + def __str__(self): return text_type(self.value) tmpl = env.from_string('''{{ items|sort(attribute='value')|join }}''') assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == '1234' diff --git a/jinja2/utils.py b/jinja2/utils.py index 610d8e3..ba78148 100644 --- a/jinja2/utils.py +++ b/jinja2/utils.py @@ -23,7 +23,7 @@ except ImportError: except ImportError: from dummy_thread import allocate_lock from collections import deque -from jinja2._compat import text_type, string_types, Iterator, PY2 +from jinja2._compat import text_type, string_types, implements_iterator, PY2 _word_split_re = re.compile(r'(\s+)') @@ -506,7 +506,8 @@ except ImportError: pass -class Cycler(Iterator): +@implements_iterator +class Cycler(object): """A cycle helper for templates.""" def __init__(self, *items):