Merge pull request #530 from nick-garcia/master

Subtraction from Undefined does not raise UndefinedError
diff --git a/.github/ISSUE_TEMPLATE.rst b/.github/ISSUE_TEMPLATE.rst
new file mode 100644
index 0000000..8854961
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.rst
@@ -0,0 +1,2 @@
+The issue tracker is a tool to address bugs.
+Please use the #pocoo IRC channel on freenode or Stack Overflow for questions.
diff --git a/.gitignore b/.gitignore
index 3ae1fa7..d1b99f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,14 @@
 *.so
-docs/_build
+docs/_build/
 *.pyc
 *.pyo
-*.egg-info
+*.egg-info/
 *.egg
-build
-dist
+build/
+dist/
 .DS_Store
-.tox
+.tox/
+.cache/
+.idea/
+venv/
+venv-*/
diff --git a/.travis.yml b/.travis.yml
index 9cf4d86..ebaf846 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,23 +1,22 @@
+sudo: false
 language: python
 python:
   - "2.6"
   - "2.7"
+  - pypy
   - "3.3"
   - "3.4"
   - "3.5"
-  - "pypy"
-
+  - "3.6"
 install:
-    - pip install tox
+  - pip install tox
 script:
-    - tox -e \
-      $(echo $TRAVIS_PYTHON_VERSION | sed 's/^\([0-9]\)\.\([0-9]\).*/py\1\2/')
-
+  - tox -e py
 notifications:
   email: false
   irc:
     channels:
-      - "chat.freenode.net#pocoo"
+      - chat.freenode.net#pocoo
     on_success: change
     on_failure: always
     use_notice: true
diff --git a/CHANGES b/CHANGES
index 4e5df26..776af69 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,12 +1,40 @@
 Jinja2 Changelog
 ================
 
+Version 2.9
+-----------
+
+- Change cache key definition in environment. This fixes a performance
+  regression introduced in 2.8.
+- Added support for `generator_stop` on supported Python versions
+  (Python 3.5 and later)
+- Corrected a long standing issue with operator precedence of math operations
+  not being what was expected.
+- Added support for Python 3.6 async iterators through a new async mode.
+- Added policies for filter defaults and similar things.
+- urlize now sets "rel noopener" by default.
+- Support attribute fallback for old-style classes in 2.x.
+- Support toplevel set statements in extend situations.
+- Restored behavior of Cycler for Python 3 users.
+
+Version 2.8.2
+-------------
+
+(bugfix release, unreleased)
+
+- Fixed a runtime error in the sandbox when attributes of async generators
+  were accessed.
+
 Version 2.8.1
 -------------
 
-(unreleased bugfix release)
+(bugfix release, released on December 29th 2016)
 
 - Fixed the `for_qs` flag for `urlencode`.
+- Fixed regression when applying `int` to non-string values.
+- SECURITY: if the sandbox mode is used format expressions are now sandboxed
+  with the same rules as in Jinja.  This solves various information leakage
+  problems that can occur with format strings.
 
 Version 2.8
 -----------
@@ -26,7 +54,7 @@
 - If unmarshalling of cached data fails the template will be
   reloaded now.
 - Implemented a block ``set`` tag.
-- Default cache size was incrased to 400 from a low 50.
+- Default cache size was increased to 400 from a low 50.
 - Fixed ``is number`` test to accept long integers in all Python versions.
 - Changed ``is number`` to accept Decimal as a number.
 - Added a check for default arguments followed by non-default arguments. This
@@ -132,7 +160,7 @@
   makes this necessary, Jinja2 will automatically detect these cases now.
 - the sum filter can now sum up values by attribute.  This is a backwards
   incompatible change.  The argument to the filter previously was the
-  optional starting index which defaultes to zero.  This now became the
+  optional starting index which defaults to zero.  This now became the
   second argument to the function because it's rarely used.
 - like sum, sort now also makes it possible to order items by attribute.
 - like sum and sort, join now also is able to join attributes of objects
@@ -190,11 +218,11 @@
   pulled from markupsafe by the Jinja2 developers.  The debug support
   went into a separate feature called "debugsupport" and is disabled
   by default because it is only relevant for Python 2.4
-- fixed an issue with unary operators having the wrong precendence.
+- fixed an issue with unary operators having the wrong precedence.
 
 Version 2.5
 -----------
-(codename Incoherence, relased on May 29th 2010)
+(codename Incoherence, released on May 29th 2010)
 
 - improved the sort filter (should have worked like this for a
   long time) by adding support for case insensitive searches.
diff --git a/Makefile b/Makefile
index aadaaa9..3c12a8c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 test:
-	py.test
+	py.test tests --tb=short
 
 develop:
 	pip install --editable .
diff --git a/README.rst b/README.rst
index 0684e8a..44571dc 100644
--- a/README.rst
+++ b/README.rst
@@ -8,7 +8,9 @@
 Nutshell
 --------
 
-Here a small example of a Jinja template::
+Here a small example of a Jinja template:
+
+.. code-block:: jinja
 
     {% extends 'base.html' %}
     {% block title %}Memberlist{% endblock %}
@@ -28,8 +30,8 @@
 
 For more information visit the new `Jinja2 webpage`_ and `documentation`_.
 
-The `Jinja2 tip`_ is installable via `easy_install` with ``easy_install
-Jinja2==dev``.
+The `Jinja2 tip`_ is installable via ``pip`` with ``pip install
+https://github.com/pallets/jinja/zipball/master``.
 
 .. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
 .. _Django: http://www.djangoproject.com/
@@ -41,9 +43,9 @@
 ------
 
 +---------------------+------------------------------------------------------------------------------+
-| ``master``          | .. image:: https://travis-ci.org/mitsuhiko/jinja2.svg?branch=master          |
-|                     |     :target: https://travis-ci.org/mitsuhiko/jinja2                          |
+| ``master``          | .. image:: https://travis-ci.org/pallets/jinja.svg?branch=master             |
+|                     |     :target: https://travis-ci.org/pallets/jinja                             |
 +---------------------+------------------------------------------------------------------------------+
-| ``2.7-maintenance`` | .. image:: https://travis-ci.org/mitsuhiko/jinja2.svg?branch=2.7-maintenance |
-|                     |     :target: https://travis-ci.org/mitsuhiko/jinja2                          |
+| ``2.7-maintenance`` | .. image:: https://travis-ci.org/pallets/jinja.svg?branch=2.7-maintenance    |
+|                     |     :target: https://travis-ci.org/pallets/jinja                             |
 +---------------------+------------------------------------------------------------------------------+
diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html
index b7fcfda..9c430c0 100644
--- a/docs/_templates/sidebarintro.html
+++ b/docs/_templates/sidebarintro.html
@@ -16,5 +16,5 @@
 <ul>
   <li><a href="http://jinja.pocoo.org/">The Jinja2 Website</a></li>
   <li><a href="http://pypi.python.org/pypi/Jinja2">Jinja2 @ PyPI</a></li>
-  <li><a href="http://github.com/mitsuhiko/jinja2">Jinja2 @ github</a></li>
+  <li><a href="http://github.com/pallets/jinja">Jinja2 @ github</a></li>
 </ul>
diff --git a/docs/api.rst b/docs/api.rst
index 088c867..107acd6 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -19,7 +19,7 @@
 albeit a shared one.
 
 Most applications will create one :class:`Environment` object on application
-initialization and use that to load templates.  In some cases it's however
+initialization and use that to load templates.  In some cases however, it's 
 useful to have multiple environments side by side, if different configurations
 are in use.
 
@@ -154,6 +154,12 @@
         to modify this dict.  For more details see :ref:`global-namespace`.
         For valid object names have a look at :ref:`identifier-naming`.
 
+    .. attribute:: policies
+
+        A dictionary with :ref:`policies`.  These can be reconfigured to
+        change the runtime behavior or certain template features.  Usually
+        these are security related.
+
     .. attribute:: code_generator_class
 
        The class used for code generation.  This should not be changed
@@ -229,6 +235,10 @@
 
     .. automethod:: stream([context])
 
+    .. automethod:: render_async([context])
+
+    .. automethod:: generate_async([context])
+
 
 .. autoclass:: jinja2.environment.TemplateStream()
     :members: disable_buffering, enable_buffering, dump
@@ -497,6 +507,65 @@
 .. autoclass:: jinja2.MemcachedBytecodeCache
 
 
+Async Support
+-------------
+
+Starting with version 2.9, Jinja2 also supports the Python `async` and
+`await` constructs.  As far as template designers go this feature is
+entirely opaque to them however as a developer you should be aware of how
+it's implemented as it influences what type of APIs you can safely expose
+to the template environment.
+
+First you need to be aware that by default async support is disabled as
+enabling it will generate different template code behind the scenes which
+passes everything through the asyncio event loop.  This is important to
+understand because it has some impact to what you are doing:
+
+*   template rendering will require an event loop to be set for the
+    current thread (``asyncio.get_event_loop`` needs to return one)
+*   all template generation code internally runs async generators which
+    means that you will pay a performance penalty even if the non sync
+    methods are used!
+*   The sync methods are based on async methods if the async mode is
+    enabled which means that `render` for instance will internally invoke
+    `render_async` and run it as part of the current event loop until the
+    execution finished.
+
+Awaitable objects can be returned from functions in templates and any
+function call in a template will automatically await the result.  This
+means that you can let provide a method that asynchronously loads data
+from a database if you so desire and from the template designer's point of
+view this is just another function they can call.  This means that the
+``await`` you would normally issue in Python is implied.  However this
+only applies to function calls.  If an attribute for instance would be an
+avaitable object then this would not result in the expected behavior.
+
+Likewise iterations with a `for` loop support async iterators.
+
+.. _policies:
+
+Policies
+--------
+
+Starting with Jinja 2.9 policies can be configured on the environment
+which can slightly influence how filters and other template constructs
+behave.  They can be configured with the
+:attr:`~jinja2.Environment.policies` attribute.
+
+Example::
+
+    env.policies['urlize.rel'] = 'nofollow noopener'
+
+``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
+    default is `noopener`.
+
+``urlize.target``:
+    The default target that is issued for links from the `urlize` filter
+    if no other target is defined by the call explicitly.
+
+
 Utilities
 ---------
 
@@ -581,7 +650,7 @@
 --------------
 
 Custom filters are just regular Python functions that take the left side of
-the filter as first argument and the the arguments passed to the filter as
+the filter as first argument and the arguments passed to the filter as
 extra arguments or keyword arguments.
 
 For example in the filter ``{{ 42|myfilter(23) }}`` the function would be
diff --git a/docs/extensions.rst b/docs/extensions.rst
index 0825fd4..afdfb00 100644
--- a/docs/extensions.rst
+++ b/docs/extensions.rst
@@ -107,7 +107,7 @@
 :ref:`of the template documentation <i18n-in-templates>`.
 
 .. _gettext: http://docs.python.org/dev/library/gettext
-.. _Babel: http://babel.edgewall.org/
+.. _Babel: http://babel.pocoo.org/
 
 .. _newstyle-gettext:
 
diff --git a/docs/faq.rst b/docs/faq.rst
index 0b0375e..9dd348f 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -141,8 +141,8 @@
 
     import os
     if os.environ.get('SERVER_SOFTWARE', '').startswith('Dev'):
-        from google.appengine.tools.dev_appserver import HardenedModulesHook
-        HardenedModulesHook._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt']
+        from google.appengine.tools.devappserver2.python import sandbox
+        sandbox._WHITE_LIST_C_MODULES += ['_ctypes', 'gestalt']
 
 Credit for this snippet goes to `Thomas Johansson
 <http://stackoverflow.com/questions/3086091/debug-jinja2-in-google-app-engine/3694434#3694434>`_
diff --git a/docs/integration.rst b/docs/integration.rst
index 92a3b14..3a4988b 100644
--- a/docs/integration.rst
+++ b/docs/integration.rst
@@ -6,7 +6,7 @@
 This is a brief description of whats included.
 
 Files to help integration are available
-`here. <https://github.com/mitsuhiko/jinja2/tree/master/ext>`_
+`here. <https://github.com/pallets/jinja/tree/master/ext>`_
 
 .. _babel-integration:
 
@@ -50,7 +50,7 @@
    want that behavior you can add ``silent=false`` to the settings and
    exceptions are propagated.
 
-.. _mapping file: http://babel.edgewall.org/wiki/Documentation/messages.html#extraction-method-mapping-and-configuration
+.. _mapping file: http://babel.pocoo.org/en/latest/messages.html#extraction-method-mapping-and-configuration
 
 Pylons
 ------
@@ -81,10 +81,10 @@
 TextMate
 --------
 
-There is a bundle for TextMate that supports syntax highlighting for Jinja1 and Jinja2 for text based
+There is a `bundle for TextMate`_ that supports syntax highlighting for Jinja1 and Jinja2 for text based
 templates as well as HTML.  It also contains a few often used snippets.
 
-.. _TextMate Bundle: https://github.com/mitsuhiko/jinja2-tmbundle
+.. _bundle for TextMate: https://github.com/mitsuhiko/jinja2-tmbundle
 
 Vim
 ---
@@ -97,5 +97,5 @@
 
 Copy the files into your `syntax` folder.
 
-.. _Babel: http://babel.edgewall.org/
+.. _Babel: http://babel.pocoo.org/
 .. _Vim: http://www.vim.org/
diff --git a/docs/intro.rst b/docs/intro.rst
index 99c3582..e097718 100644
--- a/docs/intro.rst
+++ b/docs/intro.rst
@@ -36,15 +36,12 @@
 This will install a Jinja2 egg in your Python installation's site-packages
 directory.
 
-(If you are installing from the Windows command line omit the `sudo` and make
-sure to run the command as user with administrator rights)
-
 From the tarball release
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
 1.  Download the most recent tarball from the `download page`_
 2.  Unpack the tarball
-3.  ``sudo python setup.py install``
+3.  ``python setup.py install``
 
 Note that you either have to have `setuptools` or `distribute` installed;
 the latter is preferred.
