Browse files

Added project, updated readme.

  • Loading branch information...
1 parent 791bffb commit 3e44df02e660fb7c7f6b6da85609dfdebb3a5b75 Justin Sanders committed Apr 13, 2010
Showing with 19,696 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. 0 README
  3. +90 −0 README.textile
  4. +5 −0 app.cgi
  5. +9 −0 app.yaml
  6. +60 −0 application.py
  7. +637 −0 flask.py
  8. +11 −0 index.yaml
  9. +73 −0 jinja2/__init__.py
  10. +130 −0 jinja2/_stringdefs.py
  11. +280 −0 jinja2/bccache.py
  12. +1,633 −0 jinja2/compiler.py
  13. +290 −0 jinja2/constants.py
  14. +308 −0 jinja2/debug.py
  15. +40 −0 jinja2/defaults.py
  16. +1,095 −0 jinja2/environment.py
  17. +143 −0 jinja2/exceptions.py
  18. +553 −0 jinja2/ext.py
  19. +730 −0 jinja2/filters.py
  20. +680 −0 jinja2/lexer.py
  21. +449 −0 jinja2/loaders.py
  22. +102 −0 jinja2/meta.py
  23. +874 −0 jinja2/nodes.py
  24. +68 −0 jinja2/optimizer.py
  25. +882 −0 jinja2/parser.py
  26. +537 −0 jinja2/runtime.py
  27. +271 −0 jinja2/sandbox.py
  28. +146 −0 jinja2/tests.py
  29. +818 −0 jinja2/utils.py
  30. +87 −0 jinja2/visitor.py
  31. +17 −0 templates/list.html
  32. +157 −0 werkzeug/__init__.py
  33. +398 −0 werkzeug/_internal.py
  34. +16 −0 werkzeug/contrib/__init__.py
  35. +343 −0 werkzeug/contrib/atom.py
  36. +511 −0 werkzeug/contrib/cache.py
  37. +205 −0 werkzeug/contrib/fixers.py
  38. +281 −0 werkzeug/contrib/iterio.py
  39. +258 −0 werkzeug/contrib/jsrouting.py
  40. +284 −0 werkzeug/contrib/kickstart.py
  41. +36 −0 werkzeug/contrib/limiter.py
  42. +331 −0 werkzeug/contrib/lint.py
  43. +116 −0 werkzeug/contrib/profiler.py
  44. +328 −0 werkzeug/contrib/securecookie.py
  45. +342 −0 werkzeug/contrib/sessions.py
  46. +66 −0 werkzeug/contrib/testtools.py
  47. +275 −0 werkzeug/contrib/wrappers.py
  48. +2,331 −0 werkzeug/datastructures.py
  49. +166 −0 werkzeug/debug/__init__.py
  50. +201 −0 werkzeug/debug/console.py
  51. +103 −0 werkzeug/debug/render.py
  52. +238 −0 werkzeug/debug/repr.py
  53. +81 −0 werkzeug/debug/shared/body.tmpl
  54. +8 −0 werkzeug/debug/shared/codetable.tmpl
  55. BIN werkzeug/debug/shared/console.png
  56. +196 −0 werkzeug/debug/shared/debugger.js
  57. +19 −0 werkzeug/debug/shared/jquery.js
  58. BIN werkzeug/debug/shared/less.png
  59. BIN werkzeug/debug/shared/more.png
  60. BIN werkzeug/debug/shared/source.png
  61. +93 −0 werkzeug/debug/shared/style.css
  62. +16 −0 werkzeug/debug/shared/vartable.tmpl
  63. +297 −0 werkzeug/debug/tbtools.py
  64. +28 −0 werkzeug/debug/templates/console.html
  65. +14 −0 werkzeug/debug/templates/dump_object.html
  66. +6 −0 werkzeug/debug/templates/frame.html
  67. +10 −0 werkzeug/debug/templates/help_command.html
  68. +8 −0 werkzeug/debug/templates/source.html
  69. +55 −0 werkzeug/debug/templates/traceback_full.html
  70. +6 −0 werkzeug/debug/templates/traceback_plaintext.html
  71. +23 −0 werkzeug/debug/templates/traceback_summary.html
  72. +20 −0 werkzeug/debug/utils.py
  73. +459 −0 werkzeug/exceptions.py
  74. +352 −0 werkzeug/formparser.py