@@ -55,7 +52,7 @@
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 1.  Install `git`_
-2.  ``git clone git://github.com/mitsuhiko/jinja2.git``
+2.  ``git clone git://github.com/pallets/jinja.git``
 3.  ``cd jinja2``
 4.  ``ln -s jinja2 /usr/lib/python2.X/site-packages``
 
@@ -120,4 +117,4 @@
 for yourself.
 
 
-.. _Jinja bug tracker: http://github.com/mitsuhiko/jinja2/issues
+.. _Jinja bug tracker: http://github.com/pallets/jinja/issues
diff --git a/docs/templates.rst b/docs/templates.rst
index d9102ec..257d6bf 100644
--- a/docs/templates.rst
+++ b/docs/templates.rst
@@ -606,10 +606,10 @@
 | `loop.cycle`          | A helper function to cycle between a list of      |
 |                       | sequences.  See the explanation below.            |
 +-----------------------+---------------------------------------------------+
-| `loop.depth`          | Indicates how deep in deep in a recursive loop    |
+| `loop.depth`          | Indicates how deep in a recursive loop            |
 |                       | the rendering currently is.  Starts at level 1    |
 +-----------------------+---------------------------------------------------+
-| `loop.depth0`         | Indicates how deep in deep in a recursive loop    |
+| `loop.depth0`         | Indicates how deep in a recursive loop            |
 |                       | the rendering currently is.  Starts at level 0    |
 +-----------------------+---------------------------------------------------+
 
diff --git a/jinja2/__init__.py b/jinja2/__init__.py
index e68c285..cefd0d6 100644
--- a/jinja2/__init__.py
+++ b/jinja2/__init__.py
@@ -68,3 +68,14 @@
     'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
     'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
 ]
+
+
+def _patch_async():
+    from jinja2.utils import have_async_gen
+    if have_async_gen:
+        from jinja2.asyncsupport import patch_all
+        patch_all()
+
+
+_patch_async()
+del _patch_async
diff --git a/jinja2/_compat.py b/jinja2/_compat.py
index ebe7433..61d8530 100644
--- a/jinja2/_compat.py
+++ b/jinja2/_compat.py
@@ -45,7 +45,6 @@
     implements_iterator = _identity
     implements_to_string = _identity
     encode_filename = _identity
-    get_next = lambda x: x.__next__
 
 else:
     unichr = unichr
@@ -77,8 +76,6 @@
         cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
         return cls
 
-    get_next = lambda x: x.next
-
     def encode_filename(filename):
         if isinstance(filename, unicode):
             return filename.encode('utf-8')
diff --git a/jinja2/asyncfilters.py b/jinja2/asyncfilters.py
new file mode 100644
index 0000000..d12afaf
--- /dev/null
+++ b/jinja2/asyncfilters.py
@@ -0,0 +1,144 @@
+from functools import wraps
+
+from jinja2.asyncsupport import auto_aiter
+from jinja2 import filters
+
+
+async def auto_to_seq(value):
+    seq = []
+    if hasattr(value, '__aiter__'):
+        async for item in value:
+            seq.append(item)
+    else:
+        for item in value:
+            seq.append(item)
+    return seq
+
+
+async def async_select_or_reject(args, kwargs, modfunc, lookup_attr):
+    seq, func = filters.prepare_select_or_reject(
+        args, kwargs, modfunc, lookup_attr)
+    if seq:
+        async for item in auto_aiter(seq):
+            if func(item):
+                yield item
+
+
+def dualfilter(normal_filter, async_filter):
+    wrap_evalctx = False
+    if getattr(normal_filter, 'environmentfilter', False):
+        is_async = lambda args: args[0].is_async
+        wrap_evalctx = False
+    else:
+        if not getattr(normal_filter, 'evalcontextfilter', False) and \
+           not getattr(normal_filter, 'contextfilter', False):
+            wrap_evalctx = True
+        is_async = lambda args: args[0].environment.is_async
+
+    @wraps(normal_filter)
+    def wrapper(*args, **kwargs):
+        b = is_async(args)
+        if wrap_evalctx:
+            args = args[1:]
+        if b:
+            return async_filter(*args, **kwargs)
+        return normal_filter(*args, **kwargs)
+
+    if wrap_evalctx:
+        wrapper.evalcontextfilter = True
+
+    return wrapper
+
+
+def asyncfiltervariant(original):
+    def decorator(f):
+        return dualfilter(original, f)
+    return decorator
+
+
+@asyncfiltervariant(filters.do_first)
+async def do_first(environment, seq):
+    try:
+        return await auto_aiter(seq).__anext__()
+    except StopAsyncIteration:
+        return environment.undefined('No first item, sequence was empty.')
+
+
+@asyncfiltervariant(filters.do_groupby)
+async def do_groupby(environment, value, attribute):
+    expr = filters.make_attrgetter(environment, attribute)
+    return [filters._GroupTuple(key, await auto_to_seq(values))
+            for key, values in filters.groupby(sorted(
+                await auto_to_seq(value), key=expr), expr)]
+
+
+@asyncfiltervariant(filters.do_join)
+async def do_join(eval_ctx, value, d=u'', attribute=None):
+    return filters.do_join(eval_ctx, await auto_to_seq(value), d, attribute)
+
+
+@asyncfiltervariant(filters.do_list)
+async def do_list(value):
+    return await auto_to_seq(value)
+
+
+@asyncfiltervariant(filters.do_reject)
+async def do_reject(*args, **kwargs):
+    return async_select_or_reject(args, kwargs, lambda x: not x, False)
+
+
+@asyncfiltervariant(filters.do_rejectattr)
+async def do_rejectattr(*args, **kwargs):
+    return async_select_or_reject(args, kwargs, lambda x: not x, True)
+
+
+@asyncfiltervariant(filters.do_select)
+async def do_select(*args, **kwargs):
+    return async_select_or_reject(args, kwargs, lambda x: x, False)
+
+
+@asyncfiltervariant(filters.do_selectattr)
+async def do_selectattr(*args, **kwargs):
+    return async_select_or_reject(args, kwargs, lambda x: x, True)
+
+
+@asyncfiltervariant(filters.do_map)
+async def do_map(*args, **kwargs):
+    seq, func = filters.prepare_map(args, kwargs)
+    if seq:
+        async for item in auto_aiter(seq):
+            yield func(item)
+
+
+@asyncfiltervariant(filters.do_sum)
+async def do_sum(environment, iterable, attribute=None, start=0):
+    rv = start
+    if attribute is not None:
+        func = filters.make_attrgetter(environment, attribute)
+    else:
+        func = lambda x: x
+    async for item in auto_aiter(iterable):
+        rv += func(item)
+    return rv
+
+
+@asyncfiltervariant(filters.do_slice)
+async def do_slice(value, slices, fill_with=None):
+    return filters.do_slice(await auto_to_seq(value), slices, fill_with)
+
+
+ASYNC_FILTERS = {
+    'first':        do_first,
+    'groupby':      do_groupby,
+    'join':         do_join,
+    'list':         do_list,
+    # we intentionally do not support do_last because that would be
+    # ridiculous
+    'reject':       do_reject,
+    'rejectattr':   do_rejectattr,
+    'map':          do_map,
+    'select':       do_select,
+    'selectattr':   do_selectattr,
+    'sum':          do_sum,
+    'slice':        do_slice,
+}
diff --git a/jinja2/asyncsupport.py b/jinja2/asyncsupport.py
new file mode 100644
index 0000000..33a1a07
--- /dev/null
+++ b/jinja2/asyncsupport.py
@@ -0,0 +1,237 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja2.asyncsupport
+    ~~~~~~~~~~~~~~~~~~~
+
+    Has all the code for async support which is implemented as a patch
+    for supported Python versions.
+
+    :copyright: (c) 2017 by the Jinja Team.
+    :license: BSD, see LICENSE for more details.
+"""
+import sys
+import asyncio
+import inspect
+from functools import update_wrapper
+
+from jinja2.utils import concat, internalcode, concat, Markup
+from jinja2.environment import TemplateModule
+from jinja2.runtime import LoopContextBase, _last_iteration
+
+
+async def concat_async(async_gen):
+    rv = []
+    async def collect():
+        async for event in async_gen:
+            rv.append(event)
+    await collect()
+    return concat(rv)
+
+
+async def generate_async(self, *args, **kwargs):
+    vars = dict(*args, **kwargs)
+    try:
+        async for event in self.root_render_func(self.new_context(vars)):
+            yield event
+    except Exception:
+        exc_info = sys.exc_info()
+    else:
+        return
+    yield self.environment.handle_exception(exc_info, True)
+
+
+def wrap_generate_func(original_generate):
+    def _convert_generator(self, loop, args, kwargs):
+        async_gen = self.generate_async(*args, **kwargs)
+        try:
+            while 1:
+                yield loop.run_until_complete(async_gen.__anext__())
+        except StopAsyncIteration:
+            pass
+    def generate(self, *args, **kwargs):
+        if not self.environment.is_async:
+            return original_generate(self, *args, **kwargs)
+        return _convert_generator(self, asyncio.get_event_loop(), args, kwargs)
+    return update_wrapper(generate, original_generate)
+
+
+async def render_async(self, *args, **kwargs):
+    if not self.environment.is_async:
+        raise RuntimeError('The environment was not created with async mode '
+                           'enabled.')
+
+    vars = dict(*args, **kwargs)
+    ctx = self.new_context(vars)
+
+    try:
+        return await concat_async(self.root_render_func(ctx))
+    except Exception:
+        exc_info = sys.exc_info()
+    return self.environment.handle_exception(exc_info, True)
+
+
+def wrap_render_func(original_render):
+    def render(self, *args, **kwargs):
+        if not self.environment.is_async:
+            return original_render(self, *args, **kwargs)
+        loop = asyncio.get_event_loop()
+        return loop.run_until_complete(self.render_async(*args, **kwargs))
+    return update_wrapper(render, original_render)
+
+
+def wrap_block_reference_call(original_call):
+    @internalcode
+    async def async_call(self):
+        rv = await concat_async(self._stack[self._depth](self._context))
+        if self._context.eval_ctx.autoescape:
+            rv = Markup(rv)
+        return rv
+
+    @internalcode
+    def __call__(self):
+        if not self._context.environment.is_async:
+            return original_call(self)
+        return async_call(self)
+
+    return update_wrapper(__call__, original_call)
+
+
+@internalcode
+async def get_default_module_async(self):
+    if self._module is not None:
+        return self._module
+    self._module = rv = await self.make_module_async()
+    return rv
+
+
+def wrap_default_module(original_default_module):
+    @internalcode
+    def _get_default_module(self):
+        if self.environment.is_async:
+            raise RuntimeError('Template module attribute is unavailable '
+                               'in async mode')
+        return original_default_module(self)
+    return _get_default_module
+
+
+async def make_module_async(self, vars=None, shared=False, locals=None):
+    context = self.new_context(vars, shared, locals)
+    body_stream = []
+    async for item in self.root_render_func(context):
+        body_stream.append(item)
+    return TemplateModule(self, context, body_stream)
+
+
+def patch_template():
+    from jinja2 import Template
+    Template.generate = wrap_generate_func(Template.generate)
+    Template.generate_async = update_wrapper(
+        generate_async, Template.generate_async)
+    Template.render_async = update_wrapper(
+        render_async, Template.render_async)
+    Template.render = wrap_render_func(Template.render)
+    Template._get_default_module = wrap_default_module(
+        Template._get_default_module)
+    Template._get_default_module_async = get_default_module_async
+    Template.make_module_async = update_wrapper(
+        make_module_async, Template.make_module_async)
+
+
+def patch_runtime():
+    from jinja2.runtime import BlockReference
+    BlockReference.__call__ = wrap_block_reference_call(
+        BlockReference.__call__)
+
+
+def patch_filters():
+    from jinja2.filters import FILTERS
+    from jinja2.asyncfilters import ASYNC_FILTERS
+    FILTERS.update(ASYNC_FILTERS)
+
+
+def patch_all():
+    patch_template()
+    patch_runtime()
+    patch_filters()
+
+
+async def auto_await(value):
+    if inspect.isawaitable(value):
+        return await value
+    return value
+
+
+async def auto_aiter(iterable):
+    if hasattr(iterable, '__aiter__'):
+        async for item in iterable:
+            yield item
+        return
+    for item in iterable:
+        yield item
+
+
+class AsyncLoopContext(LoopContextBase):
+
+    def __init__(self, async_iterator, after, length, recurse=None,
+                 depth0=0):
+        LoopContextBase.__init__(self, recurse, depth0)
+        self._async_iterator = async_iterator
+        self._after = after
+        self._length = length
+
+    @property
+    def length(self):
+        if self._length is None:
+            raise TypeError('Loop length for some iterators cannot be '
+                            'lazily calculated in async mode')
+        return self._length
+
+    def __aiter__(self):
+        return AsyncLoopContextIterator(self)
+
+
+class AsyncLoopContextIterator(object):
+    __slots__ = ('context',)
+
+    def __init__(self, context):
+        self.context = context
+
+    def __aiter__(self):
+        return self
+
+    async def __anext__(self):
+        ctx = self.context
+        ctx.index0 += 1
+        if ctx._after is _last_iteration:
+            raise StopAsyncIteration()
+        next_elem = ctx._after
+        try:
+            ctx._after = await ctx._async_iterator.__anext__()
+        except StopAsyncIteration:
+            ctx._after = _last_iteration
+        return next_elem, ctx
+
+
+async def make_async_loop_context(iterable, recurse=None, depth0=0):
+    # Length is more complicated and less efficient in async mode.  The
+    # reason for this is that we cannot know if length will be used
+    # upfront but because length is a property we cannot lazily execute it
+    # later.  This means that we need to buffer it up and measure :(
+    #
+    # We however only do this for actual iterators, not for async
+    # iterators as blocking here does not seem like the best idea in the
+    # world.
+    try:
+        length = len(iterable)
+    except (TypeError, AttributeError):
+        if not hasattr(iterable, '__aiter__'):
+            iterable = tuple(iterable)
+            length = len(iterable)
+        else:
+            length = None
+    async_iterator = auto_aiter(iterable)
+    try:
+        after = await async_iterator.__anext__()
+    except StopAsyncIteration:
+        after = _last_iteration
+    return AsyncLoopContext(async_iterator, after, length, recurse, depth0)
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index fad007b..0fe0d92 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -38,6 +38,15 @@
 else:
     dict_item_iter = 'items'
 
+code_features = ['division']
+
+# does this python version support generator stops? (PEP 0479)
+try:
+    exec('from __future__ import generator_stop')
+    code_features.append('generator_stop')
+except SyntaxError:
+    pass
+
 
 # does if 0: dummy(x) get us x into the scope?
 def unoptimize_before_dead_code():
@@ -639,6 +648,11 @@
             #   a = 42; b = lambda: a; del a
             self.writeline(' = '.join(to_delete) + ' = missing')
 
+    def func(self, name):
+        if self.environment.is_async:
+            return 'async def %s' % name
+        return 'def %s' % name
+
     def function_scoping(self, node, frame, children=None,
                          find_special=True):
         """In Jinja a few statements require the help of anonymous
@@ -723,7 +737,7 @@
         # and assigned.
         if 'loop' in frame.identifiers.declared:
             args = args + ['l_loop=l_loop']
-        self.writeline('def macro(%s):' % ', '.join(args), node)
+        self.writeline('%s(%s):' % (self.func('macro'), ', '.join(args)), node)
         self.indent()
         self.buffer(frame)
         self.pull_locals(frame)
@@ -763,11 +777,15 @@
         eval_ctx = EvalContext(self.environment, self.name)
 
         from jinja2.runtime import __all__ as exported
-        self.writeline('from __future__ import division')
+        self.writeline('from __future__ import %s' % ', '.join(code_features))
         self.writeline('from jinja2.runtime import ' + ', '.join(exported))
         if not unoptimize_before_dead_code:
             self.writeline('dummy = lambda *x: None')
 
+        if self.environment.is_async:
+            self.writeline('from jinja2.asyncsupport import auto_await, '
+                           'auto_aiter, make_async_loop_context')
+
         # if we want a deferred initialization we cannot move the
         # environment into a local name
         envenv = not self.defer_init and ', environment=environment' or ''
@@ -798,7 +816,7 @@
         self.writeline('name = %r' % self.name)
 
         # generate the root render function.
-        self.writeline('def root(context%s):' % envenv, extra=1)
+        self.writeline('%s(context%s):' % (self.func('root'), envenv), extra=1)
 
         # process the root
         frame = Frame(eval_ctx)
@@ -833,7 +851,7 @@
             block_frame = Frame(eval_ctx)
             block_frame.inspect(block.body)
             block_frame.block = name
-            self.writeline('def block_%s(context%s):' % (name, envenv),
+            self.writeline('%s(context%s):' % (self.func('block_' + name), envenv),
                            block, 1)
             self.indent()
             undeclared = find_undeclared(block.body, ('self', 'super'))
@@ -870,8 +888,10 @@
                 self.indent()
                 level += 1
         context = node.scoped and 'context.derived(locals())' or 'context'
-        self.writeline('for event in context.blocks[%r][0](%s):' % (
-                       node.name, context), node)
+
+        loop = self.environment.is_async and 'async for' or 'for'
+        self.writeline('%s event in context.blocks[%r][0](%s):' % (
+                       loop, node.name, context), node)
         self.indent()
         self.simple_write('event', frame)
         self.outdent(level)
@@ -953,11 +973,17 @@
             self.indent()
 
         if node.with_context:
-            self.writeline('for event in template.root_render_func('
+            loop = self.environment.is_async and 'async for' or 'for'
+            self.writeline('%s event in template.root_render_func('
                            'template.new_context(context.parent, True, '
-                           'locals())):')
+                           'locals())):' % loop)
+        elif self.environment.is_async:
+            self.writeline('for event in (await '
+                           'template._get_default_module_async())'
+                           '._body_stream:')
         else:
-            self.writeline('for event in template.module._body_stream:')
+            self.writeline('for event in template._get_default_module()'
+                           '._body_stream:')
 
         self.indent()
         self.simple_write('event', frame)
@@ -973,13 +999,18 @@
         self.writeline('l_%s = ' % node.target, node)
         if frame.toplevel:
             self.write('context.vars[%r] = ' % node.target)
+        if self.environment.is_async:
+            self.write('await ')
         self.write('environment.get_template(')
         self.visit(node.template, frame)
         self.write(', %r).' % self.name)
         if node.with_context:
-            self.write('make_module(context.parent, True, locals())')
+            self.write('make_module%s(context.parent, True, locals())'
+                       % (self.environment.is_async and '_async' or ''))
+        elif self.environment.is_async:
+            self.write('_get_default_module_async()')
         else:
-            self.write('module')
+            self.write('_get_default_module()')
         if frame.toplevel and not node.target.startswith('_'):
             self.writeline('context.exported_vars.discard(%r)' % node.target)
         frame.assigned_names.add(node.target)
@@ -987,13 +1018,17 @@
     def visit_FromImport(self, node, frame):
         """Visit named imports."""
         self.newline(node)
-        self.write('included_template = environment.get_template(')
+        self.write('included_template = %senvironment.get_template('
+                   % (self.environment.is_async and 'await ' or ''))
         self.visit(node.template, frame)
         self.write(', %r).' % self.name)
         if node.with_context:
-            self.write('make_module(context.parent, True)')
+            self.write('make_module%s(context.parent, True)'
+                       % (self.environment.is_async and '_async' or ''))
+        elif self.environment.is_async:
+            self.write('_get_default_module_async()')
         else:
-            self.write('module')
+            self.write('_get_default_module()')
 
         var_names = []
         discarded_names = []
@@ -1063,7 +1098,8 @@
 
         # otherwise we set up a buffer and add a function def
         else:
-            self.writeline('def loop(reciter, loop_render_func, depth=0):', node)
+            self.writeline('%s(reciter, loop_render_func, depth=0):' %
+                           self.func('loop'), node)
             self.indent()
             self.buffer(loop_frame)
             aliases = {}
@@ -1095,22 +1131,32 @@
                  "loop it's undefined.  Happened in loop on %s" %
                  self.position(node)))
 
-        self.writeline('for ', node)
+        self.writeline(self.environment.is_async and 'async for ' or 'for ', node)
         self.visit(node.target, loop_frame)
-        self.write(extended_loop and ', l_loop in LoopContext(' or ' in ')
+        if extended_loop:
+            if self.environment.is_async:
+                self.write(', l_loop in await make_async_loop_context(')
+            else:
+                self.write(', l_loop in LoopContext(')
+        else:
+            self.write(' in ')
 
         # if we have an extened loop and a node test, we filter in the
         # "outer frame".
         if extended_loop and node.test is not None:
             self.write('(')
             self.visit(node.target, loop_frame)
-            self.write(' for ')
+            self.write(self.environment.is_async and ' async for ' or ' for ')
             self.visit(node.target, loop_frame)
             self.write(' in ')
             if node.recursive:
                 self.write('reciter')
             else:
+                if self.environment.is_async:
+                    self.write('auto_aiter(')
                 self.visit(node.iter, loop_frame)
+                if self.environment.is_async:
+                    self.write(')')
             self.write(' if (')
             test_frame = loop_frame.copy()
             self.visit(node.test, test_frame)
@@ -1119,7 +1165,11 @@
         elif node.recursive:
             self.write('reciter')
         else:
+            if self.environment.is_async and not extended_loop:
+                self.write('auto_aiter(')
             self.visit(node.iter, loop_frame)
+            if self.environment.is_async and not extended_loop:
+                self.write(')')
 
         if node.recursive:
             self.write(', loop_render_func, depth):')
@@ -1158,8 +1208,14 @@
             self.return_buffer_contents(loop_frame)
             self.outdent()
             self.start_write(frame, node)
+            if self.environment.is_async:
+                self.write('await ')
             self.write('loop(')
+            if self.environment.is_async:
+                self.write('auto_aiter(')
             self.visit(node.iter, frame)
+            if self.environment.is_async:
+                self.write(')')
             self.write(', loop)')
             self.end_write(frame)
 
@@ -1402,6 +1458,10 @@
 
     def visit_AssignBlock(self, node, frame):
         block_frame = frame.inner()
+        # This is a special case.  Since a set block always captures we
+        # will disable output checks.  This way one can use set blocks
+        # toplevel even in extended templates.
+        block_frame.require_output_check = False
         block_frame.inspect(node.body)
         aliases = self.push_scope(block_frame)
         self.pull_locals(block_frame)
@@ -1559,6 +1619,8 @@
             self.visit(node.step, frame)
 
     def visit_Filter(self, node, frame):
+        if self.environment.is_async:
+            self.write('await auto_await(')
         self.write(self.filters[node.name] + '(')
         func = self.environment.filters.get(node.name)
         if func is None:
@@ -1584,6 +1646,8 @@
             self.write('concat(%s)' % frame.buffer)
         self.signature(node, frame)
         self.write(')')
+        if self.environment.is_async:
+            self.write(')')
 
     def visit_Test(self, node, frame):
         self.write(self.tests[node.name] + '(')
@@ -1610,6 +1674,8 @@
         self.write(')')
 
     def visit_Call(self, node, frame, forward_caller=False):
+        if self.environment.is_async:
+            self.write('await auto_await(')
         if self.environment.sandboxed:
             self.write('environment.call(context, ')
         else:
@@ -1618,6 +1684,8 @@
         extra_kwargs = forward_caller and {'caller': 'caller'} or None
         self.signature(node, frame, extra_kwargs)
         self.write(')')
+        if self.environment.is_async:
+            self.write(')')
 
     def visit_Keyword(self, node, frame):
         self.write(node.key + '=')
diff --git a/jinja2/defaults.py b/jinja2/defaults.py
index 3717a72..bdb538d 100644
--- a/jinja2/defaults.py
+++ b/jinja2/defaults.py
@@ -39,5 +39,12 @@
 }
 
 
+# default policies
+DEFAULT_POLICIES = {
+    'urlize.rel':       'noopener',
+    'urlize.target':    None,
+}
+
+
 # export all constants
 __all__ = tuple(x for x in locals().keys() if x.isupper())
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 8b2572b..cfa7ff1 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -10,13 +10,15 @@
 """
 import os
 import sys
+import weakref
+from functools import reduce, partial
 from jinja2 import nodes
 from jinja2.defaults import BLOCK_START_STRING, \
      BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
      COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \
      LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \
      DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE, \
-     KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
+     DEFAULT_POLICIES, KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
 from jinja2.lexer import get_lexer, TokenStream
 from jinja2.parser import Parser
 from jinja2.nodes import EvalContext
@@ -26,11 +28,10 @@
 from jinja2.exceptions import TemplateSyntaxError, TemplateNotFound, \
      TemplatesNotFound, TemplateRuntimeError
 from jinja2.utils import import_string, LRUCache, Markup, missing, \
-     concat, consume, internalcode
+     concat, consume, internalcode, have_async_gen
 from jinja2._compat import imap, ifilter, string_types, iteritems, \
      text_type, reraise, implements_iterator, implements_to_string, \
-     get_next, encode_filename, PY2, PYPY
-from functools import reduce
+     encode_filename, PY2, PYPY
 
 
 # for direct template usage we have up to ten living environments
@@ -167,7 +168,7 @@
             look at :ref:`the extensions documentation <jinja-extensions>`.
 
         `optimized`
-            should the optimizer be enabled?  Default is `True`.
+            should the optimizer be enabled?  Default is ``True``.
 
         `undefined`
             :class:`Undefined` or a subclass of it that is used to represent
@@ -176,14 +177,14 @@
         `finalize`
             A callable that can be used to process the result of a variable
             expression before it is output.  For example one can convert
-            `None` implicitly into an empty string here.
+            ``None`` implicitly into an empty string here.
 
         `autoescape`
-            If set to true the XML/HTML autoescaping feature is enabled by
+            If set to ``True`` the XML/HTML autoescaping feature is enabled by
             default.  For more details about autoescaping see
             :class:`~jinja2.utils.Markup`.  As of Jinja 2.4 this can also
             be a callable that is passed the template name and has to
-            return `True` or `False` depending on autoescape should be
+            return ``True`` or ``False`` depending on autoescape should be
             enabled by default.
 
             .. versionchanged:: 2.4
@@ -205,7 +206,7 @@
         `auto_reload`
             Some loaders load templates from locations where the template
             sources may change (ie: file system or database).  If
-            `auto_reload` is set to `True` (default) every time a template is
+            ``auto_reload`` is set to ``True`` (default) every time a template is
             requested the loader checks if the source changed and if yes, it
             will reload the template.  For higher performance it's possible to
             disable that.
@@ -216,6 +217,11 @@
             have to be parsed if they were not changed.
 
             See :ref:`bytecode-cache` for more information.
+
+        `enable_async`
+            If set to true this enables async template execution which allows
+            you to take advantage of newer Python features.  This requires
+            Python 3.6 or later.
     """
 
     #: if this environment is sandboxed.  Modifying this variable won't make
@@ -267,7 +273,8 @@
                  loader=None,
                  cache_size=400,
                  auto_reload=True,
-                 bytecode_cache=None):
+                 bytecode_cache=None,
+                 enable_async=False):
         # !!Important notice!!
         #   The constructor accepts quite a few arguments that should be
         #   passed by keyword rather than position.  However it's important to
@@ -310,9 +317,15 @@
         self.bytecode_cache = bytecode_cache
         self.auto_reload = auto_reload
 
+        # configurable policies
+        self.policies = DEFAULT_POLICIES.copy()
+
         # load extensions
         self.extensions = load_extensions(self, extensions)
 