Sorry, we could not display the entire diff because it was too big.
View
1 .gitignore
@@ -0,0 +1 @@
+.pyc
View
0 README
No changes.
View
90 README.textile
@@ -0,0 +1,90 @@
+"Flask":http://flask.pocoo.org/ is a micro framework for python. I wanted to create a simple example using Flask on "Google App Engine":http://appengine.google.com so I created this todo list app.
+
+You can try it out for yourself at "flasktodo.appspot.com":http://flasktodo.appspot.com. To give you an idea of how simple coding in a micro framework can be I've included all the code below.
+
+application.py
+
+@
+from flask import Flask
+app = Flask(__name__)
+
+from google.appengine.ext import db
+from google.appengine.api import users
+
+from flask import redirect, url_for, request, render_template, abort, flash, get_flashed_messages
+
+class Task(db.Model):
+ user = db.UserProperty()
+ name = db.StringProperty(required=True)
+ done = db.BooleanProperty()
+
+@app.route('/')
+def list():
+ user = users.get_current_user()
+ tasks = Task.all().filter('user =', user)
+ return render_template('list.html', user=user, logout_url=users.create_logout_url("/"), tasks=tasks, flashes=get_flashed_messages());
+
+@app.route('/', methods=['POST'])
+def task_post():
+ name = request.form['name']
+ if not name:
+ flash("Oops you forgot to set a task name.")
+ return redirect(url_for('list'))
+ task = Task(name = request.form['name'])
+ task.user = users.get_current_user()
+ task.put()
+ return redirect(url_for('list'))
+
+@app.route('/delete/<int:id>')
+def task_delete(id):
+ task = Task.get_by_id(id)
+ if task and task.user == users.get_current_user():
+ task.delete()
+ else:
+ abort(404)
+
+ return redirect(url_for('list'))
+
+@app.route('/done/<int:id>')
+def task_done(id):
+ task = Task.get_by_id(id)
+ if task and task.user == users.get_current_user():
+ if task.done:
+ task.done = False
+ else:
+ task.done = True
+ task.put()
+ else:
+ abort(404)
+
+ return redirect(url_for('list'))
+
+
+# set the secret key. keep this really secret:
+app.secret_key = 'the secret key'
+
+if __name__ == '__main__':
+ app.run()
+@
+
+list.html
+
+@
+<!doctype html>
+Welcome, {{ user.nickname() }} <a href="{{ logout_url }}">sign out</a>
+
+<ul>
+{% for task in tasks %}
+ <li><a href="/done/{{ task.key().id() }}">X</a> {% if task.done %}<strike>{% endif %}{{ task.name }}{% if task.done %}</strike>{% endif %} <a href="/delete/{{ task.key().id() }}">delete</a></li>
+{% endfor %}
+</ul>
+
+{% for flash in flashes %}
+ <p>{{ flash }}</p>
+{% endfor %}
+
+<form action="" method="post">
+ <p><input type=text name=name>
+ <input type=submit value=Add>
+</form>
+@
View
5 app.cgi
@@ -0,0 +1,5 @@
+#!/usr/bin/python
+from wsgiref.handlers import CGIHandler
+from application import app
+
+CGIHandler().run(app)
View
9 app.yaml
@@ -0,0 +1,9 @@
+application: flasktodo
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: /.*
+ script: app.cgi
+ login: required
View
60 application.py
@@ -0,0 +1,60 @@
+from flask import Flask
+app = Flask(__name__)
+
+from google.appengine.ext import db
+from google.appengine.api import users
+
+from flask import redirect, url_for, request, render_template, abort, flash, get_flashed_messages
+
+class Task(db.Model):
+ user = db.UserProperty()
+ name = db.StringProperty(required=True)
+ done = db.BooleanProperty()
+
+@app.route('/')
+def list():
+ user = users.get_current_user()
+ tasks = Task.all().filter('user =', user)
+ return render_template('list.html', user=user, logout_url=users.create_logout_url("/"), tasks=tasks, flashes=get_flashed_messages());
+
+@app.route('/', methods=['POST'])
+def task_post():
+ name = request.form['name']
+ if not name:
+ flash("Oops you forgot to set a task name.")
+ return redirect(url_for('list'))
+ task = Task(name = request.form['name'])
+ task.user = users.get_current_user()
+ task.put()
+ return redirect(url_for('list'))
+
+@app.route('/delete/<int:id>')
+def task_delete(id):
+ task = Task.get_by_id(id)
+ if task and task.user == users.get_current_user():
+ task.delete()
+ else:
+ abort(404)
+
+ return redirect(url_for('list'))
+
+@app.route('/done/<int:id>')
+def task_done(id):
+ task = Task.get_by_id(id)
+ if task and task.user == users.get_current_user():
+ if task.done:
+ task.done = False
+ else:
+ task.done = True
+ task.put()
+ else:
+ abort(404)
+
+ return redirect(url_for('list'))
+
+
+# set the secret key. keep this really secret:
+app.secret_key = 'the secret key'
+
+if __name__ == '__main__':
+ app.run()
View
637 flask.py
@@ -0,0 +1,637 @@
+# -*- coding: utf-8 -*-
+"""
+ flask
+ ~~~~~
+
+ A microframework based on Werkzeug. It's extensively documented
+ and follows best practice patterns.
+
+ :copyright: (c) 2010 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+# Need to import with_statment from future in app engine
+from __future__ import with_statement
+
+import os
+import sys
+try:
+ import pkg_resources
+except ImportError:
+ # App engine doesn't support pkg_resources
+ pass
+
+from threading import local
+from contextlib import contextmanager
+from jinja2 import Environment, FileSystemLoader
+from werkzeug import Request as RequestBase, Response as ResponseBase, \
+ LocalStack, LocalProxy, create_environ, cached_property
+from werkzeug.routing import Map, Rule
+from werkzeug.exceptions import HTTPException, InternalServerError
+from werkzeug.contrib.securecookie import SecureCookie
+
+# utilities we import from Werkzeug and Jinja2 that are unused
+# in the module but are exported as public interface.
+from werkzeug import abort, redirect
+from jinja2 import Markup, escape
+
+class Request(RequestBase):
+ """The request object used by default in flask. Remembers the
+ matched endpoint and view arguments.
+
+ It is what ends up as :class:`~flask.request`. If you want to replace
+ the request object used you can subclass this and set
+ :attr:`~flask.Flask.request_class` to your subclass.
+ """
+
+ def __init__(self, environ):
+ RequestBase.__init__(self, environ)
+ self.endpoint = None
+ self.view_args = None
+
+
+class Response(ResponseBase):
+ """The response object that is used by default in flask. Works like the
+ response object from Werkzeug but is set to have a HTML mimetype by
+ default. Quite often you don't have to create this object yourself because
+ :meth:`~flask.Flask.make_response` will take care of that for you.
+
+ If you want to replace the response object used you can subclass this and
+ set :attr:`~flask.Flask.request_class` to your subclass.
+ """
+ default_mimetype = 'text/html'
+
+
+class _RequestGlobals(object):
+ pass
+
+
+class _RequestContext(object):
+ """The request context contains all request relevant information. It is
+ created at the beginning of the request and pushed to the
+ `_request_ctx_stack` and removed at the end of it. It will create the
+ URL adapter and request object for the WSGI environment provided.
+ """
+
+ def __init__(self, app, environ):
+ self.app = app
+ self.url_adapter = app.url_map.bind_to_environ(environ)
+ self.request = app.request_class(environ)
+ self.session = app.open_session(self.request)
+ self.g = _RequestGlobals()
+ self.flashes = None
+
+
+def url_for(endpoint, **values):
+ """Generates a URL to the given endpoint with the method provided.
+
+ :param endpoint: the endpoint of the URL (name of the function)
+ :param values: the variable arguments of the URL rule
+ """
+ return _request_ctx_stack.top.url_adapter.build(endpoint, values)
+
+
+def flash(message):
+ """Flashes a message to the next request. In order to remove the
+ flashed message from the session and to display it to the user,
+ the template has to call :func:`get_flashed_messages`.
+
+ :param message: the message to be flashed.
+ """
+ session['_flashes'] = (session.get('_flashes', [])) + [message]
+
+
+def get_flashed_messages():
+ """Pulls all flashed messages from the session and returns them.
+ Further calls in the same request to the function will return
+ the same messages.
+ """
+ flashes = _request_ctx_stack.top.flashes
+ if flashes is None:
+ _request_ctx_stack.top.flashes = flashes = \
+ session.pop('_flashes', [])
+ return flashes
+
+
+def render_template(template_name, **context):
+ """Renders a template from the template folder with the given
+ context.
+
+ :param template_name: the name of the template to be rendered
+ :param context: the variables that should be available in the
+ context of the template.
+ """
+ current_app.update_template_context(context)
+ return current_app.jinja_env.get_template(template_name).render(context)
+
+
+def render_template_string(source, **context):
+ """Renders a template from the given template source string
+ with the given context.
+
+ :param template_name: the sourcecode of the template to be
+ rendered
+ :param context: the variables that should be available in the
+ context of the template.
+ """
+ current_app.update_template_context(context)
+ return current_app.jinja_env.from_string(source).render(context)
+
+
+def _default_template_ctx_processor():
+ """Default template context processor. Injects `request`,
+ `session` and `g`.
+ """
+ reqctx = _request_ctx_stack.top
+ return dict(
+ request=reqctx.request,
+ session=reqctx.session,
+ g=reqctx.g
+ )
+
+
+class Flask(object):
+ """The flask object implements a WSGI application and acts as the central
+ object. It is passed the name of the module or package of the
+ application. Once it is created it will act as a central registry for
+ the view functions, the URL rules, template configuration and much more.
+
+ The name of the package is used to resolve resources from inside the
+ package or the folder the module is contained in depending on if the
+ package parameter resolves to an actual python package (a folder with
+ an `__init__.py` file inside) or a standard module (just a `.py` file).
+
+ For more information about resource loading, see :func:`open_resource`.
+
+ Usually you create a :class:`Flask` instance in your main module or
+ in the `__init__.py` file of your package like this::
+
+ from flask import Flask
+ app = Flask(__name__)
+ """
+
+ #: the class that is used for request objects. See :class:`~flask.request`
+ #: for more information.
+ request_class = Request
+
+ #: the class that is used for response objects. See
+ #: :class:`~flask.Response` for more information.
+ response_class = Response
+
+ #: path for the static files. If you don't want to use static files
+ #: you can set this value to `None` in which case no URL rule is added
+ #: and the development server will no longer serve any static files.
+ static_path = '/static'
+
+ #: if a secret key is set, cryptographic components can use this to
+ #: sign cookies and other things. Set this to a complex random value
+ #: when you want to use the secure cookie for instance.
+ secret_key = None
+
+ #: The secure cookie uses this for the name of the session cookie
+ session_cookie_name = 'session'
+
+ #: options that are passed directly to the Jinja2 environment
+ jinja_options = dict(
+ autoescape=True,
+ extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
+ )
+
+ def __init__(self, package_name):
+ #: the debug flag. Set this to `True` to enable debugging of
+ #: the application. In debug mode the debugger will kick in
+ #: when an unhandled exception ocurrs and the integrated server
+ #: will automatically reload the application if changes in the
+ #: code are detected.
+ self.debug = False
+
+ #: the name of the package or module. Do not change this once
+ #: it was set by the constructor.
+ self.package_name = package_name
+
+ #: a dictionary of all view functions registered. The keys will
+ #: be function names which are also used to generate URLs and
+ #: the values are the function objects themselves.
+ #: to register a view function, use the :meth:`route` decorator.
+ self.view_functions = {}
+
+ #: a dictionary of all registered error handlers. The key is
+ #: be the error code as integer, the value the function that
+ #: should handle that error.
+ #: To register a error handler, use the :meth:`errorhandler`
+ #: decorator.
+ self.error_handlers = {}
+
+ #: a list of functions that should be called at the beginning
+ #: of the request before request dispatching kicks in. This
+ #: can for example be used to open database connections or
+ #: getting hold of the currently logged in user.
+ #: To register a function here, use the :meth:`request_init`
+ #: decorator.
+ self.request_init_funcs = []
+
+ #: a list of functions that are called at the end of the
+ #: request. Tha function is passed the current response
+ #: object and modify it in place or replace it.
+ #: To register a function here use the :meth:`request_shtdown`
+ #: decorator.
+ self.request_shutdown_funcs = []
+
+ #: a list of functions that are called without arguments
+ #: to populate the template context. Each returns a dictionary
+ #: that the template context is updated with.
+ #: To register a function here, use the :meth:`context_processor`
+ #: decorator.
+ self.template_context_processors = [_default_template_ctx_processor]
+
+ self.url_map = Map()
+
+ if self.static_path is not None:
+ self.url_map.add(Rule(self.static_path + '/<filename>',
+ build_only=True, endpoint='static'))
+
+ #: the Jinja2 environment. It is created from the
+ #: :attr:`jinja_options` and the loader that is returned
+ #: by the :meth:`create_jinja_loader` function.
+ self.jinja_env = Environment(loader=self.create_jinja_loader(),
+ **self.jinja_options)
+ self.jinja_env.globals.update(
+ url_for=url_for,
+ get_flashed_messages=get_flashed_messages
+ )
+
+ def create_jinja_loader(self):
+ """Creates the Jinja loader. By default just a package loader for
+ the configured package is returned that looks up templates in the
+ `templates` folder. To add other loaders it's possible to
+ override this method.
+ """
+ #return PackageLoader(self.package_name)
+ return FileSystemLoader('templates')
+
+ def update_template_context(self, context):
+ """Update the template context with some commonly used variables.
+ This injects request, session and g into the template context.
+
+ :param context: the context as a dictionary that is updated in place
+ to add extra variables.
+ """
+ reqctx = _request_ctx_stack.top
+ for func in self.template_context_processors:
+ context.update(func())
+
+ def run(self, host='localhost', port=5000, **options):
+ """Runs the application on a local development server. If the
+ :attr:`debug` flag is set the server will automatically reload
+ for code changes and show a debugger in case an exception happened.
+
+ :param host: the hostname to listen on. set this to ``'0.0.0.0'``
+ to have the server available externally as well.
+ :param port: the port of the webserver
+ :param options: the options to be forwarded to the underlying
+ Werkzeug server. See :func:`werkzeug.run_simple`
+ for more information.
+ """
+ from werkzeug import run_simple
+ if 'debug' in options:
+ self.debug = options.pop('debug')
+ if self.static_path is not None:
+ options['static_files'] = {
+ self.static_path: (self.package_name, 'static')
+ }
+ options.setdefault('use_reloader', self.debug)
+ options.setdefault('use_debugger', self.debug)
+ return run_simple(host, port, self, **options)
+
+ def test_client(self):
+ """Creates a test client for this application. For information
+ about unit testing head over to :ref:`testing`.
+ """
+ from werkzeug import Client
+ return Client(self, self.response_class, use_cookies=True)
+
+ def open_resource(self, resource):
+ """Opens a resource from the application's resource folder. To see
+ how this works, consider the following folder structure::
+
+ /myapplication.py
+ /schemal.sql
+ /static
+ /style.css
+ /template
+ /layout.html
+ /index.html
+
+ If you want to open the `schema.sql` file you would do the
+ following::
+
+ with app.open_resource('schema.sql') as f:
+ contents = f.read()
+ do_something_with(contents)
+
+ :param resource: the name of the resource. To access resources within
+ subfolders use forward slashes as separator.
+ """
+ return pkg_resources.resource_stream(self.package_name, resource)
+
+ def open_session(self, request):
+ """Creates or opens a new session. Default implementation stores all
+ session data in a signed cookie. This requires that the
+ :attr:`secret_key` is set.
+
+ :param request: an instance of :attr:`request_class`.
+ """
+ key = self.secret_key
+ if key is not None:
+ return SecureCookie.load_cookie(request, self.session_cookie_name,
+ secret_key=key)
+
+ def save_session(self, session, response):
+ """Saves the session if it needs updates. For the default
+ implementation, check :meth:`open_session`.
+
+ :param session: the session to be saved (a
+ :class:`~werkzeug.contrib.securecookie.SecureCookie`
+ object)
+ :param response: an instance of :attr:`response_class`
+ """
+ if session is not None:
+ session.save_cookie(response, self.session_cookie_name)
+
+ def add_url_rule(self, rule, endpoint, **options):
+ """Connects a URL rule. Works exactly like the :meth:`route`
+ decorator but does not register the view function for the endpoint.
+
+ Basically this example::
+
+ @app.route('/')
+ def index():
+ pass
+
+ Is equivalent to the following::
+
+ def index():
+ pass
+ app.add_url_rule('index', '/')
+ app.view_functions['index'] = index
+
+ :param rule: the URL rule as string
+ :param endpoint: the endpoint for the registered URL rule. Flask
+ itself assumes the name of the view function as
+ endpoint
+ :param options: the options to be forwarded to the underlying
+ :class:`~werkzeug.routing.Rule` object
+ """
+ options['endpoint'] = endpoint
+ options.setdefault('methods', ('GET',))
+ self.url_map.add(Rule(rule, **options))
+
+ def route(self, rule, **options):
+ """A decorator that is used to register a view function for a
+ given URL rule. Example::
+
+ @app.route('/')
+ def index():
+ return 'Hello World'
+
+ Variables parts in the route can be specified with angular
+ brackets (``/user/<username>``). By default a variable part
+ in the URL accepts any string without a slash however a different
+ converter can be specified as well by using ``<converter:name>``.
+
+ Variable parts are passed to the view function as keyword
+ arguments.
+
+ The following converters are possible:
+
+ =========== ===========================================
+ `int` accepts integers
+ `float` like `int` but for floating point values
+ `path` like the default but also accepts slashes
+ =========== ===========================================
+
+ Here some examples::
+
+ @app.route('/')
+ def index():
+ pass
+
+ @app.route('/<username>')
+ def show_user(username):
+ pass
+
+ @app.route('/post/<int:post_id>')
+ def show_post(post_id):
+ pass
+
+ An important detail to keep in mind is how Flask deals with trailing
+ slashes. The idea is to keep each URL unique so the following rules
+ apply:
+
+ 1. If a rule ends with a slash and is requested without a slash
+ by the user, the user is automatically redirected to the same
+ page with a trailing slash attached.
+ 2. If a rule does not end with a trailing slash and the user request
+ the page with a trailing slash, a 404 not found is raised.
+
+ This is consistent with how web servers deal with static files. This
+ also makes it possible to use relative link targets safely.
+
+ The :meth:`route` decorator accepts a couple of other arguments
+ as well:
+
+ :param rule: the URL rule as string
+ :param methods: a list of methods this rule should be limited
+ to (``GET``, ``POST`` etc.). By default a rule
+ just listens for ``GET`` (and implicitly ``HEAD``).
+ :param subdomain: specifies the rule for the subdoain in case
+ subdomain matching is in use.
+ :param strict_slashes: can be used to disable the strict slashes
+ setting for this rule. See above.
+ :param options: other options to be forwarded to the underlying
+ :class:`~werkzeug.routing.Rule` object.
+ """
+ def decorator(f):
+ self.add_url_rule(rule, f.__name__, **options)
+ self.view_functions[f.__name__] = f
+ return f
+ return decorator
+
+ def errorhandler(self, code):
+ """A decorator that is used to register a function give a given
+ error code. Example::
+
+ @app.errorhandler(404)
+ def page_not_found():
+ return 'This page does not exist', 404
+
+ You can also register a function as error handler without using
+ the :meth:`errorhandler` decorator. The following example is
+ equivalent to the one above::
+
+ def page_not_found():
+ return 'This page does not exist', 404
+ app.error_handlers[404] = page_not_found
+
+ :param code: the code as integer for the handler
+ """
+ def decorator(f):
+ self.error_handlers[code] = f
+ return f
+ return decorator
+
+ def request_init(self, f):
+ """Registers a function to run before each request."""
+ self.request_init_funcs.append(f)
+ return f
+
+ def request_shutdown(self, f):
+ """Register a function to be run after each request."""
+ self.request_shutdown_funcs.append(f)
+ return f
+
+ def context_processor(self, f):
+ """Registers a template context processor function."""
+ self.template_context_processors.append(f)
+ return f
+
+ def match_request(self):
+ """Matches the current request against the URL map and also
+ stores the endpoint and view arguments on the request object
+ is successful, otherwise the exception is stored.
+ """
+ rv = _request_ctx_stack.top.url_adapter.match()
+ request.endpoint, request.view_args = rv
+ return rv
+
+ def dispatch_request(self):
+ """Does the request dispatching. Matches the URL and returns the
+ return value of the view or error handler. This does not have to
+ be a response object. In order to convert the return value to a
+ proper response object, call :func:`make_response`.
+ """
+ try:
+ endpoint, values = self.match_request()
+ return self.view_functions[endpoint](**values)
+ except HTTPException, e:
+ handler = self.error_handlers.get(e.code)
+ if handler is None:
+ return e
+ return handler(e)
+ except Exception, e:
+ handler = self.error_handlers.get(500)
+ if self.debug or handler is None:
+ raise
+ return handler(e)
+
+ def make_response(self, rv):
+ """Converts the return value from a view function to a real
+ response object that is an instance of :attr:`response_class`.
+
+ The following types are allowd for `rv`:
+
+ ======================= ===========================================
+ :attr:`response_class` the object is returned unchanged
+ :class:`str` a response object is created with the
+ string as body
+ :class:`unicode` a response object is created with the
+ string encoded to utf-8 as body
+ :class:`tuple` the response object is created with the
+ contents of the tuple as arguments
+ a WSGI function the function is called as WSGI application
+ and buffered as response object
+ ======================= ===========================================
+
+ :param rv: the return value from the view function
+ """
+ if isinstance(rv, self.response_class):
+ return rv
+ if isinstance(rv, basestring):
+ return self.response_class(rv)
+ if isinstance(rv, tuple):
+ return self.response_class(*rv)
+ return self.response_class.force_type(rv, request.environ)
+
+ def preprocess_request(self):
+ """Called before the actual request dispatching and will
+ call every as :func:`request_init` decorated function.
+ If any of these function returns a value it's handled as
+ if it was the return value from the view and further
+ request handling is stopped.
+ """
+ for func in self.request_init_funcs:
+ rv = func()
+ if rv is not None:
+ return rv
+
+ def process_response(self, response):
+ """Can be overridden in order to modify the response object
+ before it's sent to the WSGI server.
+
+ :param response: a :attr:`response_class` object.
+ :return: a new response object or the same, has to be an
+ instance of :attr:`response_class`.
+ """
+ session = _request_ctx_stack.top.session
+ if session is not None:
+ self.save_session(session, response)
+ for handler in self.request_shutdown_funcs:
+ response = handler(response)
+ return response
+
+ def wsgi_app(self, environ, start_response):
+ """The actual WSGI application. This is not implemented in
+ `__call__` so that middlewares can be applied:
+
+ app.wsgi_app = MyMiddleware(app.wsgi_app)
+
+ :param environ: a WSGI environment
+ :param start_response: a callable accepting a status code,
+ a list of headers and an optional
+ exception context to start the response
+ """
+ with self.request_context(environ):
+ rv = self.preprocess_request()
+ if rv is None:
+ rv = self.dispatch_request()
+ response = self.make_response(rv)
+ response = self.process_response(response)
+ return response(environ, start_response)
+
+ @contextmanager
+ def request_context(self, environ):
+ """Creates a request context from the given environment and binds
+ it to the current context. This must be used in combination with
+ the `with` statement because the request is only bound to the
+ current context for the duration of the `with` block.
+
+ Example usage::
+
+ with app.request_context(environ):
+ do_something_with(request)
+
+ :params environ: a WSGI environment
+ """
+ _request_ctx_stack.push(_RequestContext(self, environ))
+ try:
+ yield
+ finally:
+ _request_ctx_stack.pop()
+
+ def test_request_context(self, *args, **kwargs):
+ """Creates a WSGI environment from the given values (see
+ :func:`werkzeug.create_environ` for more information, this
+ function accepts the same arguments).
+ """
+ return self.request_context(create_environ(*args, **kwargs))
+
+ def __call__(self, environ, start_response):
+ """Shortcut for :attr:`wsgi_app`"""
+ return self.wsgi_app(environ, start_response)
+
+
+# context locals
+_request_ctx_stack = LocalStack()
+current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
+request = LocalProxy(lambda: _request_ctx_stack.top.request)
+session = LocalProxy(lambda: _request_ctx_stack.top.session)
+g = LocalProxy(lambda: _request_ctx_stack.top.g)
View
11 index.yaml
@@ -0,0 +1,11 @@
+indexes:
+
+# AUTOGENERATED
+
+# This index.yaml is automatically updated whenever the dev_appserver
+# detects that a new type of query is run. If you want to manage the
+# index.yaml file manually, remove the above marker line (the line
+# saying "# AUTOGENERATED"). If you want to manage some indexes
+# manually, move them above the marker line. The index.yaml file is
+# automatically uploaded to the admin console when you next deploy
+# your application using appcfg.py.
View
73 jinja2/__init__.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2
+ ~~~~~~
+
+ Jinja2 is a template engine written in pure Python. It provides a
+ Django inspired non-XML syntax but supports inline expressions and
+ an optional sandboxed environment.
+
+ Nutshell
+ --------
+
+ Here a small example of a Jinja2 template::
+
+ {% extends 'base.html' %}
+ {% block title %}Memberlist{% endblock %}
+ {% block content %}
+ <ul>
+ {% for user in users %}
+ <li><a href="{{ user.url }}">{{ user.username }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endblock %}
+
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+__docformat__ = 'restructuredtext en'
+try:
+ __version__ = __import__('pkg_resources') \
+ .get_distribution('Jinja2').version
+except:
+ __version__ = 'unknown'
+
+# high level interface
+from jinja2.environment import Environment, Template
+
+# loaders
+from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
+ DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \
+ ModuleLoader
+
+# bytecode caches
+from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
+ MemcachedBytecodeCache
+
+# undefined types
+from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
+
+# exceptions
+from jinja2.exceptions import TemplateError, UndefinedError, \
+ TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
+ TemplateAssertionError
+
+# decorators and public utilities
+from jinja2.filters import environmentfilter, contextfilter, \
+ evalcontextfilter
+from jinja2.utils import Markup, escape, clear_caches, \
+ environmentfunction, evalcontextfunction, contextfunction, \
+ is_undefined
+
+__all__ = [
+ 'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
+ 'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader',
+ 'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache',
+ 'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
+ 'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
+ 'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
+ 'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
+ 'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
+ 'evalcontextfilter', 'evalcontextfunction'
+]
View
130 jinja2/_stringdefs.py
130 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
280 jinja2/bccache.py
@@ -0,0 +1,280 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.bccache
+ ~~~~~~~~~~~~~~
+
+ This module implements the bytecode cache system Jinja is optionally
+ using. This is useful if you have very complex template situations and
+ the compiliation of all those templates slow down your application too
+ much.
+
+ Situations where this is useful are often forking web applications that
+ are initialized on the first request.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD.
+"""
+from os import path, listdir
+import marshal
+import tempfile
+import cPickle as pickle
+import fnmatch
+from cStringIO import StringIO
+try:
+ from hashlib import sha1
+except ImportError:
+ from sha import new as sha1
+from jinja2.utils import open_if_exists
+
+
+bc_version = 1
+bc_magic = 'j2'.encode('ascii') + pickle.dumps(bc_version, 2)
+
+
+class Bucket(object):
+ """Buckets are used to store the bytecode for one template. It's created
+ and initialized by the bytecode cache and passed to the loading functions.
+
+ The buckets get an internal checksum from the cache assigned and use this
+ to automatically reject outdated cache material. Individual bytecode
+ cache subclasses don't have to care about cache invalidation.
+ """
+
+ def __init__(self, environment, key, checksum):
+ self.environment = environment
+ self.key = key
+ self.checksum = checksum
+ self.reset()
+
+ def reset(self):
+ """Resets the bucket (unloads the bytecode)."""
+ self.code = None
+
+ def load_bytecode(self, f):
+ """Loads bytecode from a file or file like object."""
+ # make sure the magic header is correct
+ magic = f.read(len(bc_magic))
+ if magic != bc_magic:
+ self.reset()
+ return
+ # the source code of the file changed, we need to reload
+ checksum = pickle.load(f)
+ if self.checksum != checksum:
+ self.reset()
+ return
+ # now load the code. Because marshal is not able to load
+ # from arbitrary streams we have to work around that
+ if isinstance(f, file):
+ self.code = marshal.load(f)
+ else:
+ self.code = marshal.loads(f.read())
+
+ def write_bytecode(self, f):
+ """Dump the bytecode into the file or file like object passed."""
+ if self.code is None:
+ raise TypeError('can\'t write empty bucket')
+ f.write(bc_magic)
+ pickle.dump(self.checksum, f, 2)
+ if isinstance(f, file):
+ marshal.dump(self.code, f)
+ else:
+ f.write(marshal.dumps(self.code))
+
+ def bytecode_from_string(self, string):
+ """Load bytecode from a string."""
+ self.load_bytecode(StringIO(string))
+
+ def bytecode_to_string(self):
+ """Return the bytecode as string."""
+ out = StringIO()
+ self.write_bytecode(out)
+ return out.getvalue()
+
+
+class BytecodeCache(object):
+ """To implement your own bytecode cache you have to subclass this class
+ and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
+ these methods are passed a :class:`~jinja2.bccache.Bucket`.
+
+ A very basic bytecode cache that saves the bytecode on the file system::
+
+ from os import path
+
+ class MyCache(BytecodeCache):
+
+ def __init__(self, directory):
+ self.directory = directory
+
+ def load_bytecode(self, bucket):
+ filename = path.join(self.directory, bucket.key)
+ if path.exists(filename):
+ with open(filename, 'rb') as f:
+ bucket.load_bytecode(f)
+
+ def dump_bytecode(self, bucket):
+ filename = path.join(self.directory, bucket.key)
+ with open(filename, 'wb') as f:
+ bucket.write_bytecode(f)
+
+ A more advanced version of a filesystem based bytecode cache is part of
+ Jinja2.
+ """
+
+ def load_bytecode(self, bucket):
+ """Subclasses have to override this method to load bytecode into a
+ bucket. If they are not able to find code in the cache for the
+ bucket, it must not do anything.
+ """
+ raise NotImplementedError()
+
+ def dump_bytecode(self, bucket):
+ """Subclasses have to override this method to write the bytecode
+ from a bucket back to the cache. If it unable to do so it must not
+ fail silently but raise an exception.
+ """
+ raise NotImplementedError()
+
+ def clear(self):
+ """Clears the cache. This method is not used by Jinja2 but should be
+ implemented to allow applications to clear the bytecode cache used
+ by a particular environment.
+ """
+
+ def get_cache_key(self, name, filename=None):
+ """Returns the unique hash key for this template name."""
+ hash = sha1(name.encode('utf-8'))
+ if filename is not None:
+ if isinstance(filename, unicode):
+ filename = filename.encode('utf-8')
+ hash.update('|' + filename)
+ return hash.hexdigest()
+
+ def get_source_checksum(self, source):
+ """Returns a checksum for the source."""
+ return sha1(source.encode('utf-8')).hexdigest()
+
+ def get_bucket(self, environment, name, filename, source):
+ """Return a cache bucket for the given template. All arguments are
+ mandatory but filename may be `None`.
+ """
+ key = self.get_cache_key(name, filename)
+ checksum = self.get_source_checksum(source)
+ bucket = Bucket(environment, key, checksum)
+ self.load_bytecode(bucket)
+ return bucket
+
+ def set_bucket(self, bucket):
+ """Put the bucket into the cache."""
+ self.dump_bytecode(bucket)
+
+
+class FileSystemBytecodeCache(BytecodeCache):
+ """A bytecode cache that stores bytecode on the filesystem. It accepts
+ two arguments: The directory where the cache items are stored and a
+ pattern string that is used to build the filename.
+
+ If no directory is specified the system temporary items folder is used.
+
+ The pattern can be used to have multiple separate caches operate on the
+ same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
+ is replaced with the cache key.
+
+ >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
+
+ This bytecode cache supports clearing of the cache using the clear method.
+ """
+
+ def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
+ if directory is None:
+ directory = tempfile.gettempdir()
+ self.directory = directory
+ self.pattern = pattern
+
+ def _get_cache_filename(self, bucket):
+ return path.join(self.directory, self.pattern % bucket.key)
+
+ def load_bytecode(self, bucket):
+ f = open_if_exists(self._get_cache_filename(bucket), 'rb')
+ if f is not None:
+ try:
+ bucket.load_bytecode(f)
+ finally:
+ f.close()
+
+ def dump_bytecode(self, bucket):
+ f = open(self._get_cache_filename(bucket), 'wb')
+ try:
+ bucket.write_bytecode(f)
+ finally:
+ f.close()
+
+ def clear(self):
+ # imported lazily here because google app-engine doesn't support
+ # write access on the file system and the function does not exist
+ # normally.
+ from os import remove
+ files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
+ for filename in files:
+ try:
+ remove(path.join(self.directory, filename))
+ except OSError:
+ pass
+
+
+class MemcachedBytecodeCache(BytecodeCache):
+ """This class implements a bytecode cache that uses a memcache cache for
+ storing the information. It does not enforce a specific memcache library
+ (tummy's memcache or cmemcache) but will accept any class that provides
+ the minimal interface required.
+
+ Libraries compatible with this class:
+
+ - `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
+ - `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
+ - `cmemcache <http://gijsbert.org/cmemcache/>`_
+
+ (Unfortunately the django cache interface is not compatible because it
+ does not support storing binary data, only unicode. You can however pass
+ the underlying cache client to the bytecode cache which is available
+ as `django.core.cache.cache._client`.)
+
+ The minimal interface for the client passed to the constructor is this:
+
+ .. class:: MinimalClientInterface
+
+ .. method:: set(key, value[, timeout])
+
+ Stores the bytecode in the cache. `value` is a string and
+ `timeout` the timeout of the key. If timeout is not provided
+ a default timeout or no timeout should be assumed, if it's
+ provided it's an integer with the number of seconds the cache
+ item should exist.
+
+ .. method:: get(key)
+
+ Returns the value for the cache key. If the item does not
+ exist in the cache the return value must be `None`.
+
+ The other arguments to the constructor are the prefix for all keys that
+ is added before the actual cache key and the timeout for the bytecode in
+ the cache system. We recommend a high (or no) timeout.
+
+ This bytecode cache does not support clearing of used items in the cache.
+ The clear method is a no-operation function.
+ """
+
+ def __init__(self, client, prefix='jinja2/bytecode/', timeout=None):
+ self.client = client
+ self.prefix = prefix
+ self.timeout = timeout
+
+ def load_bytecode(self, bucket):
+ code = self.client.get(self.prefix + bucket.key)
+ if code is not None:
+ bucket.bytecode_from_string(code)
+
+ def dump_bytecode(self, bucket):
+ args = (self.prefix + bucket.key, bucket.bytecode_to_string())
+ if self.timeout is not None:
+ args += (self.timeout,)
+ self.client.set(*args)
View
1,633 jinja2/compiler.py
@@ -0,0 +1,1633 @@
+# -*- coding: utf-8 -*-
+"""
+ jinja2.compiler
+ ~~~~~~~~~~~~~~~
+
+ Compiles nodes into python code.
+
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD, see LICENSE for more details.
+"""
+from cStringIO import StringIO
+from itertools import chain
+from copy import deepcopy
+from jinja2 import nodes
+from jinja2.nodes import EvalContext
+from jinja2.visitor import NodeVisitor, NodeTransformer
+from jinja2.exceptions import TemplateAssertionError
+from jinja2.utils import Markup, concat, escape, is_python_keyword, next
+
+
+operators = {
+ 'eq': '==',
+ 'ne': '!=',
+ 'gt': '>',
+ 'gteq': '>=',
+ 'lt': '<',
+ 'lteq': '<=',
+ 'in': 'in',
+ 'notin': 'not in'
+}
+
+try:
+ exec '(0 if 0 else 0)'
+except SyntaxError:
+ have_condexpr = False
+else:
+ have_condexpr = True
+
+
+# what method to iterate over items do we want to use for dict iteration
+# in generated code? on 2.x let's go with iteritems, on 3.x with items
+if hasattr(dict, 'iteritems'):
+ dict_item_iter = 'iteritems'
+else:
+ dict_item_iter = 'items'
+
+
+# does if 0: dummy(x) get us x into the scope?
+def unoptimize_before_dead_code():
+ x = 42
+ def f():
+ if 0: dummy(x)
+ return f
+unoptimize_before_dead_code = bool(unoptimize_before_dead_code().func_closure)
+
+
+def generate(node, environment, name, filename, stream=None,
+ defer_init=False):
+ """Generate the python source for a node tree."""
+ if not isinstance(node, nodes.Template):
+ raise TypeError('Can\'t compile non template nodes')
+ generator = CodeGenerator(environment, name, filename, stream, defer_init)
+ generator.visit(node)
+ if stream is None:
+ return generator.stream.getvalue()
+
+
+def has_safe_repr(value):
+ """Does the node have a safe representation?"""
+ if value is None or value is NotImplemented or value is Ellipsis:
+ return True
+ if isinstance(value, (bool, int, long, float, complex, basestring,
+ xrange, Markup)):
+ return True
+ if isinstance(value, (tuple, list, set, frozenset)):
+ for item in value:
+ if not has_safe_repr(item):
+ return False
+ return True
+ elif isinstance(value, dict):
+ for key, value in value.iteritems():
+ if not has_safe_repr(key):
+ return False
+ if not has_safe_repr(value):
+ return False
+ return True
+ return False
+
+
+def find_undeclared(nodes, names):
+ """Check if the names passed are accessed undeclared. The return value
+ is a set of all the undeclared names from the sequence of names found.
+ """
+ visitor = UndeclaredNameVisitor(names)
+ try:
+ for node in nodes:
+ visitor.visit(node)
+ except VisitorExit:
+ pass
+ return visitor.undeclared
+
+
+class Identifiers(object):
+ """Tracks the status of identifiers in frames."""
+
+ def __init__(self):
+ # variables that are known to be declared (probably from outer
+ # frames or because they are special for the frame)
+ self.declared = set()
+
+ # undeclared variables from outer scopes
+ self.outer_undeclared = set()
+
+ # names that are accessed without being explicitly declared by
+ # this one or any of the outer scopes. Names can appear both in
+ # declared and undeclared.
+ self.undeclared = set()
+
+ # names that are declared locally
+ self.declared_locally = set()
+
+ # names that are declared by parameters
+ self.declared_parameter = set()
+
+ def add_special(self, name):
+ """Register a special name like `loop`."""
+ self.undeclared.discard(name)
+ self.declared.add(name)
+
+ def is_declared(self, name, local_only=False):
+ """Check if a name is declared in this or an outer scope."""
+ if name in self.declared_locally or name in self.declared_parameter:
+ return True
+ if local_only:
+ return False
+ return name in self.declared
+
+ def copy(self):
+ return deepcopy(self)
+
+
+class Frame(object):
+ """Holds compile time information for us."""
+
+ def __init__(self, eval_ctx, parent=None):
+ self.eval_ctx = eval_ctx
+ self.identifiers = Identifiers()
+
+ # a toplevel frame is the root + soft frames such as if conditions.
+ self.toplevel = False
+
+ # the root frame is basically just the outermost frame, so no if
+ # conditions. This information is used to optimize inheritance
+ # situations.
+ self.rootlevel = False
+
+ # in some dynamic inheritance situations the compiler needs to add
+ # write tests around output statements.
+ self.require_output_check = parent and parent.require_output_check
+
+ # inside some tags we are using a buffer rather than yield statements.
+ # this for example affects {% filter %} or {% macro %}. If a frame
+ # is buffered this variable points to the name of the list used as
+ # buffer.
+ self.buffer = None
+
+ # the name of the block we're in, otherwise None.
+ self.block = parent and parent.block or None
+
+ # a set of actually assigned names
+ self.assigned_names = set()
+
+ # the parent of this frame
+ self.parent = parent
+
+ if parent is not None:
+ self.identifiers.declared.update(
+ parent.identifiers.declared |
+ parent.identifiers.declared_parameter |
+ parent.assigned_names
+ )
+ self.identifiers.outer_undeclared.update(
+ parent.identifiers.undeclared -
+ self.identifiers.declared
+ )
+ self.buffer = parent.buffer
+
+ def copy(self):
+ """Create a copy of the current one."""
+ rv = object.__new__(self.__class__)
+ rv.__dict__.update(self.__dict__)
+ rv.identifiers = object.__new__(self.identifiers.__class__)
+ rv.identifiers.__dict__.update(self.identifiers.__dict__)
+ return rv
+
+ def inspect(self, nodes, hard_scope=False):
+ """Walk the node and check for identifiers. If the scope is hard (eg:
+ enforce on a python level) overrides from outer scopes are tracked
+ differently.
+ """
+ visitor = FrameIdentifierVisitor(self.identifiers, hard_scope)
+ for node in nodes:
+ visitor.visit(node)
+
+ def find_shadowed(self, extra=()):
+ """Find all the shadowed names. extra is an iterable of variables
+ that may be defined with `add_special` which may occour scoped.
+ """
+ i = self.identifiers
+ return (i.declared | i.outer_undeclared) & \
+ (i.declared_locally | i.declared_parameter) | \
+ set(x for x in extra if i.is_declared(x))
+
+ def inner(self):
+ """Return an inner frame."""
+ return Frame(self.eval_ctx, self)
+
+ def soft(self):
+ """Return a soft frame. A soft frame may not be modified as
+ standalone thing as it shares the resources with the frame it
+ was created of, but it's not a rootlevel frame any longer.
+ """
+ rv = self.copy()
+ rv.rootlevel = False
+ return rv
+
+ __copy__ = copy
+
+
+class VisitorExit(RuntimeError):
+ """Exception used by the `UndeclaredNameVisitor` to signal a stop."""
+
+
+class DependencyFinderVisitor(NodeVisitor):
+ """A visitor that collects filter and test calls."""
+
+ def __init__(self):
+ self.filters = set()
+ self.tests = set()
+
+ def visit_Filter(self, node):
+ self.generic_visit(node)
+ self.filters.add(node.name)
+
+ def visit_Test(self, node):
+ self.generic_visit(node)
+ self.tests.add(node.name)
+
+ def visit_Block(self, node):
+ """Stop visiting at blocks."""
+
+
+class UndeclaredNameVisitor(NodeVisitor):
+ """A visitor that checks if a name is accessed without being
+ declared. This is different from the frame visitor as it will
+ not stop at closure frames.
+ """
+
+ def __init__(self, names):
+ self.names = set(names)
+ self.undeclared = set()
+
+ def visit_Name(self, node):
+ if node.ctx == 'load' and node.name in self.names:
+ self.undeclared.add(node.name)
+ if self.undeclared == self.names:
+ raise VisitorExit()
+ else:
+ self.names.discard(node.name)
+
+ def visit_Block(self, node):
+ """Stop visiting a blocks."""
+
+
+class FrameIdentifierVisitor(NodeVisitor):
+ """A visitor for `Frame.inspect`."""
+
+ def __init__(self, identifiers, hard_scope):
+ self.identifiers = identifiers
+ self.hard_scope = hard_scope
+
+ def visit_Name(self, node):
+ """All assignments to names go through this function."""
+ if node.ctx == 'store':
+ self.identifiers.declared_locally.add(node.name)
+ elif node.ctx == 'param':
+ self.identifiers.declared_parameter.add(node.name)
+ elif node.ctx == 'load' and not \
+ self.identifiers.is_declared(node.name, self.hard_scope):
+ self.identifiers.undeclared.add(node.name)
+
+ def visit_If(self, node):
+ self.visit(node.test)
+ real_identifiers = self.identifiers
+
+ old_names = real_identifiers.declared_locally | \
+ real_identifiers.declared_parameter
+
+ def inner_visit(nodes):
+ if not nodes:
+ return set()
+ self.identifiers = real_identifiers.copy()
+ for subnode in nodes:
+ self.visit(subnode)
+ rv = self.identifiers.declared_locally - old_names
+ # we have to remember the undeclared variables of this branch
+ # because we will have to pull them.
+ real_identifiers.undeclared.update(self.identifiers.undeclared)
+ self.identifiers = real_identifiers
+ return rv
+
+ body = inner_visit(node.body)
+ else_ = inner_visit(node.else_ or ())
+
+ # the differences between the two branches are also pulled as
+ # undeclared variables
+ real_identifiers.undeclared.update(body.symmetric_difference(else_) -
+ real_identifiers.declared)
+
+ # remember those that are declared.
+ real_identifiers.declared_locally.update(body | else_)
+
+ def visit_Macro(self, node):
+ self.identifiers.declared_locally.add(node.name)
+
+ def visit_Import(self, node):
+ self.generic_visit(node)
+ self.identifiers.declared_locally.add(node.target)
+
+ def visit_FromImport(self, node):
+ self.generic_visit(node)
+ for name in node.names:
+ if isinstance(name, tuple):
+ self.identifiers.declared_locally.add(name[1])
+ else:
+ self.identifiers.declared_locally.add(name)
+
+ def visit_Assign(self, node):
+ """Visit assignments in the correct order."""
+ self.visit(node.node)
+ self.visit(node.target)
+
+ def visit_For(self, node):
+ """Visiting stops at for blocks. However the block sequence
+ is visited as part of the outer scope.
+ """
+ self.visit(node.iter)
+
+ def visit_CallBlock(self, node):
+ self.visit(node.call)
+
+ def visit_FilterBlock(self, node):
+ self.visit(node.filter)
+
+ def visit_Scope(self, node):
+ """Stop visiting at scopes."""
+
+ def visit_Block(self, node):
+ """Stop visiting at blocks."""
+
+
+class CompilerExit(Exception):
+ """Raised if the compiler encountered a situation where it just
+ doesn't make sense to further process the code. Any block that
+ raises such an exception is not further processed.
+ """
+
+
+class CodeGenerator(NodeVisitor):
+
+ def __init__(self, environment, name, filename, stream=None,
+ defer_init=False):
+ if stream is None:
+ stream = StringIO()
+ self.environment = environment
+ self.name = name
+ self.filename = filename
+ self.stream = stream
+ self.created_block_context = False
+ self.defer_init = defer_init
+
+ # aliases for imports
+ self.import_aliases = {}
+
+ # a registry for all blocks. Because blocks are moved out
+ # into the global python scope they are registered here
+ self.blocks = {}
+
+ # the number of extends statements so far
+ self.extends_so_far = 0
+
+ # some templates have a rootlevel extends. In this case we
+ # can safely assume that we're a child template and do some
+ # more optimizations.
+ self.has_known_extends = False
+
+ # the current line number
+ self.code_lineno = 1
+
+ # registry of all filters and tests (global, not block local)
+ self.tests = {}
+ self.filters = {}
+
+ # the debug information
+ self.debug_info = []
+ self._write_debug_info = None
+
+ # the number of new lines before the next write()
+ self._new_lines = 0
+
+ # the line number of the last written statement
+ self._last_line = 0
+
+ # true if nothing was written so far.
+ self._first_write = True
+
+ # used by the `temporary_identifier` method to get new
+ # unique, temporary identifier
+ self._last_identifier = 0
+
+ # the current indentation
+ self._indentation = 0
+
+ # -- Various compilation helpers
+
+ def fail(self, msg, lineno):
+ """Fail with a :exc:`TemplateAssertionError`."""
+ raise TemplateAssertionError(msg, lineno, self.name, self.filename)
+
+ def temporary_identifier(self):
+ """Get a new unique identifier."""
+ self._last_identifier += 1
+ return 't_%d' % self._last_identifier
+
+ def buffer(self, frame):
+ """Enable buffering for the frame from that point onwards."""
+ frame.buffer = self.temporary_identifier()
+ self.writeline('%s = []' % frame.buffer)
+
+ def return_buffer_contents(self, frame):
+ """Return the buffer contents of the frame."""
+ if frame.eval_ctx.volatile:
+ self.writeline('if context.eval_ctx.autoescape:')
+ self.indent()
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ self.outdent()
+ self.writeline('else:')
+ self.indent()
+ self.writeline('return concat(%s)' % frame.buffer)
+ self.outdent()
+ elif frame.eval_ctx.autoescape:
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ else:
+ self.writeline('return concat(%s)' % frame.buffer)
+
+ def indent(self):
+ """Indent by one."""
+ self._indentation += 1
+
+ def outdent(self, step=1):
+ """Outdent by step."""
+ self._indentation -= step
+
+ def start_write(self, frame, node=None):
+ """Yield or write into the frame buffer."""
+ if frame.buffer is None:
+ self.writeline('yield ', node)
+ else:
+ self.writeline('%s.append(' % frame.buffer, node)
+
+ def end_write(self, frame):
+ """End the writing process started by `start_write`."""
+ if frame.buffer is not None:
+ self.write(')')
+
+ def simple_write(self, s, frame, node=None):
+ """Simple shortcut for start_write + write + end_write."""
+ self.start_write(frame, node)
+ self.write(s)
+ self.end_write(frame)
+
+ def blockvisit(self, nodes, frame):
+ """Visit a list of nodes as block in a frame. If the current frame
+ is no buffer a dummy ``if 0: yield None`` is written automatically
+ unless the force_generator parameter is set to False.
+ """
+ if frame.buffer is None:
+ self.writeline('if 0: yield None')
+ else:
+ self.writeline('pass')
+ try:
+ for node in nodes:
+ self.visit(node, frame)
+ except CompilerExit:
+ pass
+
+ def write(self, x):
+ """Write a string into the output stream."""
+ if self._new_lines:
+ if not self._first_write:
+ self.stream.write('\n' * self._new_lines)
+ self.code_lineno += self._new_lines
+ if self._write_debug_info is not None:
+ self.debug_info.append((self._write_debug_info,
+ self.code_lineno))
+ self._write_debug_info = None
+ self._first_write = False
+ self.stream.write(' ' * self._indentation)
+ self._new_lines = 0
+ self.stream.write(x)
+
+ def writeline(self, x, node=None, extra=0):
+ """Combination of newline and write."""
+ self.newline(node, extra)
+ self.write(x)
+
+ def newline(self, node=None, extra=0):
+ """Add one or more newlines before the next write."""
+ self._new_lines = max(self._new_lines, 1 + extra)
+ if node is not None and node.lineno != self._last_line:
+ self._write_debug_info = node.lineno
+ self._last_line = node.lineno
+
+ def signature(self, node, frame, extra_kwargs=None):
+ """Writes a function call to the stream for the current node.
+ A leading comma is added automatically. The extra keyword
+ arguments may not include python keywords otherwise a syntax
+ error could occour. The extra keyword arguments should be given
+ as python dict.
+ """
+ # if any of the given keyword arguments is a python keyword
+ # we have to make sure that no invalid call is created.
+ kwarg_workaround = False
+ for kwarg in chain((x.key for x in node.kwargs), extra_kwargs or ()):
+ if is_python_keyword(kwarg):
+ kwarg_workaround = True
+ break
+
+ for arg in node.args:
+ self.write(', ')
+ self.visit(arg, frame)
+
+ if not kwarg_workaround:
+ for kwarg in node.kwargs:
+ self.write(', ')
+ self.visit(kwarg, frame)
+ if extra_kwargs is not None:
+ for key, value in extra_kwargs.iteritems():
+ self.write(', %s=%s' % (key, value))
+ if node.dyn_args:
+ self.write(', *')
+ self.visit(node.dyn_args, frame)
+
+ if kwarg_workaround:
+ if node.dyn_kwargs is not None:
+ self.write(', **dict({')
+ else:
+ self.write(', **{')
+ for kwarg in node.kwargs:
+ self.write('%r: ' % kwarg.key)
+ self.visit(kwarg.value, frame)
+ self.write(', ')
+ if extra_kwargs is not None:
+ for key, value in extra_kwargs.iteritems():
+ self.write('%r: %s, ' % (key, value))
+ if node.dyn_kwargs is not None:
+ self.write('}, **')
+ self.visit(node.dyn_kwargs, frame)
+ self.write(')')
+ else:
+ self.write('}')
+
+ elif node.dyn_kwargs is not None:
+ self.write(', **')
+ self.visit(node.dyn_kwargs, frame)
+
+ def pull_locals(self, frame):
+ """Pull all the references identifiers into the local scope."""
+ for name in frame.identifiers.undeclared:
+ self.writeline('l_%s = context.resolve(%r)' % (name, name))
+
+ def pull_dependencies(self, nodes):
+ """Pull all the dependencies."""
+ visitor = DependencyFinderVisitor()
+ for node in nodes:
+ visitor.visit(node)
+ for dependency in 'filters', 'tests':
+ mapping = getattr(self, dependency)
+ for name in getattr(visitor, dependency):
+ if name not in mapping:
+ mapping[name] = self.temporary_identifier()
+ self.writeline('%s = environment.%s[%r]' %
+ (mapping[name], dependency, name))
+
+ def unoptimize_scope(self, frame):
+ """Disable Python optimizations for the frame."""
+ # XXX: this is not that nice but it has no real overhead. It
+ # mainly works because python finds the locals before dead code
+ # is removed. If that breaks we have to add a dummy function
+ # that just accepts the arguments and does nothing.
+ if frame.identifiers.declared:
+ self.writeline('%sdummy(%s)' % (
+ unoptimize_before_dead_code and 'if 0: ' or '',
+ ', '.join('l_' + name for name in frame.identifiers.declared)
+ ))
+
+ def push_scope(self, frame, extra_vars=()):
+ """This function returns all the shadowed variables in a dict
+ in the form name: alias and will write the required assignments
+ into the current scope. No indentation takes place.
+
+ This also predefines locally declared variables from the loop
+ body because under some circumstances it may be the case that
+
+ `extra_vars` is passed to `Frame.find_shadowed`.
+ """
+ aliases = {}
+ for name in frame.find_shadowed(extra_vars):
+ aliases[name] = ident = self.temporary_identifier()
+ self.writeline('%s = l_%s' % (ident, name))
+ to_declare = set()
+ for name in frame.identifiers.declared_locally:
+ if name not in aliases:
+ to_declare.add('l_' + name)
+ if to_declare:
+ self.writeline(' = '.join(to_declare) + ' = missing')
+ return aliases
+
+ def pop_scope(self, aliases, frame):
+ """Restore all aliases and delete unused variables."""
+ for name, alias in aliases.iteritems():
+ self.writeline('l_%s = %s' % (name, alias))
+ to_delete = set()
+ for name in frame.identifiers.declared_locally:
+ if name not in aliases:
+ to_delete.add('l_' + name)
+ if to_delete:
+ # we cannot use the del statement here because enclosed
+ # scopes can trigger a SyntaxError:
+ # a = 42; b = lambda: a; del a
+ self.writeline(' = '.join(to_delete) + ' = missing')
+
+ def function_scoping(self, node, frame, children=None,
+ find_special=True):
+ """In Jinja a few statements require the help of anonymous
+ functions. Those are currently macros and call blocks and in
+ the future also recursive loops. As there is currently
+ technical limitation that doesn't allow reading and writing a
+ variable in a scope where the initial value is coming from an
+ outer scope, this function tries to fall back with a common
+ error message. Additionally the frame passed is modified so
+ that the argumetns are collected and callers are looked up.
+
+ This will return the modified frame.
+ """
+ # we have to iterate twice over it, make sure that works
+ if children is None:
+ children = node.iter_child_nodes()
+ children = list(children)
+ func_frame = frame.inner()
+ func_frame.inspect(children, hard_scope=True)
+
+ # variables that are undeclared (accessed before declaration) and
+ # declared locally *and* part of an outside scope raise a template
+ # assertion error. Reason: we can't generate reasonable code from
+ # it without aliasing all the variables.
+ # this could be fixed in Python 3 where we have the nonlocal
+ # keyword or if we switch to bytecode generation
+ overriden_closure_vars = (
+ func_frame.identifiers.undeclared &
+ func_frame.identifiers.declared &
+ (func_frame.identifiers.declared_locally |
+ func_frame.identifiers.declared_parameter)
+ )
+ if overriden_closure_vars:
+ self.fail('It\'s not possible to set and access variables '
+ 'derived from an outer scope! (affects: %s)' %
+ ', '.join(sorted(overriden_closure_vars)), node.lineno)
+
+ # remove variables from a closure from the frame's undeclared
+ # identifiers.
+ func_frame.identifiers.undeclared -= (
+ func_frame.identifiers.undeclared &
+ func_frame.identifiers.declared
+ )
+
+ # no special variables for this scope, abort early
+ if not find_special:
+ return func_frame
+
+ func_frame.accesses_kwargs = False
+ func_frame.accesses_varargs = False
+ func_frame.accesses_caller = False
+ func_frame.arguments = args = ['l_' + x.name for x in node.args]
+
+ undeclared = find_undeclared(children, ('caller', 'kwargs', 'varargs'))
+
+ if 'caller' in undeclared:
+ func_frame.accesses_caller = True
+ func_frame.identifiers.add_special('caller')
+ args.append('l_caller')
+ if 'kwargs' in undeclared:
+ func_frame.accesses_kwargs = True
+ func_frame.identifiers.add_special('kwargs')
+ args.append('l_kwargs')
+ if 'varargs' in undeclared:
+ func_frame.accesses_varargs = True
+ func_frame.identifiers.add_special('varargs')
+ args.append('l_varargs')
+ return func_frame
+
+ def macro_body(self, node, frame, children=None):
+ """Dump the function def of a macro or call block."""
+ frame = self.function_scoping(node, frame, children)
+ # macros are delayed, they never require output checks
+ frame.require_output_check = False
+ args = frame.arguments
+ # XXX: this is an ugly fix for the loop nesting bug
+ # (tests.test_old_bugs.test_loop_call_bug). This works around
+ # a identifier nesting problem we have in general. It's just more
+ # likely to happen in loops which is why we work around it. The
+ # real solution would be "nonlocal" all the identifiers that are
+ # leaking into a new python frame and might be used both unassigned
+ # and assigned.
+ if 'loop' in frame.identifiers.declared:
+ args = args + ['l_loop=l_loop']
+ self.writeline('def macro(%s):' % ', '.join(args), node)
+ self.indent()
+ self.buffer(frame)
+ self.pull_locals(frame)
+ self.blockvisit(node.body, frame)
+ self.return_buffer_contents(frame)
+ self.outdent()
+ return frame
+
+ def macro_def(self, node, frame):
+ """Dump the macro definition for the def created by macro_body."""
+ arg_tuple = ', '.join(repr(x.name) for x in node.args)
+ name = getattr(node, 'name', None)
+ if len(node.args) == 1:
+ arg_tuple += ','
+ self.write('Macro(environment, macro, %r, (%s), (' %
+ (name, arg_tuple))
+ for arg in node.defaults:
+ self.visit(arg, frame)
+ self.write(', ')
+ self.write('), %r, %r, %r)' % (
+ bool(frame.accesses_kwargs),
+ bool(frame.accesses_varargs),
+ bool(frame.accesses_caller)
+ ))
+
+ def position(self, node):
+ """Return a human readable position for the node."""
+ rv = 'line %d' % node.lineno
+ if self.name is not None:
+ rv += ' in ' + repr(self.name)
+ return rv
+
+ # -- Statement Visitors
+
+ def visit_Template(self, node, frame=None):
+ assert frame is None, 'no root frame allowed'
+ eval_ctx = EvalContext(self.environment, self.name)
+
+ from jinja2.runtime import __all__ as exported
+ self.writeline('from __future__ import division')
+ self.writeline('from jinja2.runtime import ' + ', '.join(exported))
+ if not unoptimize_before_dead_code:
+ self.writeline('dummy = lambda *x: None')
+
+ # if we want a deferred initialization we cannot move the
+ # environment into a local name
+ envenv = not self.defer_init and ', environment=environment' or ''
+
+ # do we have an extends tag at all? If not, we can save some
+ # overhead by just not processing any inheritance code.
+ have_extends = node.find(nodes.Extends) is not None
+
+ # find all blocks
+ for block in node.find_all(nodes.Block):
+ if block.name in self.blocks:
+ self.fail('block %r defined twice' % block.name, block.lineno)
+ self.blocks[block.name] = block
+
+ # find all imports and import them
+ for import_ in node.find_all(nodes.ImportedName):
+ if import_.importname not in self.import_aliases:
+ imp = import_.importname
+ self.import_aliases[imp] = alias = self.temporary_identifier()
+ if '.' in imp:
+ module, obj = imp.rsplit('.', 1)
+ self.writeline('from %s import %s as %s' %
+ (module, obj, alias))
+ else:
+ self.writeline('import %s as %s' % (imp, alias))
+
+ # add the load name
+ self.writeline('name = %r' % self.name)
+
+ # generate the root render function.
+ self.writeline('def root(context%s):' % envenv, extra=1)
+
+ # process the root
+ frame = Frame(eval_ctx)
+ frame.inspect(node.body)
+ frame.toplevel = frame.rootlevel = True
+ frame.require_output_check = have_extends and not self.has_known_extends
+ self.indent()
+ if have_extends:
+ self.writeline('parent_template = None')
+ if 'self' in find_undeclared(node.body, ('self',)):
+ frame.identifiers.add_special('self')
+ self.writeline('l_self = TemplateReference(context)')
+ self.pull_locals(frame)
+ self.pull_dependencies(node.body)
+ self.blockvisit(node.body, frame)
+ self.outdent()
+
+ # make sure that the parent root is called.
+ if have_extends:
+ if not self.has_known_extends:
+ self.indent()
+ self.writeline('if parent_template is not None:')
+ self.indent()
+ self.writeline('for event in parent_template.'
+ 'root_render_func(context):')
+ self.indent()
+ self.writeline('yield event')
+ self.outdent(2 + (not self.has_known_extends))
+
+ # at this point we now have the blocks collected and can visit them too.
+ for name, block in self.blocks.iteritems():
+ block_frame = Frame(eval_ctx)
+ block_frame.inspect(block.body)
+ block_frame.block = name
+ self.writeline('def block_%s(context%s):' % (name, envenv),
+ block, 1)
+ self.indent()
+ undeclared = find_undeclared(block.body, ('self', 'super'))
+ if 'self' in undeclared:
+ block_frame.identifiers.add_special('self')
+ self.writeline('l_self = TemplateReference(context)')
+ if 'super' in undeclared:
+ block_frame.identifiers.add_special('super')
+ self.writeline('l_super = context.super(%r, '
+ 'block_%s)' % (name, name))
+ self.pull_locals(block_frame)
+ self.pull_dependencies(block.body)
+ self.blockvisit(block.body, block_frame)
+ self.outdent()
+
+ self.writeline('blocks = {%s}' % ', '.join('%r: block_%s' % (x, x)
+ for x in self.blocks),
+ extra=1)
+
+ # add a function that returns the debug info
+ self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x
+ in self.debug_info))
+
+ def visit_Block(self, node, frame):
+ """Call a block and register it for the template."""
+ level = 1
+ if frame.toplevel:
+ # if we know that we are a child template, there is no need to
+ # check if we are one
+ if self.has_known_extends:
+ return
+ if self.extends_so_far > 0:
+ self.writeline('if parent_template is None:')
+ 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)
+ self.indent()
+ self.simple_write('event', frame)
+ self.outdent(level)
+
+ def visit_Extends(self, node, frame):
+ """Calls the extender."""
+ if not frame.toplevel:
+ self.fail('cannot use extend from a non top-level scope',
+ node.lineno)
+
+ # if the number of extends statements in general is zero so
+ # far, we don't have to add a check if something extended
+ # the template before this one.
+ if self.extends_so_far > 0:
+
+ # if we have a known extends we just add a template runtime
+ # error into the generated code. We could catch that at compile
+ # time too, but i welcome it not to confuse users by throwing the
+ # same error at different times just "because we can".
+ if not self.has_known_extends:
+ self.writeline('if parent_template is not None:')
+ self.indent()
+ self.writeline('raise TemplateRuntimeError(%r)' %
+ 'extended multiple times')
+ self.outdent()
+
+ # if we have a known extends already we don't need that code here
+ # as we know that the template execution will end here.
+ if self.has_known_extends:
+ raise CompilerExit()
+
+ self.writeline('parent_template = environment.get_template(', node)
+ self.visit(node.template, frame)
+ self.write(', %r)' % self.name)
+ self.writeline('for name, parent_block in parent_template.'
+ 'blocks.%s():' % dict_item_iter)
+ self.indent()
+ self.writeline('context.blocks.setdefault(name, []).'
+ 'append(parent_block)')
+ self.outdent()
+
+ # if this extends statement was in the root level we can take
+ # advantage of that information and simplify the generated code
+ # in the top level from this point onwards
+ if frame.rootlevel:
+ self.has_known_extends = True
+
+ # and now we have one more
+ self.extends_so_far += 1
+
+ def visit_Include(self, node, frame):
+ """Handles includes."""
+ if node.with_context:
+ self.unoptimize_scope(frame)
+ if node.ignore_missing:
+ self.writeline('try:')
+ self.indent()
+
+ func_name = 'get_or_select_template'
+ if isinstance(node.template, nodes.Const):
+ if isinstance(node.template.value, basestring):
+ func_name = 'get_template'
+ elif isinstance(node.template.value, (tuple, list)):
+ func_name = 'select_template'
+ elif isinstance(node.template, (nodes.Tuple, nodes.List)):
+ func_name = 'select_template'
+
+ self.writeline('template = environment.%s(' % func_name, node)
+ self.visit(node.template, frame)
+ self.write(', %r)' % self.name)
+ if node.ignore_missing:
+ self.outdent()
+ self.writeline('except TemplateNotFound:')
+ self.indent()
+ self.writeline('pass')
+ self.outdent()
+ self.writeline('else:')
+ self.indent()
+
+ if node.with_context:
+ self.writeline('for event in template.root_render_func('
+ 'template.new_context(context.parent, True, '
+ 'locals())):')
+ else:
+ self.writeline('for event in template.module._body_stream:')
+
+ self.indent()
+ self.simple_write('event', frame)
+ self.outdent()
+
+ if node.ignore_missing:
+ self.outdent()
+
+ def visit_Import(self, node, frame):
+ """Visit regular imports."""
+ if node.with_context:
+ self.unoptimize_scope(frame)
+ self.writeline('l_%s = ' % node.target, node)
+ if frame.toplevel:
+ self.write('context.vars[%r] = ' % node.target)
+ 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())')
+ else:
+ self.write('module')
+ if frame.toplevel and not node.target.startswith('_'):
+ self.writeline('context.exported_vars.discard(%r)' % node.target)
+ frame.assigned_names.add(node.target)
+
+ def visit_FromImport(self, node, frame):
+ """Visit named imports."""
+ self.newline(node)
+ self.write('included_template = environment.get_template(')
+ self.visit(node.template, frame)
+ self.write(', %r).' % self.name)
+ if node.with_context:
+ self.write('make_module(context.parent, True)')
+ else:
+ self.write('module')
+
+ var_names = []
+ discarded_names = []
+ for name in node.names:
+ if isinstance(name, tuple):
+ name, alias = name
+ else:
+ alias = name
+ self.writeline('l_%s = getattr(included_template, '
+ '%r, missing)' % (alias, name))
+ self.writeline('if l_%s is missing:' % alias)
+ self.indent()
+ self.writeline('l_%s = environment.undefined(%r %% '
+ 'included_template.__name__, '
+ 'name=%r)' %
+ (alias, 'the template %%r (imported on %s) does '
+ 'not export the requested name %s' % (
+ self.position(node),
+ repr(name)
+ ), name))
+ self.outdent()
+ if frame.toplevel:
+ var_names.append(alias)
+ if not alias.startswith('_'):
+ discarded_names.append(alias)
+ frame.assigned_names.add(alias)
+
+ if var_names:
+ if len(var_names) == 1:
+ name = var_names[0]
+ self.writeline('context.vars[%r] = l_%s' % (name, name))
+ else:
+ self.writeline('context.vars.update({%s})' % ', '.join(
+ '%r: l_%s' % (name, name) for name in var_names
+ ))
+ if discarded_names:
+ if len(discarded_names) == 1:
+ self.writeline('context.exported_vars.discard(%r)' %
+ discarded_names[0])
+ else:
+ self.writeline('context.exported_vars.difference_'
+ 'update((%s))' % ', '.join(map(repr, discarded_names)))
+
+ def visit_For(self, node, frame):
+ # when calculating the nodes for the inner frame we have to exclude
+ # the iterator contents from it
+ children = node.iter_child_nodes(exclude=('iter',))
+ if node.recursive:
+ loop_frame = self.function_scoping(node, frame, children,
+ find_special=False)
+ else:
+ loop_frame = frame.inner()
+ loop_frame.inspect(children)
+
+ # try to figure out if we have an extended loop. An extended loop
+ # is necessary if the loop is in recursive mode if the special loop
+ # variable is accessed in the body.
+ extended_loop = node.recursive or 'loop' in \
+ find_undeclared(node.iter_child_nodes(
+ only=('body',)), ('loop',))
+
+ # if we don't have an recursive loop we have to find the shadowed
+ # variables at that point. Because loops can be nested but the loop
+ # variable is a special one we have to enforce aliasing for it.
+ if not node.recursive:
+ aliases = self.push_scope(loop_frame, ('loop',))
+
+ # otherwise we set up a buffer and add a function def
+ else:
+ self.writeline('def loop(reciter, loop_render_func):', node)
+ self.indent()
+ self.buffer(loop_frame)
+ aliases = {}
+
+ # make sure the loop variable is a special one and raise a template
+ # assertion error if a loop tries to write to loop
+ if extended_loop:
+ loop_frame.identifiers.add_special('loop')
+ for name in node.find_all(nodes.Name):
+ if name.ctx == 'store' and name.name == 'loop':
+ self.fail('Can\'t assign to