+        self.enable_async = enable_async
+        self.is_async = self.enable_async and have_async_gen
+
         _environment_sanity_check(self)
 
     def add_extension(self, extension):
@@ -387,7 +400,7 @@
         """Get an item or attribute of an object but prefer the item."""
         try:
             return obj[argument]
-        except (TypeError, LookupError):
+        except (AttributeError, TypeError, LookupError):
             if isinstance(argument, string_types):
                 try:
                     attr = str(argument)
@@ -417,6 +430,11 @@
                     context=None, eval_ctx=None):
         """Invokes a filter on a value the same way the compiler does it.
 
+        Note that on Python 3 this might return a coroutine in case the
+        filter is running from an environment in async mode and the filter
+        supports async execution.  It's your responsibility to await this
+        if needed.
+
         .. versionadded:: 2.7
         """
         func = self.filters.get(name)
@@ -769,15 +787,7 @@
     def _load_template(self, name, globals):
         if self.loader is None:
             raise TypeError('no loader for this environment specified')
-        try:
-            # use abs path for cache key
-            cache_key = self.loader.get_source(self, name)[1]
-        except RuntimeError:
-            # if loader does not implement get_source()
-            cache_key = None
-        # if template is not file, use name for cache key
-        if cache_key is None:
-            cache_key = name
+        cache_key = (weakref.ref(self.loader), name)
         if self.cache is not None:
             template = self.cache.get(cache_key)
             if template is not None and (not self.auto_reload or
@@ -915,14 +925,15 @@
                 optimized=True,
                 undefined=Undefined,
                 finalize=None,
-                autoescape=False):
+                autoescape=False,
+                enable_async=False):
         env = get_spontaneous_environment(
             block_start_string, block_end_string, variable_start_string,
             variable_end_string, comment_start_string, comment_end_string,
             line_statement_prefix, line_comment_prefix, trim_blocks,
             lstrip_blocks, newline_sequence, keep_trailing_newline,
             frozenset(extensions), optimized, undefined, finalize, autoescape,
-            None, 0, False, None)
+            None, 0, False, None, enable_async)
         return env.from_string(source, template_class=cls)
 
     @classmethod
@@ -988,6 +999,19 @@
             exc_info = sys.exc_info()
         return self.environment.handle_exception(exc_info, True)
 
+    def render_async(self, *args, **kwargs):
+        """This works similar to :meth:`render` but returns a coroutine
+        that when awaited returns the entire rendered template string.  This
+        requires the async feature to be enabled.
+
+        Example usage::
+
+            await template.render_async(knights='that say nih; asynchronously')
+        """
+        # see asyncsupport for the actual implementation
+        raise NotImplementedError('This feature is not available for this '
+                                  'version of Python')
+
     def stream(self, *args, **kwargs):
         """Works exactly like :meth:`generate` but returns a
         :class:`TemplateStream`.
@@ -1012,6 +1036,14 @@
             return
         yield self.environment.handle_exception(exc_info, True)
 
+    def generate_async(self, *args, **kwargs):
+        """An async version of :meth:`generate`.  Works very similarly but
+        returns an async iterator instead.
+        """
+        # see asyncsupport for the actual implementation
+        raise NotImplementedError('This feature is not available for this '
+                                  'version of Python')
+
     def new_context(self, vars=None, shared=False, locals=None):
         """Create a new :class:`Context` for this template.  The vars
         provided will be passed to the template.  Per default the globals
@@ -1032,6 +1064,23 @@
         """
         return TemplateModule(self, self.new_context(vars, shared, locals))
 
+    def make_module_async(self, vars=None, shared=False, locals=None):
+        """As template module creation can invoke template code for
+        asynchronous exections this method must be used instead of the
+        normal :meth:`make_module` one.  Likewise the module attribute
+        becomes unavailable in async mode.
+        """
+        # see asyncsupport for the actual implementation
+        raise NotImplementedError('This feature is not available for this '
+                                  'version of Python')
+
+    @internalcode
+    def _get_default_module(self):
+        if self._module is not None:
+            return self._module
+        self._module = rv = self.make_module()
+        return rv
+
     @property
     def module(self):
         """The template as module.  This is used for imports in the
@@ -1043,11 +1092,10 @@
         '23'
         >>> t.module.foo() == u'42'
         True
+
+        This attribute is not available if async mode is enabled.
         """
-        if self._module is not None:
-            return self._module
-        self._module = rv = self.make_module()
-        return rv
+        return self._get_default_module()
 
     def get_corresponding_lineno(self, lineno):
         """Return the source line number of a line number in the
@@ -1086,8 +1134,15 @@
     converting it into an unicode- or bytestrings renders the contents.
     """
 
-    def __init__(self, template, context):
-        self._body_stream = list(template.root_render_func(context))
+    def __init__(self, template, context, body_stream=None):
+        if body_stream is None:
+            if context.environment.is_async:
+                raise RuntimeError('Async mode requires a body stream '
+                                   'to be passed to a template module.  Use '
+                                   'the async methods of the API you are '
+                                   'using.')
+            body_stream = list(template.root_render_func(context))
+        self._body_stream = body_stream
         self.__dict__.update(context.get_exported())
         self.__name__ = template.name
 
@@ -1171,35 +1226,35 @@
 
     def disable_buffering(self):
         """Disable the output buffering."""
-        self._next = get_next(self._gen)
+        self._next = partial(next, self._gen)
         self.buffered = False
 
+    def _buffered_generator(self, size):
+        buf = []
+        c_size = 0
+        push = buf.append
+
+        while 1:
+            try:
+                while c_size < size:
+                    c = next(self._gen)
+                    push(c)
+                    if c:
+                        c_size += 1
+            except StopIteration:
+                if not c_size:
+                    return
+            yield concat(buf)
+            del buf[:]
+            c_size = 0
+
     def enable_buffering(self, size=5):
         """Enable buffering.  Buffer `size` items before yielding them."""
         if size <= 1:
             raise ValueError('buffer size too small')
 
-        def generator(next):
-            buf = []
-            c_size = 0
-            push = buf.append
-
-            while 1:
-                try:
-                    while c_size < size:
-                        c = next()
-                        push(c)
-                        if c:
-                            c_size += 1
-                except StopIteration:
-                    if not c_size:
-                        return
-                yield concat(buf)
-                del buf[:]
-                c_size = 0
-
         self.buffered = True
-        self._next = get_next(generator(get_next(self._gen)))
+        self._next = partial(next, self._buffered_generator(size))
 
     def __iter__(self):
         return self
diff --git a/jinja2/filters.py b/jinja2/filters.py
index cd89f6f..c93c8ff 100644
--- a/jinja2/filters.py
+++ b/jinja2/filters.py
@@ -12,8 +12,8 @@
 import math
 
 from random import choice
-from operator import itemgetter
 from itertools import groupby
+from collections import namedtuple
 from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \
      unicode_urlencode
 from jinja2.runtime import Undefined
@@ -21,7 +21,8 @@
 from jinja2._compat import imap, string_types, text_type, iteritems
 
 
-_word_re = re.compile(r'\w+(?u)')
+_word_re = re.compile(r'\w+', re.UNICODE)
+_word_beginning_split_re = re.compile(r'([-\s\(\{\[\<]+)', re.UNICODE)
 
 
 def contextfilter(f):
@@ -183,12 +184,10 @@
     """Return a titlecased version of the value. I.e. words will start with
     uppercase letters, all remaining characters are lowercase.
     """
-    rv = []
-    for item in re.compile(r'([-\s]+)(?u)').split(soft_unicode(s)):
-        if not item:
-            continue
-        rv.append(item[0].upper() + item[1:].lower())
-    return ''.join(rv)
+    return ''.join(
+        [item[0].upper() + item[1:].lower()
+         for item in _word_beginning_split_re.split(soft_unicode(s))
+         if item])
 
 
 def do_dictsort(value, case_sensitive=False, by='key'):
@@ -410,7 +409,7 @@
 
 @evalcontextfilter
 def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False,
-              target=None):
+              target=None, rel=None):
     """Converts URLs in plain text into clickable links.
 
     If you pass the filter an additional integer it will shorten the urls
@@ -432,7 +431,15 @@
     .. versionchanged:: 2.8+
        The *target* parameter was added.
     """
-    rv = urlize(value, trim_url_limit, nofollow, target)
+    policies = eval_ctx.environment.policies
+    rel = set((rel or '').split() or [])
+    if nofollow:
+        rel.add('nofollow')
+    rel.update((policies['urlize.rel'] or '').split())
+    if target is None:
+        target = policies['urlize.target']
+    rel = ' '.join(sorted(rel)) or None
+    rv = urlize(value, trim_url_limit, rel=rel, target=target)
     if eval_ctx.autoescape:
         rv = Markup(rv)
     return rv
@@ -518,9 +525,12 @@
     can also override the default base (10) in the second
     parameter, which handles input with prefixes such as
     0b, 0o and 0x for bases 2, 8 and 16 respectively.
+    The base is ignored for decimal numbers and non-string values.
     """
     try:
-        return int(value, base)
+        if isinstance(value, string_types):
+            return int(value, base)
+        return int(value)
     except (TypeError, ValueError):
         # this quirk is necessary so that "42.23"|int gives 42.
         try:
@@ -669,6 +679,8 @@
     return func(value * (10 ** precision)) / (10 ** precision)
 
 
+_GroupTuple = namedtuple('_GroupTuple', ['grouper', 'list'])
+
 @environmentfilter
 def do_groupby(environment, value, attribute):
     """Group a sequence of objects by a common attribute.
@@ -709,17 +721,7 @@
        attribute of another attribute.
     """
     expr = make_attrgetter(environment, attribute)
-    return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
-
-
-class _GroupTuple(tuple):
-    __slots__ = ()
-    grouper = property(itemgetter(0))
-    list = property(itemgetter(1))
-
-    def __new__(cls, xxx_todo_changeme):
-        (key, value) = xxx_todo_changeme
-        return tuple.__new__(cls, (key, list(value)))
+    return [_GroupTuple(key, list(values)) for key, values in groupby(sorted(value, key=expr), expr)]
 
 
 @environmentfilter
@@ -827,6 +829,90 @@
 
     .. versionadded:: 2.7
     """
+    seq, func = prepare_map(args, kwargs)
+    if seq:
+        for item in seq:
+            yield func(item)
+
+
+@contextfilter
+def do_select(*args, **kwargs):
+    """Filters a sequence of objects by applying a test to each object,
+    and only selecting the objects with the test succeeding.
+
+    If no test is specified, each object will be evaluated as a boolean.
+
+    Example usage:
+
+    .. sourcecode:: jinja
+
+        {{ numbers|select("odd") }}
+        {{ numbers|select("odd") }}
+
+    .. versionadded:: 2.7
+    """
+    return select_or_reject(args, kwargs, lambda x: x, False)
+
+
+@contextfilter
+def do_reject(*args, **kwargs):
+    """Filters a sequence of objects by applying a test to each object,
+    and rejecting the objects with the test succeeding.
+
+    If no test is specified, each object will be evaluated as a boolean.
+
+    Example usage:
+
+    .. sourcecode:: jinja
+
+        {{ numbers|reject("odd") }}
+
+    .. versionadded:: 2.7
+    """
+    return select_or_reject(args, kwargs, lambda x: not x, False)
+
+
+@contextfilter
+def do_selectattr(*args, **kwargs):
+    """Filters a sequence of objects by applying a test to the specified
+    attribute of each object, and only selecting the objects with the
+    test succeeding.
+
+    If no test is specified, the attribute's value will be evaluated as
+    a boolean.
+
+    Example usage:
+
+    .. sourcecode:: jinja
+
+        {{ users|selectattr("is_active") }}
+        {{ users|selectattr("email", "none") }}
+
+    .. versionadded:: 2.7
+    """
+    return select_or_reject(args, kwargs, lambda x: x, True)
+
+
+@contextfilter
+def do_rejectattr(*args, **kwargs):
+    """Filters a sequence of objects by applying a test to the specified
+    attribute of each object, and rejecting the objects with the test
+    succeeding.
+
+    If no test is specified, the attribute's value will be evaluated as
+    a boolean.
+
+    .. sourcecode:: jinja
+
+        {{ users|rejectattr("is_active") }}
+        {{ users|rejectattr("email", "none") }}
+
+    .. versionadded:: 2.7
+    """
+    return select_or_reject(args, kwargs, lambda x: not x, True)
+
+
+def prepare_map(args, kwargs):
     context = args[0]
     seq = args[1]
 
@@ -845,77 +931,10 @@
         func = lambda item: context.environment.call_filter(
             name, item, args, kwargs, context=context)
 
-    if seq:
-        for item in seq:
-            yield func(item)
+    return seq, func
 
 
-@contextfilter
-def do_select(*args, **kwargs):
-    """Filters a sequence of objects by applying a test to the object and only
-    selecting the ones with the test succeeding.
-
-    Example usage:
-
-    .. sourcecode:: jinja
-
-        {{ numbers|select("odd") }}
-        {{ numbers|select("odd") }}
-
-    .. versionadded:: 2.7
-    """
-    return _select_or_reject(args, kwargs, lambda x: x, False)
-
-
-@contextfilter
-def do_reject(*args, **kwargs):
-    """Filters a sequence of objects by applying a test to the object and
-    rejecting the ones with the test succeeding.
-
-    Example usage:
-
-    .. sourcecode:: jinja
-
-        {{ numbers|reject("odd") }}
-
-    .. versionadded:: 2.7
-    """
-    return _select_or_reject(args, kwargs, lambda x: not x, False)
-
-
-@contextfilter
-def do_selectattr(*args, **kwargs):
-    """Filters a sequence of objects by applying a test to an attribute of an
-    object and only selecting the ones with the test succeeding.
-
-    Example usage:
-
-    .. sourcecode:: jinja
-
-        {{ users|selectattr("is_active") }}
-        {{ users|selectattr("email", "none") }}
-
-    .. versionadded:: 2.7
-    """
-    return _select_or_reject(args, kwargs, lambda x: x, True)
-
-
-@contextfilter
-def do_rejectattr(*args, **kwargs):
-    """Filters a sequence of objects by applying a test to an attribute of an
-    object or the attribute and rejecting the ones with the test succeeding.
-
-    .. sourcecode:: jinja
-
-        {{ users|rejectattr("is_active") }}
-        {{ users|rejectattr("email", "none") }}
-
-    .. versionadded:: 2.7
-    """
-    return _select_or_reject(args, kwargs, lambda x: not x, True)
-
-
-def _select_or_reject(args, kwargs, modfunc, lookup_attr):
+def prepare_select_or_reject(args, kwargs, modfunc, lookup_attr):
     context = args[0]
     seq = args[1]
     if lookup_attr:
@@ -937,9 +956,14 @@
     except LookupError:
         func = bool
 
+    return seq, lambda item: modfunc(func(transfunc(item)))
+
+
+def select_or_reject(args, kwargs, modfunc, lookup_attr):
+    seq, func = prepare_select_or_reject(args, kwargs, modfunc, lookup_attr)
     if seq:
         for item in seq:
-            if modfunc(func(transfunc(item))):
+            if func(item):
                 yield item
 
 
diff --git a/jinja2/loaders.py b/jinja2/loaders.py
index 44aa392..70882f5 100644
--- a/jinja2/loaders.py
+++ b/jinja2/loaders.py
@@ -351,7 +351,7 @@
         try:
             return loader.get_source(environment, name)
         except TemplateNotFound:
-            # re-raise the exception with the correct fileame here.
+            # re-raise the exception with the correct filename here.
             # (the one that includes the prefix)
             raise TemplateNotFound(template)
 
@@ -361,7 +361,7 @@
         try:
             return loader.load(environment, local_name, globals)
         except TemplateNotFound:
-            # re-raise the exception with the correct fileame here.
+            # re-raise the exception with the correct filename here.
             # (the one that includes the prefix)
             raise TemplateNotFound(name)
 
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index d32046c..6d4593b 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -604,7 +604,7 @@
 
     def as_const(self, eval_ctx=None):
         eval_ctx = get_eval_context(self, eval_ctx)
-        if eval_ctx.volatile:
+        if eval_ctx.volatile or eval_ctx.environment.sandboxed:
             raise Impossible()
         obj = self.node.as_const(eval_ctx)
 
diff --git a/jinja2/parser.py b/jinja2/parser.py
index d24da18..cdb3fe8 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -19,6 +19,15 @@
                                  'set'])
 _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq'])
 
+_math_nodes = {
+    'add': nodes.Add,
+    'sub': nodes.Sub,
+    'mul': nodes.Mul,
+    'div': nodes.Div,
+    'floordiv': nodes.FloorDiv,
+    'mod': nodes.Mod,
+}
+
 
 class Parser(object):
     """This is the central parsing class Jinja2 uses.  It's passed to
@@ -429,19 +438,19 @@
 
     def parse_compare(self):
         lineno = self.stream.current.lineno
-        expr = self.parse_add()
+        expr = self.parse_math1()
         ops = []
         while 1:
             token_type = self.stream.current.type
             if token_type in _compare_operators:
                 next(self.stream)
-                ops.append(nodes.Operand(token_type, self.parse_add()))
+                ops.append(nodes.Operand(token_type, self.parse_math1()))
             elif self.stream.skip_if('name:in'):
-                ops.append(nodes.Operand('in', self.parse_add()))
+                ops.append(nodes.Operand('in', self.parse_math1()))
             elif (self.stream.current.test('name:not') and
                   self.stream.look().test('name:in')):
                 self.stream.skip(2)
-                ops.append(nodes.Operand('notin', self.parse_add()))
+                ops.append(nodes.Operand('notin', self.parse_math1()))
             else:
                 break
             lineno = self.stream.current.lineno
@@ -449,73 +458,35 @@
             return expr
         return nodes.Compare(expr, ops, lineno=lineno)
 
-    def parse_add(self):
-        lineno = self.stream.current.lineno
-        left = self.parse_sub()
-        while self.stream.current.type == 'add':
-            next(self.stream)
-            right = self.parse_sub()
-            left = nodes.Add(left, right, lineno=lineno)
-            lineno = self.stream.current.lineno
-        return left
-
-    def parse_sub(self):
+    def parse_math1(self):
         lineno = self.stream.current.lineno
         left = self.parse_concat()
-        while self.stream.current.type == 'sub':
+        while self.stream.current.type in ('add', 'sub'):
+            cls = _math_nodes[self.stream.current.type]
             next(self.stream)
             right = self.parse_concat()
-            left = nodes.Sub(left, right, lineno=lineno)
+            left = cls(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
         return left
 
     def parse_concat(self):
         lineno = self.stream.current.lineno
-        args = [self.parse_mul()]
+        args = [self.parse_math2()]
         while self.stream.current.type == 'tilde':
             next(self.stream)
-            args.append(self.parse_mul())
+            args.append(self.parse_math2())
         if len(args) == 1:
             return args[0]
         return nodes.Concat(args, lineno=lineno)
 
-    def parse_mul(self):
-        lineno = self.stream.current.lineno
-        left = self.parse_div()
-        while self.stream.current.type == 'mul':
-            next(self.stream)
-            right = self.parse_div()
-            left = nodes.Mul(left, right, lineno=lineno)
-            lineno = self.stream.current.lineno
-        return left
-
-    def parse_div(self):
-        lineno = self.stream.current.lineno
-        left = self.parse_floordiv()
-        while self.stream.current.type == 'div':
-            next(self.stream)
-            right = self.parse_floordiv()
-            left = nodes.Div(left, right, lineno=lineno)
-            lineno = self.stream.current.lineno
-        return left
-
-    def parse_floordiv(self):
-        lineno = self.stream.current.lineno
-        left = self.parse_mod()
-        while self.stream.current.type == 'floordiv':
-            next(self.stream)
-            right = self.parse_mod()
-            left = nodes.FloorDiv(left, right, lineno=lineno)
-            lineno = self.stream.current.lineno
-        return left
-
-    def parse_mod(self):
+    def parse_math2(self):
         lineno = self.stream.current.lineno
         left = self.parse_pow()
-        while self.stream.current.type == 'mod':
+        while self.stream.current.type in ('mul', 'div', 'floordiv', 'mod'):
+            cls = _math_nodes[self.stream.current.type]
             next(self.stream)
             right = self.parse_pow()
-            left = nodes.Mod(left, right, lineno=lineno)
+            left = cls(left, right, lineno=lineno)
             lineno = self.stream.current.lineno
         return left
 
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 3cc7aaa..876d69f 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -280,25 +280,17 @@
         return rv
 
 
-class LoopContext(object):
+class LoopContextBase(object):
     """A loop context for dynamic iteration."""
 
-    def __init__(self, iterable, recurse=None, depth0=0):
-        self._iterator = iter(iterable)
+    _after = _last_iteration
+    _length = None
+
+    def __init__(self, recurse=None, depth0=0):
         self._recurse = recurse
-        self._after = self._safe_next()
         self.index0 = -1
         self.depth0 = depth0
 
-        # try to get the length of the iterable early.  This must be done
-        # here because there are some broken iterators around where there
-        # __len__ is the number of iterations left (i'm looking at your
-        # listreverseiterator!).
-        try:
-            self._length = len(iterable)
-        except (TypeError, AttributeError):
-            self._length = None
-
     def cycle(self, *args):
         """Cycles among the arguments with the current loop index."""
         if not args:
@@ -315,15 +307,6 @@
     def __len__(self):
         return self.length
 
-    def __iter__(self):
-        return LoopContextIterator(self)
-
-    def _safe_next(self):
-        try:
-            return next(self._iterator)
-        except StopIteration:
-            return _last_iteration
-
     @internalcode
     def loop(self, iterable):
         if self._recurse is None:
@@ -336,6 +319,30 @@
     __call__ = loop
     del loop
 
+    def __repr__(self):
+        return '<%s %r/%r>' % (
+            self.__class__.__name__,
+            self.index,
+            self.length
+        )
+
+
+class LoopContext(LoopContextBase):
+
+    def __init__(self, iterable, recurse=None, depth0=0):
+        LoopContextBase.__init__(self, recurse, depth0)
+        self._iterator = iter(iterable)
+
+        # try to get the length of the iterable early.  This must be done
+        # here because there are some broken iterators around where there
+        # __len__ is the number of iterations left (i'm looking at your
+        # listreverseiterator!).
+        try:
+            self._length = len(iterable)
+        except (TypeError, AttributeError):
+            self._length = None
+        self._after = self._safe_next()
+
     @property
     def length(self):
         if self._length is None:
@@ -349,12 +356,14 @@
             self._length = len(iterable) + iterations_done
         return self._length
 
-    def __repr__(self):
-        return '<%s %r/%r>' % (
-            self.__class__.__name__,
-            self.index,
-            self.length
-        )
+    def __iter__(self):
+        return LoopContextIterator(self)
+
+    def _safe_next(self):
+        try:
+            return next(self._iterator)
+        except StopIteration:
+            return _last_iteration
 
 
 @implements_iterator
diff --git a/jinja2/sandbox.py b/jinja2/sandbox.py
index 8b491e7..549c427 100644
--- a/jinja2/sandbox.py
+++ b/jinja2/sandbox.py
@@ -14,9 +14,17 @@
 """
 import types
 import operator
+from collections import Mapping
 from jinja2.environment import Environment
 from jinja2.exceptions import SecurityError
-from jinja2._compat import string_types, PY2
+from jinja2._compat import string_types, text_type, PY2
+from jinja2.utils import Markup
+
+has_format = False
+if hasattr(text_type, 'format'):
+    from markupsafe import EscapeFormatter
+    from string import Formatter
+    has_format = True
 
 
 #: maximum number of items a range may produce
@@ -38,6 +46,12 @@
 #: unsafe generator attirbutes.
 UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code'])
 
+#: unsafe attributes on coroutines
+UNSAFE_COROUTINE_ATTRIBUTES = set(['cr_frame', 'cr_code'])
+
+#: unsafe attributes on async generators
+UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = set(['ag_code', 'ag_frame'])
+
 import warnings
 
 # make sure we don't warn in python 2.6 about stuff we don't care about
@@ -94,6 +108,49 @@
 )
 
 
+class _MagicFormatMapping(Mapping):
+    """This class implements a dummy wrapper to fix a bug in the Python
+    standard library for string formatting.
+
+    See http://bugs.python.org/issue13598 for information about why
+    this is necessary.
+    """
+
+    def __init__(self, args, kwargs):
+        self._args = args
+        self._kwargs = kwargs
+        self._last_index = 0
+
+    def __getitem__(self, key):
+        if key == '':
+            idx = self._last_index
+            self._last_index += 1
+            try:
+                return self._args[idx]
+            except LookupError:
+                pass
+            key = str(idx)
+        return self._kwargs[key]
+
+    def __iter__(self):
+        return iter(self._kwargs)
+
+    def __len__(self):
+        return len(self._kwargs)
+
+
+def inspect_format_method(callable):
+    if not has_format:
+        return None
+    if not isinstance(callable, (types.MethodType,
+                                 types.BuiltinMethodType)) or \
+       callable.__name__ != 'format':
+        return None
+    obj = callable.__self__
+    if isinstance(obj, string_types):
+        return obj
+
+
 def safe_range(*args):
     """A range that can't generate ranges with a length of more than
     MAX_RANGE items.
@@ -145,6 +202,12 @@
     elif isinstance(obj, types.GeneratorType):
         if attr in UNSAFE_GENERATOR_ATTRIBUTES:
             return True
+    elif hasattr(types, 'CoroutineType') and isinstance(obj, types.CoroutineType):
+        if attr in UNSAFE_COROUTINE_ATTRIBUTES:
+            return True
+    elif hasattr(types, 'AsyncGeneratorType') and isinstance(obj, types.AsyncGeneratorType):
+        if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
+            return True
     return attr.startswith('__')
 
 
@@ -346,8 +409,24 @@
             obj.__class__.__name__
         ), name=attribute, obj=obj, exc=SecurityError)
 
+    def format_string(self, s, args, kwargs):
+        """If a format call is detected, then this is routed through this
+        method so that our safety sandbox can be used for it.
+        """
+        if isinstance(s, Markup):
+            formatter = SandboxedEscapeFormatter(self, s.escape)
+        else:
+            formatter = SandboxedFormatter(self)
+        kwargs = _MagicFormatMapping(args, kwargs)
+        rv = formatter.vformat(s, args, kwargs)
+        return type(s)(rv)
+
     def call(__self, __context, __obj, *args, **kwargs):
         """Call an object from sandboxed code."""
+        fmt = inspect_format_method(__obj)
+        if fmt is not None:
+            return __self.format_string(fmt, args, kwargs)
+
         # the double prefixes are to avoid double keyword argument
         # errors when proxying the call.
         if not __self.is_safe_callable(__obj):
@@ -365,3 +444,37 @@
         if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
             return False
         return not modifies_known_mutable(obj, attr)
+
+
+if has_format:
+    # This really is not a public API apparenlty.
+    try:
+        from _string import formatter_field_name_split
+    except ImportError:
+        def formatter_field_name_split(field_name):
+            return field_name._formatter_field_name_split()
+
+    class SandboxedFormatterMixin(object):
+
+        def __init__(self, env):
+            self._env = env
+
+        def get_field(self, field_name, args, kwargs):
+            first, rest = formatter_field_name_split(field_name)
+            obj = self.get_value(first, args, kwargs)
+            for is_attr, i in rest:
+                if is_attr:
+                    obj = self._env.getattr(obj, i)
+                else:
+                    obj = self._env.getitem(obj, i)
+            return obj, first
+
+    class SandboxedFormatter(SandboxedFormatterMixin, Formatter):
+        def __init__(self, env):
+            SandboxedFormatterMixin.__init__(self, env)
+            Formatter.__init__(self)
+
+    class SandboxedEscapeFormatter(SandboxedFormatterMixin, EscapeFormatter):
+        def __init__(self, env, escape):
+            SandboxedFormatterMixin.__init__(self, env)
+            EscapeFormatter.__init__(self, escape)
diff --git a/jinja2/utils.py b/jinja2/utils.py
index 612d5c3..96b1352 100644
--- a/jinja2/utils.py
+++ b/jinja2/utils.py
@@ -109,7 +109,7 @@
     """Jinja2 keeps internal caches for environments and lexers.  These are
     used so that Jinja2 doesn't have to recreate environments and lexers all
     the time.  Normally you don't have to care about that but if you are
-    messuring memory consumption you may want to clean the caches.
+    measuring memory consumption you may want to clean the caches.
     """
     from jinja2.environment import _spontaneous_environments
     from jinja2.lexer import _lexer_cache
@@ -183,7 +183,7 @@
         return pformat(obj)
 
 
-def urlize(text, trim_url_limit=None, nofollow=False, target=None):
+def urlize(text, trim_url_limit=None, rel=None, target=None):
     """Converts any URLs in text into clickable links. Works on http://,
     https:// and www. links. Links can have trailing punctuation (periods,
     commas, close-parens) and leading punctuation (opening parens) and
@@ -201,11 +201,9 @@
                          and (x[:limit] + (len(x) >=limit and '...'
                          or '')) or x
     words = _word_split_re.split(text_type(escape(text)))
-    nofollow_attr = nofollow and ' rel="nofollow"' or ''
-    if target is not None and isinstance(target, string_types):
-        target_attr = ' target="%s"' % target
-    else:
-        target_attr = ''
+    rel_attr = rel and ' rel="%s"' % text_type(escape(rel)) or ''
+    target_attr = target and ' target="%s"' % escape(target) or ''
+
     for i, word in enumerate(words):
         match = _punctuation_re.match(word)
         if match:
@@ -221,11 +219,11 @@
                     middle.endswith('.com')
                 )):
                 middle = '<a href="http://%s"%s%s>%s</a>' % (middle,
-                    nofollow_attr, target_attr, trim_url(middle))
+                    rel_attr, target_attr, trim_url(middle))
             if middle.startswith('http://') or \
                middle.startswith('https://'):
                 middle = '<a href="%s"%s%s>%s</a>' % (middle,
-                    nofollow_attr, target_attr, trim_url(middle))
+                    rel_attr, target_attr, trim_url(middle))
             if '@' in middle and not middle.startswith('www.') and \
                not ':' in middle and _simple_email_re.match(middle):
                 middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
@@ -506,12 +504,14 @@
         """Returns the current item."""
         return self.items[self.pos]
 
-    def __next__(self):
+    def next(self):
         """Goes one item ahead and returns it."""
         rv = self.current
         self.pos = (self.pos + 1) % len(self.items)
         return rv
 
+    __next__ = next
+
 
 class Joiner(object):
     """A joining helper for templates."""
@@ -527,5 +527,13 @@
         return self.sep
 
 
+# does this python version support async for in and async generators?
+try:
+    exec('async def _():\n async for _ in ():\n  yield _')
+    have_async_gen = True
+except SyntaxError:
+    have_async_gen = False
+
+
 # Imported here because that's where it was in the past
 from markupsafe import Markup, escape, soft_unicode
diff --git a/setup.cfg b/setup.cfg
index 058cdfc..a24252c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,8 +1,11 @@
 [wheel]
 universal = 1
 
+[metadata]
+license_file = LICENSE
+
 [aliases]
 release = egg_info -RDb ''
 
-[pytest]
-norecursedirs = .* *.egg *.egg-info env* artwork docs examples
+[tool:pytest]
+norecursedirs = .* *.egg *.egg-info env* artwork docs examples venv*
diff --git a/setup.py b/setup.py
index 507fd07..c87ab8a 100644
--- a/setup.py
+++ b/setup.py
@@ -62,6 +62,8 @@
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
         'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: 3.5',
         'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
         'Topic :: Software Development :: Libraries :: Python Modules',
         'Topic :: Text Processing :: Markup :: HTML'
diff --git a/tests/conftest.py b/tests/conftest.py
index b558232..eaae2b0 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -16,9 +16,16 @@
 from traceback import format_exception
 from jinja2 import loaders
 from jinja2._compat import PY2
+from jinja2.utils import have_async_gen
 from jinja2 import Environment
 
 
+def pytest_ignore_collect(path, config):
+    if 'async' in path.basename and not have_async_gen:
+        return True
+    return False
+
+
 @pytest.fixture
 def env():
     '''returns a new environment.
diff --git a/tests/test_api.py b/tests/test_api.py
index 5402698..f2279ea 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -59,6 +59,17 @@
         c.reset()
         assert c.current == 1
 
+    def test_cycler_nextmethod(self, env):
+        items = 1, 2, 3
+        c = Cycler(*items)
+        for item in items + items:
+            assert c.current == item
+            assert c.next() == item
+        c.next()
+        assert c.current == 2
+        c.reset()
+        assert c.current == 1        
+
     def test_expressions(self, env):
         expr = env.compile_expression("foo")
         assert expr() is None
diff --git a/tests/test_async.py b/tests/test_async.py
new file mode 100644
index 0000000..5e7e198
--- /dev/null
+++ b/tests/test_async.py
@@ -0,0 +1,418 @@
+import pytest
+import asyncio
+
+from jinja2 import Template, Environment, DictLoader
+from jinja2.utils import have_async_gen
+from jinja2.exceptions import TemplateNotFound, TemplatesNotFound, \
+     UndefinedError
+
+
+def run(coro):
+    loop = asyncio.get_event_loop()
+    return loop.run_until_complete(coro)
+
+
+def test_basic_async():
+    t = Template('{% for item in [1, 2, 3] %}[{{ item }}]{% endfor %}',
+                 enable_async=True)
+    async def func():
+        return await t.render_async()
+
+    rv = run(func())
+    assert rv == '[1][2][3]'
+
+
+def test_await_on_calls():
+    t = Template('{{ async_func() + normal_func() }}',
+                 enable_async=True)
+
+    async def async_func():
+        return 42
+
+    def normal_func():
+        return 23
+
+    async def func():
+        return await t.render_async(
+            async_func=async_func,
+            normal_func=normal_func
+        )
+
+    rv = run(func())
+    assert rv == '65'
+
+
+def test_await_on_calls_normal_render():
+    t = Template('{{ async_func() + normal_func() }}',
+                 enable_async=True)
+
+    async def async_func():
+        return 42
+
+    def normal_func():
+        return 23
+
+    rv = t.render(
+        async_func=async_func,
+        normal_func=normal_func
+    )
+
+    assert rv == '65'
+
+
+def test_await_and_macros():
+    t = Template('{% macro foo(x) %}[{{ x }}][{{ async_func() }}]'
+                 '{% endmacro %}{{ foo(42) }}', enable_async=True)
+
+    async def async_func():
+        return 42
+
+    async def func():
+        return await t.render_async(async_func=async_func)
+
+    rv = run(func())
+    assert rv == '[42][42]'
+
+
+def test_async_blocks():
+    t = Template('{% block foo %}<Test>{% endblock %}{{ self.foo() }}',
+                 enable_async=True, autoescape=True)
+    async def func():
+        return await t.render_async()
+
+    rv = run(func())
+    assert rv == '<Test><Test>'
+
+
+def test_async_generate():
+    t = Template('{% for x in [1, 2, 3] %}{{ x }}{% endfor %}',
+                 enable_async=True)
+    rv = list(t.generate())
+    assert rv == ['1', '2', '3']
+
+
+def test_async_iteration_in_templates():
+    t = Template('{% for x in rng %}{{ x }}{% endfor %}',
+                 enable_async=True)
+    async def async_iterator():
+        for item in [1, 2, 3]:
+            yield item
+    rv = list(t.generate(rng=async_iterator()))
+    assert rv == ['1', '2', '3']
+
+
+def test_async_iteration_in_templates_extended():
+    t = Template('{% for x in rng %}{{ loop.index0 }}/{{ x }}{% endfor %}',
+                 enable_async=True)
+    async def async_iterator():
+        for item in [1, 2, 3]:
+            yield item
+    rv = list(t.generate(rng=async_iterator()))
+    assert rv == ['0/1', '1/2', '2/3']
+
+
+@pytest.fixture
+def test_env_async():
+    env = Environment(loader=DictLoader(dict(
+        module='{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}',
+        header='[{{ foo }}|{{ 23 }}]',
+        o_printer='({{ o }})'
+    )), enable_async=True)
+    env.globals['bar'] = 23
+    return env
+
+
+@pytest.mark.imports
+class TestAsyncImports(object):
+
+    def test_context_imports(self, test_env_async):
+        t = test_env_async.from_string('{% import "module" as m %}{{ m.test() }}')
+        assert t.render(foo=42) == '[|23]'
+        t = test_env_async.from_string(
+            '{% import "module" as m without context %}{{ m.test() }}'
+        )
+        assert t.render(foo=42) == '[|23]'
+        t = test_env_async.from_string(
+            '{% import "module" as m with context %}{{ m.test() }}'
+        )
+        assert t.render(foo=42) == '[42|23]'
+        t = test_env_async.from_string('{% from "module" import test %}{{ test() }}')
+        assert t.render(foo=42) == '[|23]'
+        t = test_env_async.from_string(
+            '{% from "module" import test without context %}{{ test() }}'
+        )
+        assert t.render(foo=42) == '[|23]'
+        t = test_env_async.from_string(
+            '{% from "module" import test with context %}{{ test() }}'
+        )
+        assert t.render(foo=42) == '[42|23]'
+
+    def test_trailing_comma(self, test_env_async):
+        test_env_async.from_string('{% from "foo" import bar, baz with context %}')
+        test_env_async.from_string('{% from "foo" import bar, baz, with context %}')
+        test_env_async.from_string('{% from "foo" import bar, with context %}')
+        test_env_async.from_string('{% from "foo" import bar, with, context %}')
+        test_env_async.from_string('{% from "foo" import bar, with with context %}')
+
+    def test_exports(self, test_env_async):
+        m = run(test_env_async.from_string('''
+            {% macro toplevel() %}...{% endmacro %}
+            {% macro __private() %}...{% endmacro %}
+            {% set variable = 42 %}
+            {% for item in [1] %}
+                {% macro notthere() %}{% endmacro %}
+            {% endfor %}
+        ''')._get_default_module_async())
+        assert run(m.toplevel()) == '...'
+        assert not hasattr(m, '__missing')
+        assert m.variable == 42
+        assert not hasattr(m, 'notthere')
+
+
+@pytest.mark.imports
+@pytest.mark.includes
+class TestAsyncIncludes(object):
+
+    def test_context_include(self, test_env_async):
+        t = test_env_async.from_string('{% include "header" %}')
+        assert t.render(foo=42) == '[42|23]'
+        t = test_env_async.from_string('{% include "header" with context %}')
+        assert t.render(foo=42) == '[42|23]'
+        t = test_env_async.from_string('{% include "header" without context %}')
+        assert t.render(foo=42) == '[|23]'
+
+    def test_choice_includes(self, test_env_async):
+        t = test_env_async.from_string('{% include ["missing", "header"] %}')
+        assert t.render(foo=42) == '[42|23]'
+
+        t = test_env_async.from_string(
+            '{% include ["missing", "missing2"] ignore missing %}'
+        )
+        assert t.render(foo=42) == ''
+
+        t = test_env_async.from_string('{% include ["missing", "missing2"] %}')
+        pytest.raises(TemplateNotFound, t.render)
+        try:
+            t.render()
+        except TemplatesNotFound as e:
+            assert e.templates == ['missing', 'missing2']
+            assert e.name == 'missing2'
+        else:
+            assert False, 'thou shalt raise'
+
+        def test_includes(t, **ctx):
+            ctx['foo'] = 42
+            assert t.render(ctx) == '[42|23]'
+
+        t = test_env_async.from_string('{% include ["missing", "header"] %}')
+        test_includes(t)
+        t = test_env_async.from_string('{% include x %}')
+        test_includes(t, x=['missing', 'header'])
+        t = test_env_async.from_string('{% include [x, "header"] %}')
+        test_includes(t, x='missing')
+        t = test_env_async.from_string('{% include x %}')
+        test_includes(t, x='header')
+        t = test_env_async.from_string('{% include x %}')
+        test_includes(t, x='header')
+        t = test_env_async.from_string('{% include [x] %}')
+        test_includes(t, x='header')
+
+    def test_include_ignoring_missing(self, test_env_async):
+        t = test_env_async.from_string('{% include "missing" %}')
+        pytest.raises(TemplateNotFound, t.render)
+        for extra in '', 'with context', 'without context':
+            t = test_env_async.from_string('{% include "missing" ignore missing ' +
+                                     extra + ' %}')
+            assert t.render() == ''
+
+    def test_context_include_with_overrides(self, test_env_async):
+        env = Environment(loader=DictLoader(dict(
+            main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}",
+            item="{{ item }}"
+        )))
+        assert env.get_template("main").render() == "123"
+
+    def test_unoptimized_scopes(self, test_env_async):
+        t = test_env_async.from_string("""
+            {% macro outer(o) %}
+            {% macro inner() %}
+            {% include "o_printer" %}
+            {% endmacro %}
+            {{ inner() }}
+            {% endmacro %}
+            {{ outer("FOO") }}
+        """)
+        assert t.render().strip() == '(FOO)'
+
+
+@pytest.mark.core_tags
+@pytest.mark.for_loop
+class TestAsyncForLoop(object):
+
+    def test_simple(self, test_env_async):
+        tmpl = test_env_async.from_string('{% for item in seq %}{{ item }}{% endfor %}')
+        assert tmpl.render(seq=list(range(10))) == '0123456789'
+
+    def test_else(self, test_env_async):
+        tmpl = test_env_async.from_string(
+            '{% for item in seq %}XXX{% else %}...{% endfor %}')
+        assert tmpl.render() == '...'
+
+    def test_empty_blocks(self, test_env_async):
+        tmpl = test_env_async.from_string('<{% for item in seq %}{% else %}{% endfor %}>')
+        assert tmpl.render() == '<>'
+
+    def test_context_vars(self, test_env_async):
+        slist = [42, 24]
+        for seq in [slist, iter(slist), reversed(slist), (_ for _ in slist)]:
+            tmpl = test_env_async.from_string('''{% for item in seq -%}
+            {{ loop.index }}|{{ loop.index0 }}|{{ loop.revindex }}|{{
+                loop.revindex0 }}|{{ loop.first }}|{{ loop.last }}|{{
+               loop.length }}###{% endfor %}''')
+            one, two, _ = tmpl.render(seq=seq).split('###')
+            (one_index, one_index0, one_revindex, one_revindex0, one_first,
+             one_last, one_length) = one.split('|')
+            (two_index, two_index0, two_revindex, two_revindex0, two_first,
+             two_last, two_length) = two.split('|')
+
+            assert int(one_index) == 1 and int(two_index) == 2
+            assert int(one_index0) == 0 and int(two_index0) == 1
+            assert int(one_revindex) == 2 and int(two_revindex) == 1
+            assert int(one_revindex0) == 1 and int(two_revindex0) == 0
+            assert one_first == 'True' and two_first == 'False'
+            assert one_last == 'False' and two_last == 'True'
+            assert one_length == two_length == '2'
+
+    def test_cycling(self, test_env_async):
+        tmpl = test_env_async.from_string('''{% for item in seq %}{{
+            loop.cycle('<1>', '<2>') }}{% endfor %}{%
+            for item in seq %}{{ loop.cycle(*through) }}{% endfor %}''')
+        output = tmpl.render(seq=list(range(4)), through=('<1>', '<2>'))
+        assert output == '<1><2>' * 4
+
+    def test_scope(self, test_env_async):
+        tmpl = test_env_async.from_string('{% for item in seq %}{% endfor %}{{ item }}')
+        output = tmpl.render(seq=list(range(10)))
+        assert not output
+
+    def test_varlen(self, test_env_async):
+        def inner():
+            for item in range(5):
+                yield item
+        tmpl = test_env_async.from_string('{% for item in iter %}{{ item }}{% endfor %}')
+        output = tmpl.render(iter=inner())
+        assert output == '01234'
+
+    def test_noniter(self, test_env_async):
+        tmpl = test_env_async.from_string('{% for item in none %}...{% endfor %}')
+        pytest.raises(TypeError, tmpl.render)
+
+    def test_recursive(self, test_env_async):
+        tmpl = test_env_async.from_string('''{% for item in seq recursive -%}
+            [{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
+        {%- endfor %}''')
+        assert tmpl.render(seq=[
+            dict(a=1, b=[dict(a=1), dict(a=2)]),
+            dict(a=2, b=[dict(a=1), dict(a=2)]),
+            dict(a=3, b=[dict(a='a')])
+        ]) == '[1<[1][2]>][2<[1][2]>][3<[a]>]'
+
+    def test_recursive_depth0(self, test_env_async):
+        tmpl = test_env_async.from_string('''{% for item in seq recursive -%}
+            [{{ loop.depth0 }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
+        {%- endfor %}''')
+        assert tmpl.render(seq=[
+            dict(a=1, b=[dict(a=1), dict(a=2)]),
+            dict(a=2, b=[dict(a=1), dict(a=2)]),
+            dict(a=3, b=[dict(a='a')])
+        ]) == '[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]'
+
+    def test_recursive_depth(self, test_env_async):
+        tmpl = test_env_async.from_string('''{% for item in seq recursive -%}
+            [{{ loop.depth }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
+        {%- endfor %}''')
+        assert tmpl.render(seq=[
+            dict(a=1, b=[dict(a=1), dict(a=2)]),
+            dict(a=2, b=[dict(a=1), dict(a=2)]),
+            dict(a=3, b=[dict(a='a')])
+        ]) == '[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]'
+
+    def test_looploop(self, test_env_async):
+        tmpl = test_env_async.from_string('''{% for row in table %}
+            {%- set rowloop = loop -%}
+            {% for cell in row -%}
+                [{{ rowloop.index }}|{{ loop.index }}]
+            {%- endfor %}
+        {%- endfor %}''')
+        assert tmpl.render(table=['ab', 'cd']) == '[1|1][1|2][2|1][2|2]'
+
+    def test_reversed_bug(self, test_env_async):
+        tmpl = test_env_async.from_string('{% for i in items %}{{ i }}'
+                               '{% if not loop.last %}'
+                               ',{% endif %}{% endfor %}')
+        assert tmpl.render(items=reversed([3, 2, 1])) == '1,2,3'
+
+    def test_loop_errors(self, test_env_async):
+        tmpl = test_env_async.from_string('''{% for item in [1] if loop.index
+                                      == 0 %}...{% endfor %}''')
+        pytest.raises(UndefinedError, tmpl.render)
+        tmpl = test_env_async.from_string('''{% for item in [] %}...{% else
+            %}{{ loop }}{% endfor %}''')
+        assert tmpl.render() == ''
+
+    def test_loop_filter(self, test_env_async):
+        tmpl = test_env_async.from_string('{% for item in range(10) if item '
+                               'is even %}[{{ item }}]{% endfor %}')
+        assert tmpl.render() == '[0][2][4][6][8]'
+        tmpl = test_env_async.from_string('''
+            {%- for item in range(10) if item is even %}[{{
+                loop.index }}:{{ item }}]{% endfor %}''')
+        assert tmpl.render() == '[1:0][2:2][3:4][4:6][5:8]'
+
+    def test_scoped_special_var(self, test_env_async):
+        t = test_env_async.from_string(
+            '{% for s in seq %}[{{ loop.first }}{% for c in s %}'
+            '|{{ loop.first }}{% endfor %}]{% endfor %}')
+        assert t.render(seq=('ab', 'cd')) \
+            == '[True|True|False][False|True|False]'
+
+    def test_scoped_loop_var(self, test_env_async):
+        t = test_env_async.from_string('{% for x in seq %}{{ loop.first }}'
+                            '{% for y in seq %}{% endfor %}{% endfor %}')
+        assert t.render(seq='ab') == 'TrueFalse'
+        t = test_env_async.from_string('{% for x in seq %}{% for y in seq %}'
+                            '{{ loop.first }}{% endfor %}{% endfor %}')
+        assert t.render(seq='ab') == 'TrueFalseTrueFalse'
+
+    def test_recursive_empty_loop_iter(self, test_env_async):
+        t = test_env_async.from_string('''
+        {%- for item in foo recursive -%}{%- endfor -%}
+        ''')
+        assert t.render(dict(foo=[])) == ''
+
+    def test_call_in_loop(self, test_env_async):
+        t = test_env_async.from_string('''
+        {%- macro do_something() -%}
+            [{{ caller() }}]
+        {%- endmacro %}
+
+        {%- for i in [1, 2, 3] %}
+            {%- call do_something() -%}
+                {{ i }}
+            {%- endcall %}
+        {%- endfor -%}
+        ''')
+        assert t.render() == '[1][2][3]'
+
+    def test_scoping_bug(self, test_env_async):
+        t = test_env_async.from_string('''
+        {%- for item in foo %}...{{ item }}...{% endfor %}
+        {%- macro item(a) %}...{{ a }}...{% endmacro %}
+        {{- item(2) -}}
+        ''')
+        assert t.render(foo=(1,)) == '...1......2...'
+
+    def test_unpacking(self, test_env_async):
+        tmpl = test_env_async.from_string('{% for a, b, c in [[1, 2, 3]] %}'
+                               '{{ a }}|{{ b }}|{{ c }}{% endfor %}')
+        assert tmpl.render() == '1|2|3'
diff --git a/tests/test_asyncfilters.py b/tests/test_asyncfilters.py
new file mode 100644
index 0000000..162cc6d
--- /dev/null
+++ b/tests/test_asyncfilters.py
@@ -0,0 +1,227 @@
+import pytest
+from jinja2 import Environment
+from jinja2.utils import Markup
+
+
+async def make_aiter(iter):
+    for item in iter:
+        yield item
+
+
+def mark_dualiter(parameter, factory):
+    def decorator(f):
+        return pytest.mark.parametrize(parameter, [
+            lambda: factory(),
+            lambda: make_aiter(factory()),
+        ])(f)
+    return decorator
+
+
+@pytest.fixture
+def env_async():
+    return Environment(enable_async=True)
+
+
+@mark_dualiter('foo', lambda: range(10))
+def test_first(env_async, foo):
+    tmpl = env_async.from_string('{{ foo()|first }}')
+    out = tmpl.render(foo=foo)
+    assert out == '0'
+
+
+@mark_dualiter('items', lambda: [
+    {'foo': 1, 'bar': 2},
+    {'foo': 2, 'bar': 3},
+    {'foo': 1, 'bar': 1},
+    {'foo': 3, 'bar': 4}
+])
+def test_groupby(env_async, items):
+    tmpl = env_async.from_string('''
+    {%- for grouper, list in items()|groupby('foo') -%}
+        {{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
+    {%- endfor %}''')
+    assert tmpl.render(items=items).split('|') == [
+        "1: 1, 2: 1, 1",
+        "2: 2, 3",
+        "3: 3, 4",
+        ""
+    ]
+
+
+@mark_dualiter('items', lambda: [('a', 1), ('a', 2), ('b', 1)])
+def test_groupby_tuple_index(env_async, items):
+    tmpl = env_async.from_string('''
+    {%- for grouper, list in items()|groupby(0) -%}
+        {{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
+    {%- endfor %}''')
+    assert tmpl.render(items=items) == 'a:1:2|b:1|'
+
+
+def make_articles():
+    class Date(object):
+        def __init__(self, day, month, year):
+            self.day = day
+            self.month = month
+            self.year = year
+
+    class Article(object):
+        def __init__(self, title, *date):
+            self.date = Date(*date)
+            self.title = title
+
+    return [
+        Article('aha', 1, 1, 1970),
+        Article('interesting', 2, 1, 1970),
+        Article('really?', 3, 1, 1970),
+        Article('totally not', 1, 1, 1971)
+    ]
+
+
+@mark_dualiter('articles', make_articles)
+def test_groupby_multidot(env_async, articles):
+    tmpl = env_async.from_string('''
+    {%- for year, list in articles()|groupby('date.year') -%}
+        {{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
+    {%- endfor %}''')
+    assert tmpl.render(articles=articles).split('|') == [
+        '1970[aha][interesting][really?]',
+        '1971[totally not]',
+        ''
+    ]
+
+
+@mark_dualiter('int_items', lambda: [1, 2, 3])
+def test_join(env_async, int_items):
+    tmpl = env_async.from_string('{{ items()|join("|") }}')
+    out = tmpl.render(items=int_items)
+    assert out == '1|2|3'
+
+
+@mark_dualiter('string_items', lambda: ["<foo>", Markup("<span>foo</span>")])
+def test_join(string_items):
+    env2 = Environment(autoescape=True, enable_async=True)
+    tmpl = env2.from_string(
+        '{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
+    assert tmpl.render(items=string_items) == '&lt;foo&gt;<span>foo</span>'
+
+
+def make_users():
+    class User(object):
+        def __init__(self, username):
+            self.username = username
+    return map(User, ['foo', 'bar'])
+
+
+@mark_dualiter('users', make_users)
+def test_join_attribute(env_async, users):
+    tmpl = env_async.from_string('''{{ users()|join(', ', 'username') }}''')
+    assert tmpl.render(users=users) == 'foo, bar'
+
+
+@mark_dualiter('items', lambda: [1, 2, 3, 4, 5])
+def test_simple_reject(env_async, items):
+    tmpl = env_async.from_string('{{ items()|reject("odd")|join("|") }}')
+    assert tmpl.render(items=items) == '2|4'
+
+
+@mark_dualiter('items', lambda: [None, False, 0, 1, 2, 3, 4, 5])
+def test_bool_reject(env_async, items):
+    tmpl = env_async.from_string(
+        '{{ items()|reject|join("|") }}'
+    )
+    assert tmpl.render(items=items) == 'None|False|0'
+
+
+@mark_dualiter('items', lambda: [1, 2, 3, 4, 5])
+def test_simple_select(env_async, items):
+    tmpl = env_async.from_string('{{ items()|select("odd")|join("|") }}')
+    assert tmpl.render(items=items) == '1|3|5'
+
+
+@mark_dualiter('items', lambda: [None, False, 0, 1, 2, 3, 4, 5])
+def test_bool_select(env_async, items):
+    tmpl = env_async.from_string(
+        '{{ items()|select|join("|") }}'
+    )
+    assert tmpl.render(items=items) == '1|2|3|4|5'
+
+
+def make_users():
+    class User(object):
+        def __init__(self, name, is_active):
+            self.name = name
+            self.is_active = is_active
+    return [
+        User('john', True),
+        User('jane', True),
+        User('mike', False),
+    ]
+
+
+@mark_dualiter('users', make_users)
+def test_simple_select_attr(env_async, users):
+    tmpl = env_async.from_string(
+        '{{ users()|selectattr("is_active")|'
+        'map(attribute="name")|join("|") }}'
+    )
+    assert tmpl.render(users=users) == 'john|jane'
+
+
+@mark_dualiter('items', lambda: list('123'))
+def test_simple_map(env_async, items):
+    tmpl = env_async.from_string('{{ items()|map("int")|sum }}')
+    assert tmpl.render(items=items) == '6'
+
+
+@mark_dualiter('users', make_users)
+def test_attribute_map(env_async, users):
+    tmpl = env_async.from_string('{{ users()|map(attribute="name")|join("|") }}')
+    assert tmpl.render(users=users) == 'john|jane|mike'
+
+
+def test_empty_map(env_async):
+    tmpl = env_async.from_string('{{ none|map("upper")|list }}')
+    assert tmpl.render() == '[]'
+
+
+@mark_dualiter('items', lambda: [1, 2, 3, 4, 5, 6])
+def test_sum(env_async, items):
+    tmpl = env_async.from_string('''{{ items()|sum }}''')
+    assert tmpl.render(items=items) == '21'
+
+
+@mark_dualiter('items', lambda: [
+    {'value': 23},
+    {'value': 1},
+    {'value': 18},
+])
+def test_sum_attributes(env_async, items):
+    tmpl = env_async.from_string('''{{ items()|sum('value') }}''')
+    assert tmpl.render(items=items)
+
+
+def test_sum_attributes_nested(env_async):
+    tmpl = env_async.from_string('''{{ values|sum('real.value') }}''')
+    assert tmpl.render(values=[
+        {'real': {'value': 23}},
+        {'real': {'value': 1}},
+        {'real': {'value': 18}},
+    ]) == '42'
+
+
+def test_sum_attributes_tuple(env_async):
+    tmpl = env_async.from_string('''{{ values.items()|sum('1') }}''')
+    assert tmpl.render(values={
+        'foo': 23,
+        'bar': 1,
+        'baz': 18,
+    }) == '42'
+
+
+@mark_dualiter('items', lambda: range(10))
+def test_slice(env_async, items):
+    tmpl = env_async.from_string('{{ items()|slice(3)|list }}|'
+                                 '{{ items()|slice(3, "X")|list }}')
+    out = tmpl.render(items=items)
+    assert out == ("[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
+                   "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]")
diff --git a/tests/test_ext.py b/tests/test_ext.py
index 8985416..f49c09c 100644
--- a/tests/test_ext.py
+++ b/tests/test_ext.py
@@ -99,7 +99,7 @@
 newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
 
 
-class TestExtension(Extension):
+class ExampleExtension(Extension):
     tags = set(['test'])
     ext_attr = 42
 
@@ -207,15 +207,15 @@
             == ['42 = 23', '1 = 2']
 
     def test_extension_nodes(self):
-        env = Environment(extensions=[TestExtension])
+        env = Environment(extensions=[ExampleExtension])
         tmpl = env.from_string('{% test %}')
         assert tmpl.render() == 'False|42|23|{}'
 
     def test_identifier(self):
-        assert TestExtension.identifier == __name__ + '.TestExtension'
+        assert ExampleExtension.identifier == __name__ + '.ExampleExtension'
 
     def test_rebinding(self):
-        original = Environment(extensions=[TestExtension])
+        original = Environment(extensions=[ExampleExtension])
         overlay = original.overlay()
         for env in original, overlay:
             for ext in itervalues(env.extensions):
diff --git a/tests/test_features.py b/tests/test_features.py
new file mode 100644
index 0000000..25d58e4
--- /dev/null
+++ b/tests/test_features.py
@@ -0,0 +1,16 @@
+import sys
+import pytest
+
+from jinja2 import Template
+
+
+@pytest.mark.skipif(sys.version_info < (3, 5),
+                    reason='Requires 3.5 or later')
+def test_generator_stop():
+    class X(object):
+        def __getattr__(self, name):
+            raise StopIteration()
+
+    t = Template('a{{ bad.bar() }}b')
+    with pytest.raises(RuntimeError):
+        t.render(bad=X())
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 741ef34..f8a2f19 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -14,7 +14,7 @@
 
 
 @pytest.mark.filter
-class TestFilter():
+class TestFilter(object):
 
     def test_filter_calling(self, env):
         rv = env.call_filter('sum', [1, 2, 3])
@@ -133,11 +133,16 @@
                        'foo bar foo bar\n  foo bar foo bar')
 
     def test_int(self, env):
+        class IntIsh(object):
+            def __int__(self):
+                return 42
+
         tmpl = env.from_string('{{ "42"|int }}|{{ "ajsghasjgd"|int }}|'
                                '{{ "32.32"|int }}|{{ "0x4d32"|int(0, 16) }}|'
-                               '{{ "011"|int(0, 8)}}|{{ "0x33FU"|int(0, 16) }}')
-        out = tmpl.render()
-        assert out == '42|0|32|19762|9|0'
+                               '{{ "011"|int(0, 8)}}|{{ "0x33FU"|int(0, 16) }}|'
+                               '{{ obj|int }}')
+        out = tmpl.render(obj=IntIsh())
+        assert out == '42|0|32|19762|9|0|42'
 
     def test_join(self, env):
         tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}')
@@ -208,6 +213,14 @@
         assert tmpl.render() == "Foo\tBar"
         tmpl = env.from_string('''{{ "FOO\tBAR"|title }}''')
         assert tmpl.render() == "Foo\tBar"
+        tmpl = env.from_string('''{{ "foo (bar)"|title }}''')
+        assert tmpl.render() == "Foo (Bar)"
+        tmpl = env.from_string('''{{ "foo {bar}"|title }}''')
+        assert tmpl.render() == "Foo {Bar}"
+        tmpl = env.from_string('''{{ "foo [bar]"|title }}''')
+        assert tmpl.render() == "Foo [Bar]"
+        tmpl = env.from_string('''{{ "foo <bar>"|title }}''')
+        assert tmpl.render() == "Foo <Bar>"
 
         class Foo:
             def __str__(self):
@@ -248,21 +261,28 @@
     def test_urlize(self, env):
         tmpl = env.from_string(
             '{{ "foo http://www.example.com/ bar"|urlize }}')
-        assert tmpl.render() == 'foo <a href="http://www.example.com/">'\
-                                'http://www.example.com/</a> bar'
+        assert tmpl.render() == (
+            'foo <a href="http://www.example.com/" rel="noopener">'
+            'http://www.example.com/</a> bar'
+        )
+
+    def test_urlize_rel_policy(self):
+        env = Environment()
+        env.policies['urlize.rel'] = None
+        tmpl = env.from_string(
+            '{{ "foo http://www.example.com/ bar"|urlize }}')
+        assert tmpl.render() == (
+            'foo <a href="http://www.example.com/">'
+            'http://www.example.com/</a> bar'
+        )
 
     def test_urlize_target_parameter(self, env):
         tmpl = env.from_string(
             '{{ "foo http://www.example.com/ bar"|urlize(target="_blank") }}'
         )
         assert tmpl.render() \
-            == 'foo <a href="http://www.example.com/" target="_blank">'\
+            == 'foo <a href="http://www.example.com/" rel="noopener" target="_blank">'\
             'http://www.example.com/</a> bar'
-        tmpl = env.from_string(
-            '{{ "foo http://www.example.com/ bar"|urlize(target=42) }}'
-        )
-        assert tmpl.render() == 'foo <a href="http://www.example.com/">'\
-                                'http://www.example.com/</a> bar'
 
     def test_wordcount(self, env):
         tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
diff --git a/tests/test_lexnparse.py b/tests/test_lexnparse.py
index ff334bf..7a49ba2 100644
--- a/tests/test_lexnparse.py
+++ b/tests/test_lexnparse.py
@@ -395,6 +395,10 @@
         tmpl = env.from_string('''{{ not 42 in bar }}''')
         assert tmpl.render(bar=bar) == text_type(not 42 in bar)
 
+    def test_operator_precedence(self, env):
+        tmpl = env.from_string('''{{ 2 * 3 + 4 % 2 + 1 - 2 }}''')
+        assert tmpl.render() == text_type(2 * 3 + 4 % 2 + 1 - 2)
+
     def test_implicit_subscribed_tuple(self, env):
         class Foo(object):
             def __getitem__(self, x):
diff --git a/tests/test_loader.py b/tests/test_loader.py
index 6d22fad..b916b91 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -13,6 +13,7 @@
 import tempfile
 import shutil
 import pytest
+import weakref
 
 from jinja2 import Environment, loaders
 from jinja2._compat import PYPY, PY2
@@ -78,19 +79,32 @@
         assert tmpl is not env.get_template('template')
         changed = False
 
-        env = Environment(loader=TestLoader(), cache_size=0)
-        assert env.get_template('template') \
-            is not env.get_template('template')
+    def test_no_cache(self):
+        mapping = {'foo': 'one'}
+        env = Environment(loader=loaders.DictLoader(mapping), cache_size=0)
+        assert env.get_template('foo') is not env.get_template('foo')
 
-        env = Environment(loader=TestLoader(), cache_size=2)
+    def test_limited_size_cache(self):
+        mapping = {'one': 'foo', 'two': 'bar', 'three': 'baz'}
+        loader = loaders.DictLoader(mapping)
+        env = Environment(loader=loader, cache_size=2)
         t1 = env.get_template('one')
         t2 = env.get_template('two')
         assert t2 is env.get_template('two')
         assert t1 is env.get_template('one')
         t3 = env.get_template('three')
-        assert 'one' in env.cache
-        assert 'two' not in env.cache
-        assert 'three' in env.cache
+        loader_ref = weakref.ref(loader)
+        assert (loader_ref, 'one') in env.cache
+        assert (loader_ref, 'two') not in env.cache
+        assert (loader_ref, 'three') in env.cache
+
+    def test_cache_loader_change(self):
+        loader1 = loaders.DictLoader({'foo': 'one'})
+        loader2 = loaders.DictLoader({'foo': 'two'})
+        env = Environment(loader=loader1, cache_size=2)
+        assert env.get_template('foo').render() == 'one'
+        env.loader = loader2
+        assert env.get_template('foo').render() == 'two'
 
     def test_dict_loader_cache_invalidates(self):
         mapping = {'foo': "one"}
diff --git a/tests/test_regression.py b/tests/test_regression.py
index a4aa157..951e8d4 100644
--- a/tests/test_regression.py
+++ b/tests/test_regression.py
@@ -8,6 +8,7 @@
     :copyright: (c) 2010 by the Jinja Team.
     :license: BSD, see LICENSE for more details.
 """
+import sys
 import pytest
 
 from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
@@ -100,7 +101,7 @@
 
     def test_urlize_filter_escaping(self, env):
         tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}')
-        assert tmpl.render() == '<a href="http://www.example.org/&lt;foo">'\
+        assert tmpl.render() == '<a href="http://www.example.org/&lt;foo" rel="noopener">'\
             'http://www.example.org/&lt;foo</a>'
 
     def test_loop_call_loop(self, env):
@@ -276,3 +277,17 @@
         expected = 'TEST'
 
         assert output == expected
+
+    @pytest.mark.skipif(sys.version_info[0] > 2,
+                        reason='This only works on 2.x')
+    def test_old_style_attribute(self, env):
+        class Foo:
+            x = 42
+        assert env.getitem(Foo(), 'x') == 42
+
+    def test_block_set_with_extends(self):
+        env = Environment(loader=DictLoader({
+            'main': '{% block body %}[{{ x }}]{% endblock %}',
+        }))
+        t = env.from_string('{% extends "main" %}{% set x %}42{% endset %}')
+        assert t.render() == '[42]'
diff --git a/tests/test_security.py b/tests/test_security.py
index e5b463f..c0442ce 100644
--- a/tests/test_security.py
+++ b/tests/test_security.py
@@ -12,7 +12,7 @@
 
 from jinja2 import Environment
 from jinja2.sandbox import SandboxedEnvironment, \
-     ImmutableSandboxedEnvironment, unsafe
+     ImmutableSandboxedEnvironment, unsafe, has_format
 from jinja2 import Markup, escape
 from jinja2.exceptions import SecurityError, TemplateSyntaxError, \
      TemplateRuntimeError
@@ -159,3 +159,28 @@
                 pass
             else:
                 assert False, 'expected runtime error'
+
+
+@pytest.mark.sandbox
+@pytest.mark.skipif(not has_format, reason='No format support')
+class TestStringFormat(object):
+
+    def test_basic_format_safety(self):
+        env = SandboxedEnvironment()
+        t = env.from_string('{{ "a{0.__class__}b".format(42) }}')
+        assert t.render() == 'ab'
+
+    def test_basic_format_all_okay(self):
+        env = SandboxedEnvironment()
+        t = env.from_string('{{ "a{0.foo}b".format({"foo": 42}) }}')
+        assert t.render() == 'a42b'
+
+    def test_safe_format_safety(self):
+        env = SandboxedEnvironment()
+        t = env.from_string('{{ ("a{0.__class__}b{1}"|safe).format(42, "<foo>") }}')
+        assert t.render() == 'ab&lt;foo&gt;'
+
+    def test_safe_format_all_okay(self):
+        env = SandboxedEnvironment()
+        t = env.from_string('{{ ("a{0.foo}b{1}"|safe).format({"foo": 42}, "<foo>") }}')
+        assert t.render() == 'a42b&lt;foo&gt;'
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 3731036..95cf043 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -14,7 +14,7 @@
 
 import pickle
 
-from jinja2.utils import LRUCache, escape, object_type_repr
+from jinja2.utils import LRUCache, escape, object_type_repr, urlize
 
 
 @pytest.mark.utils
@@ -74,3 +74,14 @@
                 escape(u"<foo>")
             counts.add(len(gc.get_objects()))
         assert len(counts) == 1, 'ouch, c extension seems to leak objects'
+
+
+@pytest.mark.utils
+@pytest.mark.escapeUrlizeTarget
+class TestEscapeUrlizeTarget():
+    def test_escape_urlize_target(self):
+        url = "http://example.org"
+        target = "<script>"
+        assert urlize(url, target=target) == ('<a href="http://example.org"'
+                                              ' target="&lt;script&gt;">'
+                                              'http://example.org</a>')
diff --git a/tox.ini b/tox.ini
index 0daabf7..388fb05 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,9 +1,8 @@
 [tox]
-envlist = py26, py27, pypy, py33, py34, py35
+envlist = py26,py27,pypy,py33,py34,py35
 
 [testenv]
-commands =
-  py.test []
+commands = py.test {posargs}
 
 deps =
-  pytest
+    pytest