Flask update

This commit is contained in:
Ruud
2012-01-06 17:09:44 +01:00
parent 168a9a2f95
commit f7d72dd5a9
114 changed files with 1540 additions and 3835 deletions
Regular → Executable
+2 -2
View File
@@ -6,11 +6,11 @@
A microframework based on Werkzeug. It's extensively documented
and follows best practice patterns.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
__version__ = '0.8-dev'
__version__ = '0.9-dev'
# utilities we import from Werkzeug and Jinja2 that are unused
# in the module but are exported as public interface.
Regular → Executable
+79 -97
View File
@@ -5,7 +5,7 @@
This module implements the central WSGI application object.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
@@ -41,6 +41,12 @@ from .signals import request_started, request_finished, got_request_exception, \
_logger_lock = Lock()
def _make_timedelta(value):
if not isinstance(value, timedelta):
return timedelta(seconds=value)
return value
def setupmethod(f):
"""Wraps a method so that it performs a check in debug mode if the
first request was already handled.
@@ -184,7 +190,8 @@ class Flask(_PackageBoundObject):
#: This attribute can also be configured from the config with the
#: `PERMANENT_SESSION_LIFETIME` configuration key. Defaults to
#: ``timedelta(days=31)``
permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME')
permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME',
get_converter=_make_timedelta)
#: Enable this if you want to use the X-Sendfile feature. Keep in
#: mind that the server has to support this. This only affects files
@@ -231,11 +238,16 @@ class Flask(_PackageBoundObject):
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'SESSION_COOKIE_NAME': 'session',
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': None,
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'MAX_CONTENT_LENGTH': None,
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False
@@ -461,7 +473,7 @@ class Flask(_PackageBoundObject):
if self.import_name == '__main__':
fn = getattr(sys.modules['__main__'], '__file__', None)
if fn is None:
return 'unknown'
return '__main__'
return os.path.splitext(os.path.basename(fn))[0]
return self.import_name
@@ -601,7 +613,7 @@ class Flask(_PackageBoundObject):
"""Creates the loader for the Jinja2 environment. Can be used to
override just the loader and keeping the rest unchanged. It's
discouraged to override this function. Instead one should override
the :meth:`create_jinja_loader` function instead.
the :meth:`jinja_loader` function instead.
The global loader dispatches between the loaders of the application
and the individual blueprints.
@@ -652,7 +664,7 @@ class Flask(_PackageBoundObject):
# existing views.
context.update(orig_ctx)
def run(self, host='127.0.0.1', port=5000, **options):
def run(self, host='127.0.0.1', port=5000, debug=None, **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.
@@ -675,14 +687,16 @@ class Flask(_PackageBoundObject):
: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 debug: if given, enable or disable debug mode.
See :attr:`debug`.
:param options: the options to be forwarded to the underlying
Werkzeug server. See
:func:`werkzeug.serving.run_simple` for more
information.
"""
from werkzeug.serving import run_simple
if 'debug' in options:
self.debug = options.pop('debug')
if debug is not None:
self.debug = bool(debug)
options.setdefault('use_reloader', self.debug)
options.setdefault('use_debugger', self.debug)
try:
@@ -705,6 +719,8 @@ class Flask(_PackageBoundObject):
rv = c.get('/?vodka=42')
assert request.args['vodka'] == '42'
See :class:`~flask.testing.FlaskClient` for more information.
.. versionchanged:: 0.4
added support for `with` block usage for the client.
@@ -815,9 +831,11 @@ class Flask(_PackageBoundObject):
app.view_functions['index'] = index
If a view function is provided some defaults can be specified directly
on the view function. For more information refer to
:ref:`view-func-options`.
Internally :meth:`route` invokes :meth:`add_url_rule` so if you want
to customize the behavior via subclassing you only need to change
this method.
For more information refer to :ref:`url-route-registrations`.
.. versionchanged:: 0.2
`view_func` parameter added.
@@ -876,73 +894,33 @@ class Flask(_PackageBoundObject):
def route(self, rule, **options):
"""A decorator that is used to register a view function for a
given URL rule. Example::
given URL rule. This does the same thing as :meth:`add_url_rule`
but is intended for decorator usage::
@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:
For more information refer to :ref:`url-route-registrations`.
:param rule: the URL rule as string
:param methods: a list of methods this rule should be limited
:param endpoint: the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as
endpoint
:param view_func: the function to call when serving a request to the
provided endpoint
:param options: the options to be forwarded to the underlying
:class:`~werkzeug.routing.Rule` object. A change
to Werkzeug is handling of method options. methods
is a list of methods this rule should be limited
to (`GET`, `POST` etc.). By default a rule
just listens for `GET` (and implicitly `HEAD`).
Starting with Flask 0.6, `OPTIONS` is implicitly
added and handled by the standard request handling.
:param subdomain: specifies the rule for the subdomain 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, None, f, **options)
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
@@ -1213,14 +1191,24 @@ class Flask(_PackageBoundObject):
else:
raise e
self.logger.exception('Exception on %s [%s]' % (
request.path,
request.method
))
self.log_exception((exc_type, exc_value, tb))
if handler is None:
return InternalServerError()
return handler(e)
def log_exception(self, exc_info):
"""Logs an exception. This is called by :meth:`handle_exception`
if debugging is disabled and right before the handler is called.
The default implementation logs the exception as error on the
:attr:`logger`.
.. versionadded:: 0.8
"""
self.logger.error('Exception on %s [%s]' % (
request.path,
request.method
), exc_info=exc_info)
def raise_routing_exception(self, request):
"""Exceptions that are recording during routing are reraised with
this method. During debug we are not reraising redirect requests
@@ -1302,17 +1290,18 @@ class Flask(_PackageBoundObject):
.. versionadded:: 0.7
"""
# This would be nicer in Werkzeug 0.7, which however currently
# is not released. Werkzeug 0.7 provides a method called
# allowed_methods() that returns all methods that are valid for
# a given path.
methods = []
try:
_request_ctx_stack.top.url_adapter.match(method='--')
except MethodNotAllowed, e:
methods = e.valid_methods
except HTTPException, e:
pass
adapter = _request_ctx_stack.top.url_adapter
if hasattr(adapter, 'allowed_methods'):
methods = adapter.allowed_methods()
else:
# fallback for Werkzeug < 0.7
methods = []
try:
adapter.match(method='--')
except MethodNotAllowed, e:
methods = e.valid_methods
except HTTPException, e:
pass
rv = self.response_class()
rv.allow.update(methods)
return rv
@@ -1383,7 +1372,7 @@ class Flask(_PackageBoundObject):
This also triggers the :meth:`url_value_processor` functions before
the actualy :meth:`before_request` functions are called.
"""
bp = request.blueprint
bp = _request_ctx_stack.top.request.blueprint
funcs = self.url_value_preprocessors.get(None, ())
if bp is not None and bp in self.url_value_preprocessors:
@@ -1414,8 +1403,6 @@ class Flask(_PackageBoundObject):
"""
ctx = _request_ctx_stack.top
bp = ctx.request.blueprint
if not self.session_interface.is_null_session(ctx.session):
self.save_session(ctx.session, response)
funcs = ()
if bp is not None and bp in self.after_request_funcs:
funcs = reversed(self.after_request_funcs[bp])
@@ -1423,6 +1410,8 @@ class Flask(_PackageBoundObject):
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
for handler in funcs:
response = handler(response)
if not self.session_interface.is_null_session(ctx.session):
self.save_session(ctx.session, response)
return response
def do_teardown_request(self):
@@ -1433,7 +1422,7 @@ class Flask(_PackageBoundObject):
tighter control over certain resources under testing environments.
"""
funcs = reversed(self.teardown_request_funcs.get(None, ()))
bp = request.blueprint
bp = _request_ctx_stack.top.request.blueprint
if bp is not None and bp in self.teardown_request_funcs:
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
exc = sys.exc_info()[1]
@@ -1478,19 +1467,12 @@ class Flask(_PackageBoundObject):
:func:`werkzeug.test.EnvironBuilder` for more information, this
function accepts the same arguments).
"""
from werkzeug.test import create_environ
environ_overrides = kwargs.setdefault('environ_overrides', {})
if self.config.get('SERVER_NAME'):
server_name = self.config.get('SERVER_NAME')
if ':' not in server_name:
http_host, http_port = server_name, '80'
else:
http_host, http_port = server_name.split(':', 1)
environ_overrides.setdefault('SERVER_NAME', server_name)
environ_overrides.setdefault('HTTP_HOST', server_name)
environ_overrides.setdefault('SERVER_PORT', http_port)
return self.request_context(create_environ(*args, **kwargs))
from flask.testing import make_test_environ_builder
builder = make_test_environ_builder(self, *args, **kwargs)
try:
return self.request_context(builder.get_environ())
finally:
builder.close()
def wsgi_app(self, environ, start_response):
"""The actual WSGI application. This is not implemented in
Regular → Executable
+16 -13
View File
@@ -59,7 +59,7 @@ class BlueprintSetupState(object):
self.url_defaults = dict(self.blueprint.url_values_defaults)
self.url_defaults.update(self.options.get('url_defaults', ()))
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
def add_url_rule(self, rule, endpoint = None, view_func = None, **options):
"""A helper method to register a rule (and optionally a view function)
to the application. The endpoint is automatically prefixed with the
blueprint's name.
@@ -73,7 +73,7 @@ class BlueprintSetupState(object):
if 'defaults' in options:
defaults = dict(defaults, **options.pop('defaults'))
self.app.add_url_rule(rule, '%s.%s' % (self.blueprint.name, endpoint),
view_func, defaults=defaults, **options)
view_func, defaults = defaults, **options)
class Blueprint(_PackageBoundObject):
@@ -89,9 +89,9 @@ class Blueprint(_PackageBoundObject):
warn_on_modifications = False
_got_registered_once = False
def __init__(self, name, import_name, static_folder=None,
static_url_path=None, template_folder=None,
url_prefix=None, subdomain=None, url_defaults=None):
def __init__(self, name, import_name, static_folder = None,
static_url_path = None, template_folder = None,
url_prefix = None, subdomain = None, url_defaults = None):
_PackageBoundObject.__init__(self, import_name, template_folder)
self.name = name
self.url_prefix = url_prefix
@@ -128,14 +128,14 @@ class Blueprint(_PackageBoundObject):
func(state)
return self.record(update_wrapper(wrapper, func))
def make_setup_state(self, app, options, first_registration=False):
def make_setup_state(self, app, options, first_registration = False):
"""Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
object that is later passed to the register callback functions.
Subclasses can override this to return a subclass of the setup state.
"""
return BlueprintSetupState(self, app, options, first_registration)
def register(self, app, options, first_registration=False):
def register(self, app, options, first_registration = False):
"""Called by :meth:`Flask.register_blueprint` to register a blueprint
on the application. This can be overridden to customize the register
behavior. Keyword arguments from
@@ -146,8 +146,8 @@ class Blueprint(_PackageBoundObject):
state = self.make_setup_state(app, options, first_registration)
if self.has_static_folder:
state.add_url_rule(self.static_url_path + '/<path:filename>',
view_func=self.send_static_file,
endpoint='static')
view_func = self.send_static_file,
endpoint = 'static')
for deferred in self.deferred_functions:
deferred(state)
@@ -157,14 +157,17 @@ class Blueprint(_PackageBoundObject):
:func:`url_for` function is prefixed with the name of the blueprint.
"""
def decorator(f):
self.add_url_rule(rule, f.__name__, f, **options)
endpoint = options.pop("endpoint", f.__name__)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
def add_url_rule(self, rule, endpoint = None, view_func = None, **options):
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
the :func:`url_for` function is prefixed with the name of the blueprint.
"""
if endpoint:
assert '.' not in endpoint, "Blueprint endpoint's should not contain dot's"
self.record(lambda s:
s.add_url_rule(rule, endpoint, view_func, **options))
@@ -289,8 +292,8 @@ class Blueprint(_PackageBoundObject):
def app_url_value_preprocessor(self, f):
"""Same as :meth:`url_value_preprocessor` but application wide.
"""
self.record_once(lambda s: s.app.url_value_preprocessor
.setdefault(self.name, []).append(f))
self.record_once(lambda s: s.app.url_value_preprocessors
.setdefault(None, []).append(f))
return f
def app_url_defaults(self, f):
Regular → Executable
+7 -3
View File
@@ -5,7 +5,7 @@
Implements the configuration related objects.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
@@ -21,13 +21,17 @@ from werkzeug.utils import import_string
class ConfigAttribute(object):
"""Makes an attribute forward to the config"""
def __init__(self, name):
def __init__(self, name, get_converter=None):
self.__name__ = name
self.get_converter = get_converter
def __get__(self, obj, type=None):
if obj is None:
return self
return obj.config[self.__name__]
rv = obj.config[self.__name__]
if self.get_converter is not None:
rv = self.get_converter(rv)
return rv
def __set__(self, obj, value):
obj.config[self.__name__] = value
Regular → Executable
+38 -5
View File
@@ -5,7 +5,7 @@
Implements the objects required to keep the context.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
@@ -89,9 +89,13 @@ class RequestContext(object):
self.flashes = None
self.session = None
# indicator if the context was preserved. Next time another context
# is pushed the preserved context is popped.
self.preserved = False
self.match_request()
# Support for deprecated functionality. This is doing away with
# XXX: Support for deprecated functionality. This is doing away with
# Flask 1.0
blueprint = self.request.blueprint
if blueprint is not None:
@@ -114,6 +118,18 @@ class RequestContext(object):
def push(self):
"""Binds the request context to the current context."""
# If an exception ocurrs in debug mode or if context preservation is
# activated under exception situations exactly one context stays
# on the stack. The rationale is that you want to access that
# information under debug situations. However if someone forgets to
# pop that context again we want to make sure that on the next push
# it's invalidated otherwise we run at risk that something leaks
# memory. This is usually only a problem in testsuite since this
# functionality is not active in production environments.
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop()
_request_ctx_stack.push(self)
# Open the session at the moment that the request context is
@@ -128,8 +144,15 @@ class RequestContext(object):
also trigger the execution of functions registered by the
:meth:`~flask.Flask.teardown_request` decorator.
"""
self.preserved = False
self.app.do_teardown_request()
_request_ctx_stack.pop()
rv = _request_ctx_stack.pop()
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
% (rv, self)
# get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
rv.request.environ['werkzeug.request'] = None
def __enter__(self):
self.push()
@@ -141,6 +164,16 @@ class RequestContext(object):
# access the request object in the interactive shell. Furthermore
# the context can be force kept alive for the test client.
# See flask.testing for how this works.
if not self.request.environ.get('flask._preserve_context') and \
(tb is None or not self.app.preserve_context_on_exception):
if self.request.environ.get('flask._preserve_context') or \
(tb is not None and self.app.preserve_context_on_exception):
self.preserved = True
else:
self.pop()
def __repr__(self):
return '<%s \'%s\' [%s] of %s>' % (
self.__class__.__name__,
self.request.url,
self.request.method,
self.app.name
)
Regular → Executable
+2 -1
View File
@@ -54,7 +54,8 @@ class FormDataRoutingRedirect(AssertionError):
buf.append(' Make sure to directly send your %s-request to this URL '
'since we can\'t make browsers or HTTP clients redirect '
'with form data.' % request.method)
'with form data reliably or without user interaction.' %
request.method)
buf.append('\n\nNote: this exception is only raised in debug mode')
AssertionError.__init__(self, ''.join(buf).encode('utf-8'))
+29
View File
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
"""
flask.ext
~~~~~~~~~
Redirect imports for extensions. This module basically makes it possible
for us to transition from flaskext.foo to flask_foo without having to
force all extensions to upgrade at the same time.
When a user does ``from flask.ext.foo import bar`` it will attempt to
import ``from flask_foo import bar`` first and when that fails it will
try to import ``from flaskext.foo import bar``.
We're switching from namespace packages because it was just too painful for
everybody involved.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
def setup():
from ..exthook import ExtensionImporter
importer = ExtensionImporter(['flask_%s', 'flaskext.%s'], __name__)
importer.install()
setup()
del setup
+119
View File
@@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
"""
flask.exthook
~~~~~~~~~~~~~
Redirect imports for extensions. This module basically makes it possible
for us to transition from flaskext.foo to flask_foo without having to
force all extensions to upgrade at the same time.
When a user does ``from flask.ext.foo import bar`` it will attempt to
import ``from flask_foo import bar`` first and when that fails it will
try to import ``from flaskext.foo import bar``.
We're switching from namespace packages because it was just too painful for
everybody involved.
This is used by `flask.ext`.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import sys
import os
class ExtensionImporter(object):
"""This importer redirects imports from this submodule to other locations.
This makes it possible to transition from the old flaskext.name to the
newer flask_name without people having a hard time.
"""
def __init__(self, module_choices, wrapper_module):
self.module_choices = module_choices
self.wrapper_module = wrapper_module
self.prefix = wrapper_module + '.'
self.prefix_cutoff = wrapper_module.count('.') + 1
def __eq__(self, other):
return self.__class__.__module__ == other.__class__.__module__ and \
self.__class__.__name__ == other.__class__.__name__ and \
self.wrapper_module == other.wrapper_module and \
self.module_choices == other.module_choices
def __ne__(self, other):
return not self.__eq__(other)
def install(self):
sys.meta_path[:] = [x for x in sys.meta_path if self != x] + [self]
def find_module(self, fullname, path=None):
if fullname.startswith(self.prefix):
return self
def load_module(self, fullname):
if fullname in sys.modules:
return sys.modules[fullname]
modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff]
for path in self.module_choices:
realname = path % modname
try:
__import__(realname)
except ImportError:
exc_type, exc_value, tb = sys.exc_info()
# since we only establish the entry in sys.modules at the
# very this seems to be redundant, but if recursive imports
# happen we will call into the move import a second time.
# On the second invocation we still don't have an entry for
# fullname in sys.modules, but we will end up with the same
# fake module name and that import will succeed since this
# one already has a temporary entry in the modules dict.
# Since this one "succeeded" temporarily that second
# invocation now will have created a fullname entry in
# sys.modules which we have to kill.
sys.modules.pop(fullname, None)
# If it's an important traceback we reraise it, otherwise
# we swallow it and try the next choice. The skipped frame
# is the one from __import__ above which we don't care about
if self.is_important_traceback(realname, tb):
raise exc_type, exc_value, tb.tb_next
continue
module = sys.modules[fullname] = sys.modules[realname]
if '.' not in modname:
setattr(sys.modules[self.wrapper_module], modname, module)
return module
raise ImportError('No module named %s' % fullname)
def is_important_traceback(self, important_module, tb):
"""Walks a traceback's frames and checks if any of the frames
originated in the given important module. If that is the case then we
were able to import the module itself but apparently something went
wrong when the module was imported. (Eg: import of an import failed).
"""
while tb is not None:
if self.is_important_frame(important_module, tb):
return True
tb = tb.tb_next
return False
def is_important_frame(self, important_module, tb):
"""Checks a single frame if it's important."""
g = tb.tb_frame.f_globals
if '__name__' not in g:
return False
module_name = g['__name__']
# Python 2.7 Behavior. Modules are cleaned up late so the
# name shows up properly here. Success!
if module_name == important_module:
return True
# Some python verisons will will clean up modules so early that the
# module name at that point is no longer set. Try guessing from
# the filename then.
filename = os.path.abspath(tb.tb_frame.f_code.co_filename)
test_string = os.path.sep + important_module.replace('.', os.path.sep)
return test_string + '.py' in filename or \
test_string + os.path.sep + '__init__.py' in filename
Regular → Executable
+2 -1
View File
@@ -6,7 +6,7 @@
Defines all the global objects that are proxies to the current
active context.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
@@ -19,6 +19,7 @@ def _lookup_object(name):
raise RuntimeError('working outside of request context')
return getattr(top, name)
# context locals
_request_ctx_stack = LocalStack()
current_app = LocalProxy(partial(_lookup_object, 'app'))
Regular → Executable
+25 -2
View File
@@ -5,7 +5,7 @@
Implements various helpers.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
@@ -18,6 +18,7 @@ import mimetypes
from time import time
from zlib import adler32
from threading import RLock
from werkzeug.urls import url_quote
# try to load the best simplejson implementation available. If JSON
# is not installed, we add a failing class.
@@ -55,6 +56,7 @@ def _assert_have_json():
if not json_available:
raise RuntimeError('simplejson not installed')
# figure out if simplejson escapes slashes. This behaviour was changed
# from one version to another without reason.
if not json_available or '\\/' not in json.dumps('/'):
@@ -145,6 +147,13 @@ def make_response(*args):
response = make_response(render_template('not_found.html'), 404)
The other use case of this function is to force the return value of a
view function into a response which is helpful with view
decorators::
response = make_response(view_function())
response.headers['X-Parachutes'] = 'parachutes are cool'
Internally this function does the following things:
- if no arguments are passed, it creates a new response argument
@@ -177,9 +186,14 @@ def url_for(endpoint, **values):
For more information, head over to the :ref:`Quickstart <url-building>`.
.. versionadded:: 0.9
The `_anchor` and `_method` parameters were added.
:param endpoint: the endpoint of the URL (name of the function)
:param values: the variable arguments of the URL rule
:param _external: if set to `True`, an absolute URL is generated.
:param _anchor: if provided this is added as anchor to the URL.
:param _method: if provided this explicitly specifies an HTTP method.
"""
ctx = _request_ctx_stack.top
blueprint_name = request.blueprint
@@ -197,8 +211,14 @@ def url_for(endpoint, **values):
elif endpoint.startswith('.'):
endpoint = endpoint[1:]
external = values.pop('_external', False)
anchor = values.pop('_anchor', None)
method = values.pop('_method', None)
ctx.app.inject_url_defaults(endpoint, values)
return ctx.url_adapter.build(endpoint, values, force_external=external)
rv = ctx.url_adapter.build(endpoint, values, method=method,
force_external=external)
if anchor is not None:
rv += '#' + url_quote(anchor)
return rv
def get_template_attribute(template_name, attribute):
@@ -477,6 +497,8 @@ def get_root_path(import_name):
directory = os.path.dirname(sys.modules[import_name].__file__)
return os.path.abspath(directory)
except AttributeError:
# this is necessary in case we are running from the interactive
# python shell. It will never be used for production code however
return os.getcwd()
@@ -492,6 +514,7 @@ def find_package(import_name):
root_mod = sys.modules[import_name.split('.')[0]]
package_path = getattr(root_mod, '__file__', None)
if package_path is None:
# support for the interactive python shell
package_path = os.getcwd()
else:
package_path = os.path.abspath(os.path.dirname(package_path))
Regular → Executable
+4 -2
View File
@@ -5,7 +5,7 @@
Implements the logging support for Flask.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
@@ -25,7 +25,9 @@ def create_logger(app):
class DebugLogger(Logger):
def getEffectiveLevel(x):
return DEBUG if app.debug else Logger.getEffectiveLevel(x)
if x.level == 0 and app.debug:
return DEBUG
return Logger.getEffectiveLevel(x)
class DebugHandler(StreamHandler):
def emit(x, record):
Regular → Executable
+1 -1
View File
@@ -5,7 +5,7 @@
Implements a class that represents module blueprints.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
Regular → Executable
+2 -2
View File
@@ -6,14 +6,14 @@
This module used to flask with the session global so we moved it
over to flask.sessions
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from warnings import warn
warn(DeprecationWarning('please use flask.sessions instead'))
from .sessions import *
from .sessions import SecureCookieSession, NullSession
Session = SecureCookieSession
_NullSession = NullSession
Regular → Executable
+32 -4
View File
@@ -6,7 +6,7 @@
Implements cookie based sessions based on Werkzeug's secure cookie
system.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
@@ -123,10 +123,34 @@ class SessionInterface(object):
"""Helpful helper method that returns the cookie domain that should
be used for the session cookie if session cookies are used.
"""
if app.config['SESSION_COOKIE_DOMAIN'] is not None:
return app.config['SESSION_COOKIE_DOMAIN']
if app.config['SERVER_NAME'] is not None:
# chop of the port which is usually not supported by browsers
return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0]
def get_cookie_path(self, app):
"""Returns the path for which the cookie should be valid. The
default implementation uses the value from the SESSION_COOKIE_PATH``
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
uses ``/`` if it's `None`.
"""
return app.config['SESSION_COOKIE_PATH'] or \
app.config['APPLICATION_ROOT'] or '/'
def get_cookie_httponly(self, app):
"""Returns True if the session cookie should be httponly. This
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
config var.
"""
return app.config['SESSION_COOKIE_HTTPONLY']
def get_cookie_secure(self, app):
"""Returns True if the cookie should be secure. This currently
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
"""
return app.config['SESSION_COOKIE_SECURE']
def get_expiration_time(self, app, session):
"""A helper method that returns an expiration date for the session
or `None` if the session is linked to the browser session. The
@@ -169,9 +193,13 @@ class SecureCookieSessionInterface(SessionInterface):
def save_session(self, app, session, response):
expires = self.get_expiration_time(app, session)
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
if session.modified and not session:
response.delete_cookie(app.session_cookie_name,
response.delete_cookie(app.session_cookie_name, path=path,
domain=domain)
else:
session.save_cookie(response, app.session_cookie_name,
expires=expires, httponly=True, domain=domain)
session.save_cookie(response, app.session_cookie_name, path=path,
expires=expires, httponly=httponly,
secure=secure, domain=domain)
Regular → Executable
+2 -2
View File
@@ -6,7 +6,7 @@
Implements signals based on blinker if available, otherwise
falls silently back to a noop
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
signals_available = False
@@ -34,7 +34,7 @@ except ImportError:
'not installed.')
send = lambda *a, **kw: None
connect = disconnect = has_receivers_for = receivers_for = \
temporarily_connected_to = _fail
temporarily_connected_to = connected_to = _fail
del _fail
# the namespace for code signals. If you are not flask code, do
Regular → Executable
+1 -1
View File
@@ -5,7 +5,7 @@
Implements the bridge to Jinja2.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
import posixpath
Regular → Executable
+84 -33
View File
@@ -6,62 +6,113 @@
Implements test support helpers. This module is lazily imported
and usually not used in production environments.
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from __future__ import with_statement
from contextlib import contextmanager
from werkzeug.test import Client, EnvironBuilder
from flask import _request_ctx_stack
def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
"""Creates a new test builder with some application defaults thrown in."""
http_host = app.config.get('SERVER_NAME')
app_root = app.config.get('APPLICATION_ROOT')
if base_url is None:
base_url = 'http://%s/' % (http_host or 'localhost')
if app_root:
base_url += app_root.lstrip('/')
return EnvironBuilder(path, base_url, *args, **kwargs)
class FlaskClient(Client):
"""Works like a regular Werkzeug test client but has some
knowledge about how Flask works to defer the cleanup of the
request context stack to the end of a with body when used
in a with statement.
"""Works like a regular Werkzeug test client but has some knowledge about
how Flask works to defer the cleanup of the request context stack to the
end of a with body when used in a with statement. For general information
about how to use this class refer to :class:`werkzeug.test.Client`.
Basic usage is outlined in the :ref:`testing` chapter.
"""
preserve_context = context_preserved = False
preserve_context = False
@contextmanager
def session_transaction(self, *args, **kwargs):
"""When used in combination with a with statement this opens a
session transaction. This can be used to modify the session that
the test client uses. Once the with block is left the session is
stored back.
with client.session_transaction() as session:
session['value'] = 42
Internally this is implemented by going through a temporary test
request context and since session handling could depend on
request variables this function accepts the same arguments as
:meth:`~flask.Flask.test_request_context` which are directly
passed through.
"""
if self.cookie_jar is None:
raise RuntimeError('Session transactions only make sense '
'with cookies enabled.')
app = self.application
environ_overrides = kwargs.setdefault('environ_overrides', {})
self.cookie_jar.inject_wsgi(environ_overrides)
outer_reqctx = _request_ctx_stack.top
with app.test_request_context(*args, **kwargs) as c:
sess = app.open_session(c.request)
if sess is None:
raise RuntimeError('Session backend did not open a session. '
'Check the configuration')
# Since we have to open a new request context for the session
# handling we want to make sure that we hide out own context
# from the caller. By pushing the original request context
# (or None) on top of this and popping it we get exactly that
# behavior. It's important to not use the push and pop
# methods of the actual request context object since that would
# mean that cleanup handlers are called
_request_ctx_stack.push(outer_reqctx)
try:
yield sess
finally:
_request_ctx_stack.pop()
resp = app.response_class()
if not app.session_interface.is_null_session(sess):
app.save_session(sess, resp)
headers = resp.get_wsgi_headers(c.request.environ)
self.cookie_jar.extract_wsgi(c.request.environ, headers)
def open(self, *args, **kwargs):
if self.context_preserved:
_request_ctx_stack.pop()
self.context_preserved = False
kwargs.setdefault('environ_overrides', {}) \
['flask._preserve_context'] = self.preserve_context
as_tuple = kwargs.pop('as_tuple', False)
buffered = kwargs.pop('buffered', False)
follow_redirects = kwargs.pop('follow_redirects', False)
builder = make_test_environ_builder(self.application, *args, **kwargs)
builder = EnvironBuilder(*args, **kwargs)
if self.application.config.get('SERVER_NAME'):
server_name = self.application.config.get('SERVER_NAME')
if ':' not in server_name:
http_host, http_port = server_name, None
else:
http_host, http_port = server_name.split(':', 1)
if builder.base_url == 'http://localhost/':
# Default Generated Base URL
if http_port != None:
builder.host = http_host + ':' + http_port
else:
builder.host = http_host
old = _request_ctx_stack.top
try:
return Client.open(self, builder,
as_tuple=as_tuple,
buffered=buffered,
follow_redirects=follow_redirects)
finally:
self.context_preserved = _request_ctx_stack.top is not old
return Client.open(self, builder,
as_tuple=as_tuple,
buffered=buffered,
follow_redirects=follow_redirects)
def __enter__(self):
if self.preserve_context:
raise RuntimeError('Cannot nest client invocations')
self.preserve_context = True
return self
def __exit__(self, exc_type, exc_value, tb):
self.preserve_context = False
if self.context_preserved:
_request_ctx_stack.pop()
# on exit we want to clean up earlier. Normally the request context
# stays preserved until the next request in the same thread comes
# in. See RequestGlobals.push() for the general behavior.
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop()
Regular → Executable
+41 -3
View File
@@ -15,7 +15,6 @@ http_method_funcs = frozenset(['get', 'post', 'head', 'options',
'delete', 'put', 'trace'])
class View(object):
"""Alternative way to use view functions. A subclass has to implement
:meth:`dispatch_request` which is called with the view arguments from
@@ -30,13 +29,41 @@ class View(object):
return 'Hello %s!' % name
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
When you want to decorate a pluggable view you will have to either do that
when the view function is created (by wrapping the return value of
:meth:`as_view`) or you can use the :attr:`decorators` attribute::
class SecretView(View):
methods = ['GET']
decorators = [superuser_required]
def dispatch_request(self):
...
The decorators stored in the decorators list are applied one after another
when the view function is created. Note that you can *not* use the class
based decorators since those would decorate the view class and not the
generated view function!
"""
#: A for which methods this pluggable view can handle.
methods = None
#: The canonical way to decorate class based views is to decorate the
#: return value of as_view(). However since this moves parts of the
#: logic from the class declaration to the place where it's hooked
#: into the routing system.
#:
#: You can place one or more decorators in this list and whenever the
#: view function is created the result is automatically decorated.
#:
#: .. versionadded:: 0.8
decorators = []
def dispatch_request(self):
"""Subclasses have to override this method to implement the
actual view functionc ode. This method is called with all
actual view function code. This method is called with all
the arguments from the URL rule.
"""
raise NotImplementedError()
@@ -54,6 +81,13 @@ class View(object):
def view(*args, **kwargs):
self = view.view_class(*class_args, **class_kwargs)
return self.dispatch_request(*args, **kwargs)
if cls.decorators:
view.__name__ = name
view.__module__ = cls.__module__
for decorator in cls.decorators:
view = decorator(view)
# we attach the view class to the view function for two reasons:
# first of all it allows us to easily figure out what class based
# view this thing came from, secondly it's also used for instanciating
@@ -108,5 +142,9 @@ class MethodView(View):
def dispatch_request(self, *args, **kwargs):
meth = getattr(self, request.method.lower(), None)
assert meth is not None, 'Not implemented method'
# if the request method is HEAD and we don't have a handler for it
# retry with GET
if meth is None and request.method == 'HEAD':
meth = getattr(self, 'get', None)
assert meth is not None, 'Not implemented method %r' % request.method
return meth(*args, **kwargs)
Regular → Executable
+1 -1
View File
@@ -5,7 +5,7 @@
Implements the WSGI wrappers (request and response).
:copyright: (c) 2010 by Armin Ronacher.
:copyright: (c) 2011 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
Regular → Executable
View File
Regular → Executable
View File
View File
View File
View File
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+1 -1
View File
@@ -77,7 +77,7 @@ def make_frame_proxy(frame):
class ProcessedTraceback(object):
"""Holds a Jinja preprocessed traceback for priting or reraising."""
"""Holds a Jinja preprocessed traceback for printing or reraising."""
def __init__(self, exc_type, exc_value, frames):
assert frames, 'no frames for this traceback?'
Regular → Executable
View File
Regular → Executable
+5 -5
View File
@@ -67,7 +67,7 @@ def copy_cache(cache):
def load_extensions(environment, extensions):
"""Load the extensions from the list and bind it to the environment.
Returns a dict of instanciated environments.
Returns a dict of instantiated environments.
"""
result = {}
for extension in extensions:
@@ -239,7 +239,7 @@ class Environment(object):
# passed by keyword rather than position. However it's important to
# not change the order of arguments because it's used at least
# internally in those cases:
# - spontaneus environments (i18n extension and Template)
# - spontaneous environments (i18n extension and Template)
# - unittests
# If parameter changes are required only add parameters at the end
# and don't change the arguments (or the defaults!) of the arguments
@@ -435,7 +435,7 @@ class Environment(object):
return stream
def _generate(self, source, name, filename, defer_init=False):
"""Internal hook that can be overriden to hook a different generate
"""Internal hook that can be overridden to hook a different generate
method in.
.. versionadded:: 2.5
@@ -443,7 +443,7 @@ class Environment(object):
return generate(source, self, name, filename, defer_init=defer_init)
def _compile(self, source, filename):
"""Internal hook that can be overriden to hook a different compile
"""Internal hook that can be overridden to hook a different compile
method in.
.. versionadded:: 2.5
@@ -1053,7 +1053,7 @@ class TemplateStream(object):
def dump(self, fp, encoding=None, errors='strict'):
"""Dump the complete stream into a file or file-like object.
Per default unicode strings are written, if you want to encode
before writing specifiy an `encoding`.
before writing specify an `encoding`.
Example usage::
Regular → Executable
View File
Regular → Executable
+9 -1
View File
@@ -552,6 +552,10 @@ def babel_extract(fileobj, keywords, comment_tags, options):
The `newstyle_gettext` flag can be set to `True` to enable newstyle
gettext calls.
.. versionchanged:: 2.7
A `silent` option can now be provided. If set to `False` template
syntax errors are propagated instead of being ignored.
:param fileobj: the file-like object the messages should be extracted from
:param keywords: a list of keywords (i.e. function names) that should be
recognized as translation functions
@@ -571,8 +575,10 @@ def babel_extract(fileobj, keywords, comment_tags, options):
extensions.add(InternationalizationExtension)
def getbool(options, key, default=False):
options.get(key, str(default)).lower() in ('1', 'on', 'yes', 'true')
return options.get(key, str(default)).lower() in \
('1', 'on', 'yes', 'true')
silent = getbool(options, 'silent', True)
environment = Environment(
options.get('block_start_string', BLOCK_START_STRING),
options.get('block_end_string', BLOCK_END_STRING),
@@ -596,6 +602,8 @@ def babel_extract(fileobj, keywords, comment_tags, options):
node = environment.parse(source)
tokens = list(environment.lex(environment.preprocess(source)))
except TemplateSyntaxError, e:
if not silent:
raise
# skip templates with syntax errors
return
Regular → Executable
+14 -14
View File
@@ -15,7 +15,7 @@ from operator import itemgetter
from itertools import imap, groupby
from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
from jinja2.runtime import Undefined
from jinja2.exceptions import FilterArgumentError, SecurityError
from jinja2.exceptions import FilterArgumentError
_word_re = re.compile(r'\w+(?u)')
@@ -346,25 +346,25 @@ def do_filesizeformat(value, binary=False):
bytes = float(value)
base = binary and 1024 or 1000
prefixes = [
(binary and "KiB" or "kB"),
(binary and "MiB" or "MB"),
(binary and "GiB" or "GB"),
(binary and "TiB" or "TB"),
(binary and "PiB" or "PB"),
(binary and "EiB" or "EB"),
(binary and "ZiB" or "ZB"),
(binary and "YiB" or "YB")
(binary and 'KiB' or 'kB'),
(binary and 'MiB' or 'MB'),
(binary and 'GiB' or 'GB'),
(binary and 'TiB' or 'TB'),
(binary and 'PiB' or 'PB'),
(binary and 'EiB' or 'EB'),
(binary and 'ZiB' or 'ZB'),
(binary and 'YiB' or 'YB')
]
if bytes == 1:
return "1 Byte"
return '1 Byte'
elif bytes < base:
return "%d Bytes" % bytes
return '%d Bytes' % bytes
else:
for i, prefix in enumerate(prefixes):
unit = base * base ** (i + 1)
unit = base ** (i + 2)
if bytes < unit:
return "%.1f %s" % ((bytes / unit), prefix)
return "%.1f %s" % ((bytes / unit), prefix)
return '%.1f %s' % ((base * bytes / unit), prefix)
return '%.1f %s' % ((base * bytes / unit), prefix)
def do_pprint(value, verbose=False):
Regular → Executable
+4 -4
View File
@@ -414,7 +414,7 @@ class Lexer(object):
(operator_re, TOKEN_OPERATOR, None)
]
# assamble the root lexing rule. because "|" is ungreedy
# assemble the root lexing rule. because "|" is ungreedy
# we have to sort by length so that the lexer continues working
# as expected when we have parsing rules like <% for block and
# <%= for variables. (if someone wants asp like syntax)
@@ -491,7 +491,7 @@ class Lexer(object):
}
def _normalize_newlines(self, value):
"""Called for strings and template data to normlize it to unicode."""
"""Called for strings and template data to normalize it to unicode."""
return newline_re.sub(self.newline_sequence, value)
def tokenize(self, source, name=None, filename=None, state=None):
@@ -571,7 +571,7 @@ class Lexer(object):
if m is None:
continue
# we only match blocks and variables if brances / parentheses
# we only match blocks and variables if braces / parentheses
# are balanced. continue parsing with the lower rule which
# is the operator rule. do this only if the end tags look
# like operators
@@ -669,7 +669,7 @@ class Lexer(object):
# publish new function and start again
pos = pos2
break
# if loop terminated without break we havn't found a single match
# if loop terminated without break we haven't found a single match
# either we are at the end of the file or we have a problem
else:
# end of text
Regular → Executable
+24 -1
View File
@@ -330,12 +330,16 @@ class PrefixLoader(BaseLoader):
self.mapping = mapping
self.delimiter = delimiter
def get_source(self, environment, template):
def get_loader(self, template):
try:
prefix, name = template.split(self.delimiter, 1)
loader = self.mapping[prefix]
except (ValueError, KeyError):
raise TemplateNotFound(template)
return loader, name
def get_source(self, environment, template):
loader, name = self.get_loader(template)
try:
return loader.get_source(environment, name)
except TemplateNotFound:
@@ -343,6 +347,16 @@ class PrefixLoader(BaseLoader):
# (the one that includes the prefix)
raise TemplateNotFound(template)
@internalcode
def load(self, environment, name, globals=None):
loader, local_name = self.get_loader(name)
try:
return loader.load(environment, local_name)
except TemplateNotFound:
# re-raise the exception with the correct fileame here.
# (the one that includes the prefix)
raise TemplateNotFound(name)
def list_templates(self):
result = []
for prefix, loader in self.mapping.iteritems():
@@ -376,6 +390,15 @@ class ChoiceLoader(BaseLoader):
pass
raise TemplateNotFound(template)
@internalcode
def load(self, environment, name, globals=None):
for loader in self.loaders:
try:
return loader.load(environment, name, globals)
except TemplateNotFound:
pass
raise TemplateNotFound(name)
def list_templates(self):
found = set()
for loader in self.loaders:
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+1 -2
View File
@@ -223,7 +223,7 @@ class Parser(object):
# raise a nicer error message in that case.
if self.stream.current.type == 'sub':
self.fail('Block names in Jinja have to be valid Python '
'identifiers and may not contain hypens, use an '
'identifiers and may not contain hyphens, use an '
'underscore instead.')
node.body = self.parse_statements(('name:endblock',), drop_needle=True)
@@ -698,7 +698,6 @@ class Parser(object):
arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
if token.type == 'lbracket':
priority_on_attribute = False
args = []
while self.stream.current.type != 'rbracket':
if args:
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
-95
View File
@@ -1,95 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite
~~~~~~~~~~~~~~~~
All the unittests of Jinja2. These tests can be executed by
either running run-tests.py using multiple Python versions at
the same time.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import os
import re
import sys
import unittest
from traceback import format_exception
from jinja2 import loaders
here = os.path.dirname(os.path.abspath(__file__))
dict_loader = loaders.DictLoader({
'justdict.html': 'FOO'
})
package_loader = loaders.PackageLoader('jinja2.testsuite.res', 'templates')
filesystem_loader = loaders.FileSystemLoader(here + '/res/templates')
function_loader = loaders.FunctionLoader({'justfunction.html': 'FOO'}.get)
choice_loader = loaders.ChoiceLoader([dict_loader, package_loader])
prefix_loader = loaders.PrefixLoader({
'a': filesystem_loader,
'b': dict_loader
})
class JinjaTestCase(unittest.TestCase):
### use only these methods for testing. If you need standard
### unittest method, wrap them!
def setup(self):
pass
def teardown(self):
pass
def setUp(self):
self.setup()
def tearDown(self):
self.teardown()
def assert_equal(self, a, b):
return self.assertEqual(a, b)
def assert_raises(self, *args, **kwargs):
return self.assertRaises(*args, **kwargs)
def assert_traceback_matches(self, callback, expected_tb):
try:
callback()
except Exception, e:
tb = format_exception(*sys.exc_info())
if re.search(expected_tb.strip(), ''.join(tb)) is None:
raise self.fail('Traceback did not match:\n\n%s\nexpected:\n%s'
% (''.join(tb), expected_tb))
else:
self.fail('Expected exception')
def suite():
from jinja2.testsuite import ext, filters, tests, core_tags, \
loader, inheritance, imports, lexnparse, security, api, \
regression, debug, utils, doctests
suite = unittest.TestSuite()
suite.addTest(ext.suite())
suite.addTest(filters.suite())
suite.addTest(tests.suite())
suite.addTest(core_tags.suite())
suite.addTest(loader.suite())
suite.addTest(inheritance.suite())
suite.addTest(imports.suite())
suite.addTest(lexnparse.suite())
suite.addTest(security.suite())
suite.addTest(api.suite())
suite.addTest(regression.suite())
suite.addTest(debug.suite())
suite.addTest(utils.suite())
# doctests will not run on python 3 currently. Too many issues
# with that, do not test that on that platform.
if sys.version_info < (3, 0):
suite.addTest(doctests.suite())
return suite
-245
View File
@@ -1,245 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.api
~~~~~~~~~~~~~~~~~~~~
Tests the public API and related stuff.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Environment, Undefined, DebugUndefined, \
StrictUndefined, UndefinedError, meta, \
is_undefined, Template, DictLoader
from jinja2.utils import Cycler
env = Environment()
class ExtendedAPITestCase(JinjaTestCase):
def test_item_and_attribute(self):
from jinja2.sandbox import SandboxedEnvironment
for env in Environment(), SandboxedEnvironment():
# the |list is necessary for python3
tmpl = env.from_string('{{ foo.items()|list }}')
assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
tmpl = env.from_string('{{ foo|attr("items")()|list }}')
assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
tmpl = env.from_string('{{ foo["items"] }}')
assert tmpl.render(foo={'items': 42}) == '42'
def test_finalizer(self):
def finalize_none_empty(value):
if value is None:
value = u''
return value
env = Environment(finalize=finalize_none_empty)
tmpl = env.from_string('{% for item in seq %}|{{ item }}{% endfor %}')
assert tmpl.render(seq=(None, 1, "foo")) == '||1|foo'
tmpl = env.from_string('<{{ none }}>')
assert tmpl.render() == '<>'
def test_cycler(self):
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):
expr = env.compile_expression("foo")
assert expr() is None
assert expr(foo=42) == 42
expr2 = env.compile_expression("foo", undefined_to_none=False)
assert is_undefined(expr2())
expr = env.compile_expression("42 + foo")
assert expr(foo=42) == 84
def test_template_passthrough(self):
t = Template('Content')
assert env.get_template(t) is t
assert env.select_template([t]) is t
assert env.get_or_select_template([t]) is t
assert env.get_or_select_template(t) is t
def test_autoescape_autoselect(self):
def select_autoescape(name):
if name is None or '.' not in name:
return False
return name.endswith('.html')
env = Environment(autoescape=select_autoescape,
loader=DictLoader({
'test.txt': '{{ foo }}',
'test.html': '{{ foo }}'
}))
t = env.get_template('test.txt')
assert t.render(foo='<foo>') == '<foo>'
t = env.get_template('test.html')
assert t.render(foo='<foo>') == '&lt;foo&gt;'
t = env.from_string('{{ foo }}')
assert t.render(foo='<foo>') == '<foo>'
class MetaTestCase(JinjaTestCase):
def test_find_undeclared_variables(self):
ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
x = meta.find_undeclared_variables(ast)
assert x == set(['bar'])
ast = env.parse('{% set foo = 42 %}{{ bar + foo }}'
'{% macro meh(x) %}{{ x }}{% endmacro %}'
'{% for item in seq %}{{ muh(item) + meh(seq) }}{% endfor %}')
x = meta.find_undeclared_variables(ast)
assert x == set(['bar', 'seq', 'muh'])
def test_find_refererenced_templates(self):
ast = env.parse('{% extends "layout.html" %}{% include helper %}')
i = meta.find_referenced_templates(ast)
assert i.next() == 'layout.html'
assert i.next() is None
assert list(i) == []
ast = env.parse('{% extends "layout.html" %}'
'{% from "test.html" import a, b as c %}'
'{% import "meh.html" as meh %}'
'{% include "muh.html" %}')
i = meta.find_referenced_templates(ast)
assert list(i) == ['layout.html', 'test.html', 'meh.html', 'muh.html']
def test_find_included_templates(self):
ast = env.parse('{% include ["foo.html", "bar.html"] %}')
i = meta.find_referenced_templates(ast)
assert list(i) == ['foo.html', 'bar.html']
ast = env.parse('{% include ("foo.html", "bar.html") %}')
i = meta.find_referenced_templates(ast)
assert list(i) == ['foo.html', 'bar.html']
ast = env.parse('{% include ["foo.html", "bar.html", foo] %}')
i = meta.find_referenced_templates(ast)
assert list(i) == ['foo.html', 'bar.html', None]
ast = env.parse('{% include ("foo.html", "bar.html", foo) %}')
i = meta.find_referenced_templates(ast)
assert list(i) == ['foo.html', 'bar.html', None]
class StreamingTestCase(JinjaTestCase):
def test_basic_streaming(self):
tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
"}} - {{ item }}</li>{%- endfor %}</ul>")
stream = tmpl.stream(seq=range(4))
self.assert_equal(stream.next(), '<ul>')
self.assert_equal(stream.next(), '<li>1 - 0</li>')
self.assert_equal(stream.next(), '<li>2 - 1</li>')
self.assert_equal(stream.next(), '<li>3 - 2</li>')
self.assert_equal(stream.next(), '<li>4 - 3</li>')
self.assert_equal(stream.next(), '</ul>')
def test_buffered_streaming(self):
tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
"}} - {{ item }}</li>{%- endfor %}</ul>")
stream = tmpl.stream(seq=range(4))
stream.enable_buffering(size=3)
self.assert_equal(stream.next(), u'<ul><li>1 - 0</li><li>2 - 1</li>')
self.assert_equal(stream.next(), u'<li>3 - 2</li><li>4 - 3</li></ul>')
def test_streaming_behavior(self):
tmpl = env.from_string("")
stream = tmpl.stream()
assert not stream.buffered
stream.enable_buffering(20)
assert stream.buffered
stream.disable_buffering()
assert not stream.buffered
class UndefinedTestCase(JinjaTestCase):
def test_stopiteration_is_undefined(self):
def test():
raise StopIteration()
t = Template('A{{ test() }}B')
assert t.render(test=test) == 'AB'
t = Template('A{{ test().missingattribute }}B')
self.assert_raises(UndefinedError, t.render, test=test)
def test_undefined_and_special_attributes(self):
try:
Undefined('Foo').__dict__
except AttributeError:
pass
else:
assert False, "Expected actual attribute error"
def test_default_undefined(self):
env = Environment(undefined=Undefined)
self.assert_equal(env.from_string('{{ missing }}').render(), u'')
self.assert_raises(UndefinedError,
env.from_string('{{ missing.attribute }}').render)
self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42), '')
self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
def test_debug_undefined(self):
env = Environment(undefined=DebugUndefined)
self.assert_equal(env.from_string('{{ missing }}').render(), '{{ missing }}')
self.assert_raises(UndefinedError,
env.from_string('{{ missing.attribute }}').render)
self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42),
u"{{ no such element: int object['missing'] }}")
self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
def test_strict_undefined(self):
env = Environment(undefined=StrictUndefined)
self.assert_raises(UndefinedError, env.from_string('{{ missing }}').render)
self.assert_raises(UndefinedError, env.from_string('{{ missing.attribute }}').render)
self.assert_raises(UndefinedError, env.from_string('{{ missing|list }}').render)
self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
self.assert_raises(UndefinedError, env.from_string('{{ foo.missing }}').render, foo=42)
self.assert_raises(UndefinedError, env.from_string('{{ not missing }}').render)
def test_indexing_gives_undefined(self):
t = Template("{{ var[42].foo }}")
self.assert_raises(UndefinedError, t.render, var=0)
def test_none_gives_proper_error(self):
try:
Environment().getattr(None, 'split')()
except UndefinedError, e:
assert e.message == "'None' has no attribute 'split'"
else:
assert False, 'expected exception'
def test_object_repr(self):
try:
Undefined(obj=42, name='upper')()
except UndefinedError, e:
assert e.message == "'int object' has no attribute 'upper'"
else:
assert False, 'expected exception'
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ExtendedAPITestCase))
suite.addTest(unittest.makeSuite(MetaTestCase))
suite.addTest(unittest.makeSuite(StreamingTestCase))
suite.addTest(unittest.makeSuite(UndefinedTestCase))
return suite
-285
View File
@@ -1,285 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.core_tags
~~~~~~~~~~~~~~~~~~~~~~~~~~
Test the core tags like for and if.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Environment, TemplateSyntaxError, UndefinedError, \
DictLoader
env = Environment()
class ForLoopTestCase(JinjaTestCase):
def test_simple(self):
tmpl = env.from_string('{% for item in seq %}{{ item }}{% endfor %}')
assert tmpl.render(seq=range(10)) == '0123456789'
def test_else(self):
tmpl = env.from_string('{% for item in seq %}XXX{% else %}...{% endfor %}')
assert tmpl.render() == '...'
def test_empty_blocks(self):
tmpl = env.from_string('<{% for item in seq %}{% else %}{% endfor %}>')
assert tmpl.render() == '<>'
def test_context_vars(self):
tmpl = env.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=[0, 1]).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):
tmpl = env.from_string('''{% for item in seq %}{{
loop.cycle('<1>', '<2>') }}{% endfor %}{%
for item in seq %}{{ loop.cycle(*through) }}{% endfor %}''')
output = tmpl.render(seq=range(4), through=('<1>', '<2>'))
assert output == '<1><2>' * 4
def test_scope(self):
tmpl = env.from_string('{% for item in seq %}{% endfor %}{{ item }}')
output = tmpl.render(seq=range(10))
assert not output
def test_varlen(self):
def inner():
for item in range(5):
yield item
tmpl = env.from_string('{% for item in iter %}{{ item }}{% endfor %}')
output = tmpl.render(iter=inner())
assert output == '01234'
def test_noniter(self):
tmpl = env.from_string('{% for item in none %}...{% endfor %}')
self.assert_raises(TypeError, tmpl.render)
def test_recursive(self):
tmpl = env.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_looploop(self):
tmpl = env.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):
tmpl = env.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):
tmpl = env.from_string('''{% for item in [1] if loop.index
== 0 %}...{% endfor %}''')
self.assert_raises(UndefinedError, tmpl.render)
tmpl = env.from_string('''{% for item in [] %}...{% else
%}{{ loop }}{% endfor %}''')
assert tmpl.render() == ''
def test_loop_filter(self):
tmpl = env.from_string('{% for item in range(10) if item '
'is even %}[{{ item }}]{% endfor %}')
assert tmpl.render() == '[0][2][4][6][8]'
tmpl = env.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_loop_unassignable(self):
self.assert_raises(TemplateSyntaxError, env.from_string,
'{% for loop in seq %}...{% endfor %}')
def test_scoped_special_var(self):
t = env.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):
t = env.from_string('{% for x in seq %}{{ loop.first }}'
'{% for y in seq %}{% endfor %}{% endfor %}')
assert t.render(seq='ab') == 'TrueFalse'
t = env.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):
t = env.from_string('''
{%- for item in foo recursive -%}{%- endfor -%}
''')
assert t.render(dict(foo=[])) == ''
def test_call_in_loop(self):
t = env.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):
t = env.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):
tmpl = env.from_string('{% for a, b, c in [[1, 2, 3]] %}'
'{{ a }}|{{ b }}|{{ c }}{% endfor %}')
assert tmpl.render() == '1|2|3'
class IfConditionTestCase(JinjaTestCase):
def test_simple(self):
tmpl = env.from_string('''{% if true %}...{% endif %}''')
assert tmpl.render() == '...'
def test_elif(self):
tmpl = env.from_string('''{% if false %}XXX{% elif true
%}...{% else %}XXX{% endif %}''')
assert tmpl.render() == '...'
def test_else(self):
tmpl = env.from_string('{% if false %}XXX{% else %}...{% endif %}')
assert tmpl.render() == '...'
def test_empty(self):
tmpl = env.from_string('[{% if true %}{% else %}{% endif %}]')
assert tmpl.render() == '[]'
def test_complete(self):
tmpl = env.from_string('{% if a %}A{% elif b %}B{% elif c == d %}'
'C{% else %}D{% endif %}')
assert tmpl.render(a=0, b=False, c=42, d=42.0) == 'C'
def test_no_scope(self):
tmpl = env.from_string('{% if a %}{% set foo = 1 %}{% endif %}{{ foo }}')
assert tmpl.render(a=True) == '1'
tmpl = env.from_string('{% if true %}{% set foo = 1 %}{% endif %}{{ foo }}')
assert tmpl.render() == '1'
class MacrosTestCase(JinjaTestCase):
env = Environment(trim_blocks=True)
def test_simple(self):
tmpl = self.env.from_string('''\
{% macro say_hello(name) %}Hello {{ name }}!{% endmacro %}
{{ say_hello('Peter') }}''')
assert tmpl.render() == 'Hello Peter!'
def test_scoping(self):
tmpl = self.env.from_string('''\
{% macro level1(data1) %}
{% macro level2(data2) %}{{ data1 }}|{{ data2 }}{% endmacro %}
{{ level2('bar') }}{% endmacro %}
{{ level1('foo') }}''')
assert tmpl.render() == 'foo|bar'
def test_arguments(self):
tmpl = self.env.from_string('''\
{% macro m(a, b, c='c', d='d') %}{{ a }}|{{ b }}|{{ c }}|{{ d }}{% endmacro %}
{{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}''')
assert tmpl.render() == '||c|d|a||c|d|a|b|c|d|1|2|3|d'
def test_varargs(self):
tmpl = self.env.from_string('''\
{% macro test() %}{{ varargs|join('|') }}{% endmacro %}\
{{ test(1, 2, 3) }}''')
assert tmpl.render() == '1|2|3'
def test_simple_call(self):
tmpl = self.env.from_string('''\
{% macro test() %}[[{{ caller() }}]]{% endmacro %}\
{% call test() %}data{% endcall %}''')
assert tmpl.render() == '[[data]]'
def test_complex_call(self):
tmpl = self.env.from_string('''\
{% macro test() %}[[{{ caller('data') }}]]{% endmacro %}\
{% call(data) test() %}{{ data }}{% endcall %}''')
assert tmpl.render() == '[[data]]'
def test_caller_undefined(self):
tmpl = self.env.from_string('''\
{% set caller = 42 %}\
{% macro test() %}{{ caller is not defined }}{% endmacro %}\
{{ test() }}''')
assert tmpl.render() == 'True'
def test_include(self):
self.env = Environment(loader=DictLoader({'include':
'{% macro test(foo) %}[{{ foo }}]{% endmacro %}'}))
tmpl = self.env.from_string('{% from "include" import test %}{{ test("foo") }}')
assert tmpl.render() == '[foo]'
def test_macro_api(self):
tmpl = self.env.from_string('{% macro foo(a, b) %}{% endmacro %}'
'{% macro bar() %}{{ varargs }}{{ kwargs }}{% endmacro %}'
'{% macro baz() %}{{ caller() }}{% endmacro %}')
assert tmpl.module.foo.arguments == ('a', 'b')
assert tmpl.module.foo.defaults == ()
assert tmpl.module.foo.name == 'foo'
assert not tmpl.module.foo.caller
assert not tmpl.module.foo.catch_kwargs
assert not tmpl.module.foo.catch_varargs
assert tmpl.module.bar.arguments == ()
assert tmpl.module.bar.defaults == ()
assert not tmpl.module.bar.caller
assert tmpl.module.bar.catch_kwargs
assert tmpl.module.bar.catch_varargs
assert tmpl.module.baz.caller
def test_callself(self):
tmpl = self.env.from_string('{% macro foo(x) %}{{ x }}{% if x > 1 %}|'
'{{ foo(x - 1) }}{% endif %}{% endmacro %}'
'{{ foo(5) }}')
assert tmpl.render() == '5|4|3|2|1'
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ForLoopTestCase))
suite.addTest(unittest.makeSuite(IfConditionTestCase))
suite.addTest(unittest.makeSuite(MacrosTestCase))
return suite
-60
View File
@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.debug
~~~~~~~~~~~~~~~~~~~~~~
Tests the debug system.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import sys
import unittest
from jinja2.testsuite import JinjaTestCase, filesystem_loader
from jinja2 import Environment, TemplateSyntaxError
env = Environment(loader=filesystem_loader)
class DebugTestCase(JinjaTestCase):
if sys.version_info[:2] != (2, 4):
def test_runtime_error(self):
def test():
tmpl.render(fail=lambda: 1 / 0)
tmpl = env.get_template('broken.html')
self.assert_traceback_matches(test, r'''
File ".*?broken.html", line 2, in (top-level template code|<module>)
\{\{ fail\(\) \}\}
File ".*?debug.pyc?", line \d+, in <lambda>
tmpl\.render\(fail=lambda: 1 / 0\)
ZeroDivisionError: (int(eger)? )?division (or modulo )?by zero
''')
def test_syntax_error(self):
# XXX: the .*? is necessary for python3 which does not hide
# some of the stack frames we don't want to show. Not sure
# what's up with that, but that is not that critical. Should
# be fixed though.
self.assert_traceback_matches(lambda: env.get_template('syntaxerror.html'), r'''(?sm)
File ".*?syntaxerror.html", line 4, in (template|<module>)
\{% endif %\}.*?
(jinja2\.exceptions\.)?TemplateSyntaxError: Encountered unknown tag 'endif'. Jinja was looking for the following tags: 'endfor' or 'else'. The innermost block that needs to be closed is 'for'.
''')
def test_regular_syntax_error(self):
def test():
raise TemplateSyntaxError('wtf', 42)
self.assert_traceback_matches(test, r'''
File ".*debug.pyc?", line \d+, in test
raise TemplateSyntaxError\('wtf', 42\)
(jinja2\.exceptions\.)?TemplateSyntaxError: wtf
line 42''')
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(DebugTestCase))
return suite
-29
View File
@@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.doctests
~~~~~~~~~~~~~~~~~~~~~~~~~
The doctests. Collects all tests we want to test from
the Jinja modules.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import unittest
import doctest
def suite():
from jinja2 import utils, sandbox, runtime, meta, loaders, \
ext, environment, bccache, nodes
suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite(utils))
suite.addTest(doctest.DocTestSuite(sandbox))
suite.addTest(doctest.DocTestSuite(runtime))
suite.addTest(doctest.DocTestSuite(meta))
suite.addTest(doctest.DocTestSuite(loaders))
suite.addTest(doctest.DocTestSuite(ext))
suite.addTest(doctest.DocTestSuite(environment))
suite.addTest(doctest.DocTestSuite(bccache))
suite.addTest(doctest.DocTestSuite(nodes))
return suite
-455
View File
@@ -1,455 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.ext
~~~~~~~~~~~~~~~~~~~~
Tests for the extensions.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import re
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Environment, DictLoader, contextfunction, nodes
from jinja2.exceptions import TemplateAssertionError
from jinja2.ext import Extension
from jinja2.lexer import Token, count_newlines
from jinja2.utils import next
# 2.x / 3.x
try:
from io import BytesIO
except ImportError:
from StringIO import StringIO as BytesIO
importable_object = 23
_gettext_re = re.compile(r'_\((.*?)\)(?s)')
i18n_templates = {
'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
'{% block body %}{% endblock %}',
'child.html': '{% extends "master.html" %}{% block body %}'
'{% trans %}watch out{% endtrans %}{% endblock %}',
'plural.html': '{% trans user_count %}One user online{% pluralize %}'
'{{ user_count }} users online{% endtrans %}',
'stringformat.html': '{{ _("User: %(num)s")|format(num=user_count) }}'
}
newstyle_i18n_templates = {
'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
'{% block body %}{% endblock %}',
'child.html': '{% extends "master.html" %}{% block body %}'
'{% trans %}watch out{% endtrans %}{% endblock %}',
'plural.html': '{% trans user_count %}One user online{% pluralize %}'
'{{ user_count }} users online{% endtrans %}',
'stringformat.html': '{{ _("User: %(num)s", num=user_count) }}',
'ngettext.html': '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}',
'ngettext_long.html': '{% trans num=apples %}{{ num }} apple{% pluralize %}'
'{{ num }} apples{% endtrans %}',
'transvars1.html': '{% trans %}User: {{ num }}{% endtrans %}',
'transvars2.html': '{% trans num=count %}User: {{ num }}{% endtrans %}',
'transvars3.html': '{% trans count=num %}User: {{ count }}{% endtrans %}',
'novars.html': '{% trans %}%(hello)s{% endtrans %}',
'vars.html': '{% trans %}{{ foo }}%(foo)s{% endtrans %}',
'explicitvars.html': '{% trans foo="42" %}%(foo)s{% endtrans %}'
}
languages = {
'de': {
'missing': u'fehlend',
'watch out': u'pass auf',
'One user online': u'Ein Benutzer online',
'%(user_count)s users online': u'%(user_count)s Benutzer online',
'User: %(num)s': u'Benutzer: %(num)s',
'User: %(count)s': u'Benutzer: %(count)s',
'%(num)s apple': u'%(num)s Apfel',
'%(num)s apples': u'%(num)s Äpfel'
}
}
@contextfunction
def gettext(context, string):
language = context.get('LANGUAGE', 'en')
return languages.get(language, {}).get(string, string)
@contextfunction
def ngettext(context, s, p, n):
language = context.get('LANGUAGE', 'en')
if n != 1:
return languages.get(language, {}).get(p, p)
return languages.get(language, {}).get(s, s)
i18n_env = Environment(
loader=DictLoader(i18n_templates),
extensions=['jinja2.ext.i18n']
)
i18n_env.globals.update({
'_': gettext,
'gettext': gettext,
'ngettext': ngettext
})
newstyle_i18n_env = Environment(
loader=DictLoader(newstyle_i18n_templates),
extensions=['jinja2.ext.i18n']
)
newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
class TestExtension(Extension):
tags = set(['test'])
ext_attr = 42
def parse(self, parser):
return nodes.Output([self.call_method('_dump', [
nodes.EnvironmentAttribute('sandboxed'),
self.attr('ext_attr'),
nodes.ImportedName(__name__ + '.importable_object'),
nodes.ContextReference()
])]).set_lineno(next(parser.stream).lineno)
def _dump(self, sandboxed, ext_attr, imported_object, context):
return '%s|%s|%s|%s' % (
sandboxed,
ext_attr,
imported_object,
context.blocks
)
class PreprocessorExtension(Extension):
def preprocess(self, source, name, filename=None):
return source.replace('[[TEST]]', '({{ foo }})')
class StreamFilterExtension(Extension):
def filter_stream(self, stream):
for token in stream:
if token.type == 'data':
for t in self.interpolate(token):
yield t
else:
yield token
def interpolate(self, token):
pos = 0
end = len(token.value)
lineno = token.lineno
while 1:
match = _gettext_re.search(token.value, pos)
if match is None:
break
value = token.value[pos:match.start()]
if value:
yield Token(lineno, 'data', value)
lineno += count_newlines(token.value)
yield Token(lineno, 'variable_begin', None)
yield Token(lineno, 'name', 'gettext')
yield Token(lineno, 'lparen', None)
yield Token(lineno, 'string', match.group(1))
yield Token(lineno, 'rparen', None)
yield Token(lineno, 'variable_end', None)
pos = match.end()
if pos < end:
yield Token(lineno, 'data', token.value[pos:])
class ExtensionsTestCase(JinjaTestCase):
def test_extend_late(self):
env = Environment()
env.add_extension('jinja2.ext.autoescape')
t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}')
assert t.render() == '&lt;test&gt;'
def test_loop_controls(self):
env = Environment(extensions=['jinja2.ext.loopcontrols'])
tmpl = env.from_string('''
{%- for item in [1, 2, 3, 4] %}
{%- if item % 2 == 0 %}{% continue %}{% endif -%}
{{ item }}
{%- endfor %}''')
assert tmpl.render() == '13'
tmpl = env.from_string('''
{%- for item in [1, 2, 3, 4] %}
{%- if item > 2 %}{% break %}{% endif -%}
{{ item }}
{%- endfor %}''')
assert tmpl.render() == '12'
def test_do(self):
env = Environment(extensions=['jinja2.ext.do'])
tmpl = env.from_string('''
{%- set items = [] %}
{%- for char in "foo" %}
{%- do items.append(loop.index0 ~ char) %}
{%- endfor %}{{ items|join(', ') }}''')
assert tmpl.render() == '0f, 1o, 2o'
def test_with(self):
env = Environment(extensions=['jinja2.ext.with_'])
tmpl = env.from_string('''\
{% with a=42, b=23 -%}
{{ a }} = {{ b }}
{% endwith -%}
{{ a }} = {{ b }}\
''')
assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \
== ['42 = 23', '1 = 2']
def test_extension_nodes(self):
env = Environment(extensions=[TestExtension])
tmpl = env.from_string('{% test %}')
assert tmpl.render() == 'False|42|23|{}'
def test_identifier(self):
assert TestExtension.identifier == __name__ + '.TestExtension'
def test_rebinding(self):
original = Environment(extensions=[TestExtension])
overlay = original.overlay()
for env in original, overlay:
for ext in env.extensions.itervalues():
assert ext.environment is env
def test_preprocessor_extension(self):
env = Environment(extensions=[PreprocessorExtension])
tmpl = env.from_string('{[[TEST]]}')
assert tmpl.render(foo=42) == '{(42)}'
def test_streamfilter_extension(self):
env = Environment(extensions=[StreamFilterExtension])
env.globals['gettext'] = lambda x: x.upper()
tmpl = env.from_string('Foo _(bar) Baz')
out = tmpl.render()
assert out == 'Foo BAR Baz'
def test_extension_ordering(self):
class T1(Extension):
priority = 1
class T2(Extension):
priority = 2
env = Environment(extensions=[T1, T2])
ext = list(env.iter_extensions())
assert ext[0].__class__ is T1
assert ext[1].__class__ is T2
class InternationalizationTestCase(JinjaTestCase):
def test_trans(self):
tmpl = i18n_env.get_template('child.html')
assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
def test_trans_plural(self):
tmpl = i18n_env.get_template('plural.html')
assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
def test_complex_plural(self):
tmpl = i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
'pluralize count %}{{ count }} items{% endtrans %}')
assert tmpl.render() == '2 items'
self.assert_raises(TemplateAssertionError, i18n_env.from_string,
'{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
def test_trans_stringformatting(self):
tmpl = i18n_env.get_template('stringformat.html')
assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
def test_extract(self):
from jinja2.ext import babel_extract
source = BytesIO('''
{{ gettext('Hello World') }}
{% trans %}Hello World{% endtrans %}
{% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
'''.encode('ascii')) # make python 3 happy
assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
(2, 'gettext', u'Hello World', []),
(3, 'gettext', u'Hello World', []),
(4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
]
def test_comment_extract(self):
from jinja2.ext import babel_extract
source = BytesIO('''
{# trans first #}
{{ gettext('Hello World') }}
{% trans %}Hello World{% endtrans %}{# trans second #}
{#: third #}
{% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
'''.encode('utf-8')) # make python 3 happy
assert list(babel_extract(source, ('gettext', 'ngettext', '_'), ['trans', ':'], {})) == [
(3, 'gettext', u'Hello World', ['first']),
(4, 'gettext', u'Hello World', ['second']),
(6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), ['third'])
]
class NewstyleInternationalizationTestCase(JinjaTestCase):
def test_trans(self):
tmpl = newstyle_i18n_env.get_template('child.html')
assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
def test_trans_plural(self):
tmpl = newstyle_i18n_env.get_template('plural.html')
assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
def test_complex_plural(self):
tmpl = newstyle_i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
'pluralize count %}{{ count }} items{% endtrans %}')
assert tmpl.render() == '2 items'
self.assert_raises(TemplateAssertionError, i18n_env.from_string,
'{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
def test_trans_stringformatting(self):
tmpl = newstyle_i18n_env.get_template('stringformat.html')
assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
def test_newstyle_plural(self):
tmpl = newstyle_i18n_env.get_template('ngettext.html')
assert tmpl.render(LANGUAGE='de', apples=1) == '1 Apfel'
assert tmpl.render(LANGUAGE='de', apples=5) == u'5 Äpfel'
def test_autoescape_support(self):
env = Environment(extensions=['jinja2.ext.autoescape',
'jinja2.ext.i18n'])
env.install_gettext_callables(lambda x: u'<strong>Wert: %(name)s</strong>',
lambda s, p, n: s, newstyle=True)
t = env.from_string('{% autoescape ae %}{{ gettext("foo", name='
'"<test>") }}{% endautoescape %}')
assert t.render(ae=True) == '<strong>Wert: &lt;test&gt;</strong>'
assert t.render(ae=False) == '<strong>Wert: <test></strong>'
def test_num_used_twice(self):
tmpl = newstyle_i18n_env.get_template('ngettext_long.html')
assert tmpl.render(apples=5, LANGUAGE='de') == u'5 Äpfel'
def test_num_called_num(self):
source = newstyle_i18n_env.compile('''
{% trans num=3 %}{{ num }} apple{% pluralize
%}{{ num }} apples{% endtrans %}
''', raw=True)
# quite hacky, but the only way to properly test that. The idea is
# that the generated code does not pass num twice (although that
# would work) for better performance. This only works on the
# newstyle gettext of course
assert re.search(r"l_ngettext, u?'\%\(num\)s apple', u?'\%\(num\)s "
r"apples', 3", source) is not None
def test_trans_vars(self):
t1 = newstyle_i18n_env.get_template('transvars1.html')
t2 = newstyle_i18n_env.get_template('transvars2.html')
t3 = newstyle_i18n_env.get_template('transvars3.html')
assert t1.render(num=1, LANGUAGE='de') == 'Benutzer: 1'
assert t2.render(count=23, LANGUAGE='de') == 'Benutzer: 23'
assert t3.render(num=42, LANGUAGE='de') == 'Benutzer: 42'
def test_novars_vars_escaping(self):
t = newstyle_i18n_env.get_template('novars.html')
assert t.render() == '%(hello)s'
t = newstyle_i18n_env.get_template('vars.html')
assert t.render(foo='42') == '42%(foo)s'
t = newstyle_i18n_env.get_template('explicitvars.html')
assert t.render() == '%(foo)s'
class AutoEscapeTestCase(JinjaTestCase):
def test_scoped_setting(self):
env = Environment(extensions=['jinja2.ext.autoescape'],
autoescape=True)
tmpl = env.from_string('''
{{ "<HelloWorld>" }}
{% autoescape false %}
{{ "<HelloWorld>" }}
{% endautoescape %}
{{ "<HelloWorld>" }}
''')
assert tmpl.render().split() == \
[u'&lt;HelloWorld&gt;', u'<HelloWorld>', u'&lt;HelloWorld&gt;']
env = Environment(extensions=['jinja2.ext.autoescape'],
autoescape=False)
tmpl = env.from_string('''
{{ "<HelloWorld>" }}
{% autoescape true %}
{{ "<HelloWorld>" }}
{% endautoescape %}
{{ "<HelloWorld>" }}
''')
assert tmpl.render().split() == \
[u'<HelloWorld>', u'&lt;HelloWorld&gt;', u'<HelloWorld>']
def test_nonvolatile(self):
env = Environment(extensions=['jinja2.ext.autoescape'],
autoescape=True)
tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
assert tmpl.render() == ' foo="&lt;test&gt;"'
tmpl = env.from_string('{% autoescape false %}{{ {"foo": "<test>"}'
'|xmlattr|escape }}{% endautoescape %}')
assert tmpl.render() == ' foo=&#34;&amp;lt;test&amp;gt;&#34;'
def test_volatile(self):
env = Environment(extensions=['jinja2.ext.autoescape'],
autoescape=True)
tmpl = env.from_string('{% autoescape foo %}{{ {"foo": "<test>"}'
'|xmlattr|escape }}{% endautoescape %}')
assert tmpl.render(foo=False) == ' foo=&#34;&amp;lt;test&amp;gt;&#34;'
assert tmpl.render(foo=True) == ' foo="&lt;test&gt;"'
def test_scoping(self):
env = Environment(extensions=['jinja2.ext.autoescape'])
tmpl = env.from_string('{% autoescape true %}{% set x = "<x>" %}{{ x }}'
'{% endautoescape %}{{ x }}{{ "<y>" }}')
assert tmpl.render(x=1) == '&lt;x&gt;1<y>'
def test_volatile_scoping(self):
env = Environment(extensions=['jinja2.ext.autoescape'])
tmplsource = '''
{% autoescape val %}
{% macro foo(x) %}
[{{ x }}]
{% endmacro %}
{{ foo().__class__.__name__ }}
{% endautoescape %}
{{ '<testing>' }}
'''
tmpl = env.from_string(tmplsource)
assert tmpl.render(val=True).split()[0] == 'Markup'
assert tmpl.render(val=False).split()[0] == unicode.__name__
# looking at the source we should see <testing> there in raw
# (and then escaped as well)
env = Environment(extensions=['jinja2.ext.autoescape'])
pysource = env.compile(tmplsource, raw=True)
assert '<testing>\\n' in pysource
env = Environment(extensions=['jinja2.ext.autoescape'],
autoescape=True)
pysource = env.compile(tmplsource, raw=True)
assert '&lt;testing&gt;\\n' in pysource
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ExtensionsTestCase))
suite.addTest(unittest.makeSuite(InternationalizationTestCase))
suite.addTest(unittest.makeSuite(NewstyleInternationalizationTestCase))
suite.addTest(unittest.makeSuite(AutoEscapeTestCase))
return suite
-356
View File
@@ -1,356 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.filters
~~~~~~~~~~~~~~~~~~~~~~~~
Tests for the jinja filters.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Markup, Environment
env = Environment()
class FilterTestCase(JinjaTestCase):
def test_capitalize(self):
tmpl = env.from_string('{{ "foo bar"|capitalize }}')
assert tmpl.render() == 'Foo bar'
def test_center(self):
tmpl = env.from_string('{{ "foo"|center(9) }}')
assert tmpl.render() == ' foo '
def test_default(self):
tmpl = env.from_string(
"{{ missing|default('no') }}|{{ false|default('no') }}|"
"{{ false|default('no', true) }}|{{ given|default('no') }}"
)
assert tmpl.render(given='yes') == 'no|False|no|yes'
def test_dictsort(self):
tmpl = env.from_string(
'{{ foo|dictsort }}|'
'{{ foo|dictsort(true) }}|'
'{{ foo|dictsort(false, "value") }}'
)
out = tmpl.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3})
assert out == ("[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]|"
"[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]|"
"[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]")
def test_batch(self):
tmpl = env.from_string("{{ foo|batch(3)|list }}|"
"{{ foo|batch(3, 'X')|list }}")
out = tmpl.render(foo=range(10))
assert out == ("[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|"
"[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]")
def test_slice(self):
tmpl = env.from_string('{{ foo|slice(3)|list }}|'
'{{ foo|slice(3, "X")|list }}')
out = tmpl.render(foo=range(10))
assert out == ("[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
"[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]")
def test_escape(self):
tmpl = env.from_string('''{{ '<">&'|escape }}''')
out = tmpl.render()
assert out == '&lt;&#34;&gt;&amp;'
def test_striptags(self):
tmpl = env.from_string('''{{ foo|striptags }}''')
out = tmpl.render(foo=' <p>just a small \n <a href="#">'
'example</a> link</p>\n<p>to a webpage</p> '
'<!-- <p>and some commented stuff</p> -->')
assert out == 'just a small example link to a webpage'
def test_filesizeformat(self):
tmpl = env.from_string(
'{{ 100|filesizeformat }}|'
'{{ 1000|filesizeformat }}|'
'{{ 1000000|filesizeformat }}|'
'{{ 1000000000|filesizeformat }}|'
'{{ 1000000000000|filesizeformat }}|'
'{{ 100|filesizeformat(true) }}|'
'{{ 1000|filesizeformat(true) }}|'
'{{ 1000000|filesizeformat(true) }}|'
'{{ 1000000000|filesizeformat(true) }}|'
'{{ 1000000000000|filesizeformat(true) }}'
)
out = tmpl.render()
assert out == (
'100 Bytes|0.0 kB|0.0 MB|0.0 GB|0.0 TB|100 Bytes|'
'1000 Bytes|1.0 KiB|0.9 MiB|0.9 GiB'
)
def test_first(self):
tmpl = env.from_string('{{ foo|first }}')
out = tmpl.render(foo=range(10))
assert out == '0'
def test_float(self):
tmpl = env.from_string('{{ "42"|float }}|'
'{{ "ajsghasjgd"|float }}|'
'{{ "32.32"|float }}')
out = tmpl.render()
assert out == '42.0|0.0|32.32'
def test_format(self):
tmpl = env.from_string('''{{ "%s|%s"|format("a", "b") }}''')
out = tmpl.render()
assert out == 'a|b'
def test_indent(self):
tmpl = env.from_string('{{ foo|indent(2) }}|{{ foo|indent(2, true) }}')
text = '\n'.join([' '.join(['foo', 'bar'] * 2)] * 2)
out = tmpl.render(foo=text)
assert out == ('foo bar foo bar\n foo bar foo bar| '
'foo bar foo bar\n foo bar foo bar')
def test_int(self):
tmpl = env.from_string('{{ "42"|int }}|{{ "ajsghasjgd"|int }}|'
'{{ "32.32"|int }}')
out = tmpl.render()
assert out == '42|0|32'
def test_join(self):
tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}')
out = tmpl.render()
assert out == '1|2|3'
env2 = Environment(autoescape=True)
tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
assert tmpl.render() == '&lt;foo&gt;<span>foo</span>'
def test_join_attribute(self):
class User(object):
def __init__(self, username):
self.username = username
tmpl = env.from_string('''{{ users|join(', ', 'username') }}''')
assert tmpl.render(users=map(User, ['foo', 'bar'])) == 'foo, bar'
def test_last(self):
tmpl = env.from_string('''{{ foo|last }}''')
out = tmpl.render(foo=range(10))
assert out == '9'
def test_length(self):
tmpl = env.from_string('''{{ "hello world"|length }}''')
out = tmpl.render()
assert out == '11'
def test_lower(self):
tmpl = env.from_string('''{{ "FOO"|lower }}''')
out = tmpl.render()
assert out == 'foo'
def test_pprint(self):
from pprint import pformat
tmpl = env.from_string('''{{ data|pprint }}''')
data = range(1000)
assert tmpl.render(data=data) == pformat(data)
def test_random(self):
tmpl = env.from_string('''{{ seq|random }}''')
seq = range(100)
for _ in range(10):
assert int(tmpl.render(seq=seq)) in seq
def test_reverse(self):
tmpl = env.from_string('{{ "foobar"|reverse|join }}|'
'{{ [1, 2, 3]|reverse|list }}')
assert tmpl.render() == 'raboof|[3, 2, 1]'
def test_string(self):
x = [1, 2, 3, 4, 5]
tmpl = env.from_string('''{{ obj|string }}''')
assert tmpl.render(obj=x) == unicode(x)
def test_title(self):
tmpl = env.from_string('''{{ "foo bar"|title }}''')
assert tmpl.render() == "Foo Bar"
def test_truncate(self):
tmpl = env.from_string(
'{{ data|truncate(15, true, ">>>") }}|'
'{{ data|truncate(15, false, ">>>") }}|'
'{{ smalldata|truncate(15) }}'
)
out = tmpl.render(data='foobar baz bar' * 1000,
smalldata='foobar baz bar')
assert out == 'foobar baz barf>>>|foobar baz >>>|foobar baz bar'
def test_upper(self):
tmpl = env.from_string('{{ "foo"|upper }}')
assert tmpl.render() == 'FOO'
def test_urlize(self):
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_wordcount(self):
tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
assert tmpl.render() == '3'
def test_block(self):
tmpl = env.from_string('{% filter lower|escape %}<HEHE>{% endfilter %}')
assert tmpl.render() == '&lt;hehe&gt;'
def test_chaining(self):
tmpl = env.from_string('''{{ ['<foo>', '<bar>']|first|upper|escape }}''')
assert tmpl.render() == '&lt;FOO&gt;'
def test_sum(self):
tmpl = env.from_string('''{{ [1, 2, 3, 4, 5, 6]|sum }}''')
assert tmpl.render() == '21'
def test_sum_attributes(self):
tmpl = env.from_string('''{{ values|sum('value') }}''')
assert tmpl.render(values=[
{'value': 23},
{'value': 1},
{'value': 18},
]) == '42'
def test_sum_attributes_nested(self):
tmpl = env.from_string('''{{ values|sum('real.value') }}''')
assert tmpl.render(values=[
{'real': {'value': 23}},
{'real': {'value': 1}},
{'real': {'value': 18}},
]) == '42'
def test_abs(self):
tmpl = env.from_string('''{{ -1|abs }}|{{ 1|abs }}''')
assert tmpl.render() == '1|1', tmpl.render()
def test_round_positive(self):
tmpl = env.from_string('{{ 2.7|round }}|{{ 2.1|round }}|'
"{{ 2.1234|round(3, 'floor') }}|"
"{{ 2.1|round(0, 'ceil') }}")
assert tmpl.render() == '3.0|2.0|2.123|3.0', tmpl.render()
def test_round_negative(self):
tmpl = env.from_string('{{ 21.3|round(-1)}}|'
"{{ 21.3|round(-1, 'ceil')}}|"
"{{ 21.3|round(-1, 'floor')}}")
assert tmpl.render() == '20.0|30.0|20.0',tmpl.render()
def test_xmlattr(self):
tmpl = env.from_string("{{ {'foo': 42, 'bar': 23, 'fish': none, "
"'spam': missing, 'blub:blub': '<?>'}|xmlattr }}")
out = tmpl.render().split()
assert len(out) == 3
assert 'foo="42"' in out
assert 'bar="23"' in out
assert 'blub:blub="&lt;?&gt;"' in out
def test_sort1(self):
tmpl = env.from_string('{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}')
assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
def test_sort2(self):
tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}')
assert tmpl.render() == 'AbcD'
def test_sort3(self):
tmpl = env.from_string('''{{ ['foo', 'Bar', 'blah']|sort }}''')
assert tmpl.render() == "['Bar', 'blah', 'foo']"
def test_sort4(self):
class Magic(object):
def __init__(self, value):
self.value = value
def __unicode__(self):
return unicode(self.value)
tmpl = env.from_string('''{{ items|sort(attribute='value')|join }}''')
assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == '1234'
def test_groupby(self):
tmpl = env.from_string('''
{%- for grouper, list in [{'foo': 1, 'bar': 2},
{'foo': 2, 'bar': 3},
{'foo': 1, 'bar': 1},
{'foo': 3, 'bar': 4}]|groupby('foo') -%}
{{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
{%- endfor %}''')
assert tmpl.render().split('|') == [
"1: 1, 2: 1, 1",
"2: 2, 3",
"3: 3, 4",
""
]
def test_groupby_tuple_index(self):
tmpl = env.from_string('''
{%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%}
{{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
{%- endfor %}''')
assert tmpl.render() == 'a:1:2|b:1|'
def test_groupby_multidot(self):
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
articles = [
Article('aha', 1, 1, 1970),
Article('interesting', 2, 1, 1970),
Article('really?', 3, 1, 1970),
Article('totally not', 1, 1, 1971)
]
tmpl = env.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]',
''
]
def test_filtertag(self):
tmpl = env.from_string("{% filter upper|replace('FOO', 'foo') %}"
"foobar{% endfilter %}")
assert tmpl.render() == 'fooBAR'
def test_replace(self):
env = Environment()
tmpl = env.from_string('{{ string|replace("o", 42) }}')
assert tmpl.render(string='<foo>') == '<f4242>'
env = Environment(autoescape=True)
tmpl = env.from_string('{{ string|replace("o", 42) }}')
assert tmpl.render(string='<foo>') == '&lt;f4242&gt;'
tmpl = env.from_string('{{ string|replace("<", 42) }}')
assert tmpl.render(string='<foo>') == '42foo&gt;'
tmpl = env.from_string('{{ string|replace("o", ">x<") }}')
assert tmpl.render(string=Markup('foo')) == 'f&gt;x&lt;&gt;x&lt;'
def test_forceescape(self):
tmpl = env.from_string('{{ x|forceescape }}')
assert tmpl.render(x=Markup('<div />')) == u'&lt;div /&gt;'
def test_safe(self):
env = Environment(autoescape=True)
tmpl = env.from_string('{{ "<div>foo</div>"|safe }}')
assert tmpl.render() == '<div>foo</div>'
tmpl = env.from_string('{{ "<div>foo</div>" }}')
assert tmpl.render() == '&lt;div&gt;foo&lt;/div&gt;'
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(FilterTestCase))
return suite
-141
View File
@@ -1,141 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.imports
~~~~~~~~~~~~~~~~~~~~~~~~
Tests the import features (with includes).
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Environment, DictLoader
from jinja2.exceptions import TemplateNotFound, TemplatesNotFound
test_env = Environment(loader=DictLoader(dict(
module='{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}',
header='[{{ foo }}|{{ 23 }}]',
o_printer='({{ o }})'
)))
test_env.globals['bar'] = 23
class ImportsTestCase(JinjaTestCase):
def test_context_imports(self):
t = test_env.from_string('{% import "module" as m %}{{ m.test() }}')
assert t.render(foo=42) == '[|23]'
t = test_env.from_string('{% import "module" as m without context %}{{ m.test() }}')
assert t.render(foo=42) == '[|23]'
t = test_env.from_string('{% import "module" as m with context %}{{ m.test() }}')
assert t.render(foo=42) == '[42|23]'
t = test_env.from_string('{% from "module" import test %}{{ test() }}')
assert t.render(foo=42) == '[|23]'
t = test_env.from_string('{% from "module" import test without context %}{{ test() }}')
assert t.render(foo=42) == '[|23]'
t = test_env.from_string('{% from "module" import test with context %}{{ test() }}')
assert t.render(foo=42) == '[42|23]'
def test_trailing_comma(self):
test_env.from_string('{% from "foo" import bar, baz with context %}')
test_env.from_string('{% from "foo" import bar, baz, with context %}')
test_env.from_string('{% from "foo" import bar, with context %}')
test_env.from_string('{% from "foo" import bar, with, context %}')
test_env.from_string('{% from "foo" import bar, with with context %}')
def test_exports(self):
m = test_env.from_string('''
{% macro toplevel() %}...{% endmacro %}
{% macro __private() %}...{% endmacro %}
{% set variable = 42 %}
{% for item in [1] %}
{% macro notthere() %}{% endmacro %}
{% endfor %}
''').module
assert m.toplevel() == '...'
assert not hasattr(m, '__missing')
assert m.variable == 42
assert not hasattr(m, 'notthere')
class IncludesTestCase(JinjaTestCase):
def test_context_include(self):
t = test_env.from_string('{% include "header" %}')
assert t.render(foo=42) == '[42|23]'
t = test_env.from_string('{% include "header" with context %}')
assert t.render(foo=42) == '[42|23]'
t = test_env.from_string('{% include "header" without context %}')
assert t.render(foo=42) == '[|23]'
def test_choice_includes(self):
t = test_env.from_string('{% include ["missing", "header"] %}')
assert t.render(foo=42) == '[42|23]'
t = test_env.from_string('{% include ["missing", "missing2"] ignore missing %}')
assert t.render(foo=42) == ''
t = test_env.from_string('{% include ["missing", "missing2"] %}')
self.assert_raises(TemplateNotFound, t.render)
try:
t.render()
except TemplatesNotFound, 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.from_string('{% include ["missing", "header"] %}')
test_includes(t)
t = test_env.from_string('{% include x %}')
test_includes(t, x=['missing', 'header'])
t = test_env.from_string('{% include [x, "header"] %}')
test_includes(t, x='missing')
t = test_env.from_string('{% include x %}')
test_includes(t, x='header')
t = test_env.from_string('{% include x %}')
test_includes(t, x='header')
t = test_env.from_string('{% include [x] %}')
test_includes(t, x='header')
def test_include_ignoring_missing(self):
t = test_env.from_string('{% include "missing" %}')
self.assert_raises(TemplateNotFound, t.render)
for extra in '', 'with context', 'without context':
t = test_env.from_string('{% include "missing" ignore missing ' +
extra + ' %}')
assert t.render() == ''
def test_context_include_with_overrides(self):
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):
t = test_env.from_string("""
{% macro outer(o) %}
{% macro inner() %}
{% include "o_printer" %}
{% endmacro %}
{{ inner() }}
{% endmacro %}
{{ outer("FOO") }}
""")
assert t.render().strip() == '(FOO)'
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ImportsTestCase))
suite.addTest(unittest.makeSuite(IncludesTestCase))
return suite
-227
View File
@@ -1,227 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.inheritance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests the template inheritance feature.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Environment, DictLoader
LAYOUTTEMPLATE = '''\
|{% block block1 %}block 1 from layout{% endblock %}
|{% block block2 %}block 2 from layout{% endblock %}
|{% block block3 %}
{% block block4 %}nested block 4 from layout{% endblock %}
{% endblock %}|'''
LEVEL1TEMPLATE = '''\
{% extends "layout" %}
{% block block1 %}block 1 from level1{% endblock %}'''
LEVEL2TEMPLATE = '''\
{% extends "level1" %}
{% block block2 %}{% block block5 %}nested block 5 from level2{%
endblock %}{% endblock %}'''
LEVEL3TEMPLATE = '''\
{% extends "level2" %}
{% block block5 %}block 5 from level3{% endblock %}
{% block block4 %}block 4 from level3{% endblock %}
'''
LEVEL4TEMPLATE = '''\
{% extends "level3" %}
{% block block3 %}block 3 from level4{% endblock %}
'''
WORKINGTEMPLATE = '''\
{% extends "layout" %}
{% block block1 %}
{% if false %}
{% block block2 %}
this should workd
{% endblock %}
{% endif %}
{% endblock %}
'''
env = Environment(loader=DictLoader({
'layout': LAYOUTTEMPLATE,
'level1': LEVEL1TEMPLATE,
'level2': LEVEL2TEMPLATE,
'level3': LEVEL3TEMPLATE,
'level4': LEVEL4TEMPLATE,
'working': WORKINGTEMPLATE
}), trim_blocks=True)
class InheritanceTestCase(JinjaTestCase):
def test_layout(self):
tmpl = env.get_template('layout')
assert tmpl.render() == ('|block 1 from layout|block 2 from '
'layout|nested block 4 from layout|')
def test_level1(self):
tmpl = env.get_template('level1')
assert tmpl.render() == ('|block 1 from level1|block 2 from '
'layout|nested block 4 from layout|')
def test_level2(self):
tmpl = env.get_template('level2')
assert tmpl.render() == ('|block 1 from level1|nested block 5 from '
'level2|nested block 4 from layout|')
def test_level3(self):
tmpl = env.get_template('level3')
assert tmpl.render() == ('|block 1 from level1|block 5 from level3|'
'block 4 from level3|')
def test_level4(sel):
tmpl = env.get_template('level4')
assert tmpl.render() == ('|block 1 from level1|block 5 from '
'level3|block 3 from level4|')
def test_super(self):
env = Environment(loader=DictLoader({
'a': '{% block intro %}INTRO{% endblock %}|'
'BEFORE|{% block data %}INNER{% endblock %}|AFTER',
'b': '{% extends "a" %}{% block data %}({{ '
'super() }}){% endblock %}',
'c': '{% extends "b" %}{% block intro %}--{{ '
'super() }}--{% endblock %}\n{% block data '
'%}[{{ super() }}]{% endblock %}'
}))
tmpl = env.get_template('c')
assert tmpl.render() == '--INTRO--|BEFORE|[(INNER)]|AFTER'
def test_working(self):
tmpl = env.get_template('working')
def test_reuse_blocks(self):
tmpl = env.from_string('{{ self.foo() }}|{% block foo %}42'
'{% endblock %}|{{ self.foo() }}')
assert tmpl.render() == '42|42|42'
def test_preserve_blocks(self):
env = Environment(loader=DictLoader({
'a': '{% if false %}{% block x %}A{% endblock %}{% endif %}{{ self.x() }}',
'b': '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}'
}))
tmpl = env.get_template('b')
assert tmpl.render() == 'BA'
def test_dynamic_inheritance(self):
env = Environment(loader=DictLoader({
'master1': 'MASTER1{% block x %}{% endblock %}',
'master2': 'MASTER2{% block x %}{% endblock %}',
'child': '{% extends master %}{% block x %}CHILD{% endblock %}'
}))
tmpl = env.get_template('child')
for m in range(1, 3):
assert tmpl.render(master='master%d' % m) == 'MASTER%dCHILD' % m
def test_multi_inheritance(self):
env = Environment(loader=DictLoader({
'master1': 'MASTER1{% block x %}{% endblock %}',
'master2': 'MASTER2{% block x %}{% endblock %}',
'child': '''{% if master %}{% extends master %}{% else %}{% extends
'master1' %}{% endif %}{% block x %}CHILD{% endblock %}'''
}))
tmpl = env.get_template('child')
assert tmpl.render(master='master2') == 'MASTER2CHILD'
assert tmpl.render(master='master1') == 'MASTER1CHILD'
assert tmpl.render() == 'MASTER1CHILD'
def test_scoped_block(self):
env = Environment(loader=DictLoader({
'master.html': '{% for item in seq %}[{% block item scoped %}'
'{% endblock %}]{% endfor %}'
}))
t = env.from_string('{% extends "master.html" %}{% block item %}'
'{{ item }}{% endblock %}')
assert t.render(seq=range(5)) == '[0][1][2][3][4]'
def test_super_in_scoped_block(self):
env = Environment(loader=DictLoader({
'master.html': '{% for item in seq %}[{% block item scoped %}'
'{{ item }}{% endblock %}]{% endfor %}'
}))
t = env.from_string('{% extends "master.html" %}{% block item %}'
'{{ super() }}|{{ item * 2 }}{% endblock %}')
assert t.render(seq=range(5)) == '[0|0][1|2][2|4][3|6][4|8]'
def test_scoped_block_after_inheritance(self):
env = Environment(loader=DictLoader({
'layout.html': '''
{% block useless %}{% endblock %}
''',
'index.html': '''
{%- extends 'layout.html' %}
{% from 'helpers.html' import foo with context %}
{% block useless %}
{% for x in [1, 2, 3] %}
{% block testing scoped %}
{{ foo(x) }}
{% endblock %}
{% endfor %}
{% endblock %}
''',
'helpers.html': '''
{% macro foo(x) %}{{ the_foo + x }}{% endmacro %}
'''
}))
rv = env.get_template('index.html').render(the_foo=42).split()
assert rv == ['43', '44', '45']
class BugFixTestCase(JinjaTestCase):
def test_fixed_macro_scoping_bug(self):
assert Environment(loader=DictLoader({
'test.html': '''\
{% extends 'details.html' %}
{% macro my_macro() %}
my_macro
{% endmacro %}
{% block inner_box %}
{{ my_macro() }}
{% endblock %}
''',
'details.html': '''\
{% extends 'standard.html' %}
{% macro my_macro() %}
my_macro
{% endmacro %}
{% block content %}
{% block outer_box %}
outer_box
{% block inner_box %}
inner_box
{% endblock %}
{% endblock %}
{% endblock %}
''',
'standard.html': '''
{% block content %}&nbsp;{% endblock %}
'''
})).get_template("test.html").render().split() == [u'outer_box', u'my_macro']
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(InheritanceTestCase))
suite.addTest(unittest.makeSuite(BugFixTestCase))
return suite
-387
View File
@@ -1,387 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.lexnparse
~~~~~~~~~~~~~~~~~~~~~~~~~~
All the unittests regarding lexing, parsing and syntax.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import sys
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Environment, Template, TemplateSyntaxError, \
UndefinedError, nodes
env = Environment()
# how does a string look like in jinja syntax?
if sys.version_info < (3, 0):
def jinja_string_repr(string):
return repr(string)[1:]
else:
jinja_string_repr = repr
class LexerTestCase(JinjaTestCase):
def test_raw1(self):
tmpl = env.from_string('{% raw %}foo{% endraw %}|'
'{%raw%}{{ bar }}|{% baz %}{% endraw %}')
assert tmpl.render() == 'foo|{{ bar }}|{% baz %}'
def test_raw2(self):
tmpl = env.from_string('1 {%- raw -%} 2 {%- endraw -%} 3')
assert tmpl.render() == '123'
def test_balancing(self):
env = Environment('{%', '%}', '${', '}')
tmpl = env.from_string('''{% for item in seq
%}${{'foo': item}|upper}{% endfor %}''')
assert tmpl.render(seq=range(3)) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}"
def test_comments(self):
env = Environment('<!--', '-->', '{', '}')
tmpl = env.from_string('''\
<ul>
<!--- for item in seq -->
<li>{item}</li>
<!--- endfor -->
</ul>''')
assert tmpl.render(seq=range(3)) == ("<ul>\n <li>0</li>\n "
"<li>1</li>\n <li>2</li>\n</ul>")
def test_string_escapes(self):
for char in u'\0', u'\u2668', u'\xe4', u'\t', u'\r', u'\n':
tmpl = env.from_string('{{ %s }}' % jinja_string_repr(char))
assert tmpl.render() == char
assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == u'\u2668'
def test_bytefallback(self):
from pprint import pformat
tmpl = env.from_string(u'''{{ 'foo'|pprint }}|{{ 'bär'|pprint }}''')
assert tmpl.render() == pformat('foo') + '|' + pformat(u'bär')
def test_operators(self):
from jinja2.lexer import operators
for test, expect in operators.iteritems():
if test in '([{}])':
continue
stream = env.lexer.tokenize('{{ %s }}' % test)
stream.next()
assert stream.current.type == expect
def test_normalizing(self):
for seq in '\r', '\r\n', '\n':
env = Environment(newline_sequence=seq)
tmpl = env.from_string('1\n2\r\n3\n4\n')
result = tmpl.render()
assert result.replace(seq, 'X') == '1X2X3X4'
class ParserTestCase(JinjaTestCase):
def test_php_syntax(self):
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->')
tmpl = env.from_string('''\
<!-- I'm a comment, I'm not interesting -->\
<? for item in seq -?>
<?= item ?>
<?- endfor ?>''')
assert tmpl.render(seq=range(5)) == '01234'
def test_erb_syntax(self):
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>')
tmpl = env.from_string('''\
<%# I'm a comment, I'm not interesting %>\
<% for item in seq -%>
<%= item %>
<%- endfor %>''')
assert tmpl.render(seq=range(5)) == '01234'
def test_comment_syntax(self):
env = Environment('<!--', '-->', '${', '}', '<!--#', '-->')
tmpl = env.from_string('''\
<!--# I'm a comment, I'm not interesting -->\
<!-- for item in seq --->
${item}
<!--- endfor -->''')
assert tmpl.render(seq=range(5)) == '01234'
def test_balancing(self):
tmpl = env.from_string('''{{{'foo':'bar'}.foo}}''')
assert tmpl.render() == 'bar'
def test_start_comment(self):
tmpl = env.from_string('''{# foo comment
and bar comment #}
{% macro blub() %}foo{% endmacro %}
{{ blub() }}''')
assert tmpl.render().strip() == 'foo'
def test_line_syntax(self):
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%')
tmpl = env.from_string('''\
<%# regular comment %>
% for item in seq:
${item}
% endfor''')
assert [int(x.strip()) for x in tmpl.render(seq=range(5)).split()] == \
range(5)
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##')
tmpl = env.from_string('''\
<%# regular comment %>
% for item in seq:
${item} ## the rest of the stuff
% endfor''')
assert [int(x.strip()) for x in tmpl.render(seq=range(5)).split()] == \
range(5)
def test_line_syntax_priority(self):
# XXX: why is the whitespace there in front of the newline?
env = Environment('{%', '%}', '${', '}', '/*', '*/', '##', '#')
tmpl = env.from_string('''\
/* ignore me.
I'm a multiline comment */
## for item in seq:
* ${item} # this is just extra stuff
## endfor''')
assert tmpl.render(seq=[1, 2]).strip() == '* 1\n* 2'
env = Environment('{%', '%}', '${', '}', '/*', '*/', '#', '##')
tmpl = env.from_string('''\
/* ignore me.
I'm a multiline comment */
# for item in seq:
* ${item} ## this is just extra stuff
## extra stuff i just want to ignore
# endfor''')
assert tmpl.render(seq=[1, 2]).strip() == '* 1\n\n* 2'
def test_error_messages(self):
def assert_error(code, expected):
try:
Template(code)
except TemplateSyntaxError, e:
assert str(e) == expected, 'unexpected error message'
else:
assert False, 'that was suposed to be an error'
assert_error('{% for item in seq %}...{% endif %}',
"Encountered unknown tag 'endif'. Jinja was looking "
"for the following tags: 'endfor' or 'else'. The "
"innermost block that needs to be closed is 'for'.")
assert_error('{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}',
"Encountered unknown tag 'endfor'. Jinja was looking for "
"the following tags: 'elif' or 'else' or 'endif'. The "
"innermost block that needs to be closed is 'if'.")
assert_error('{% if foo %}',
"Unexpected end of template. Jinja was looking for the "
"following tags: 'elif' or 'else' or 'endif'. The "
"innermost block that needs to be closed is 'if'.")
assert_error('{% for item in seq %}',
"Unexpected end of template. Jinja was looking for the "
"following tags: 'endfor' or 'else'. The innermost block "
"that needs to be closed is 'for'.")
assert_error('{% block foo-bar-baz %}',
"Block names in Jinja have to be valid Python identifiers "
"and may not contain hypens, use an underscore instead.")
assert_error('{% unknown_tag %}',
"Encountered unknown tag 'unknown_tag'.")
class SyntaxTestCase(JinjaTestCase):
def test_call(self):
env = Environment()
env.globals['foo'] = lambda a, b, c, e, g: a + b + c + e + g
tmpl = env.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}")
assert tmpl.render() == 'abdfh'
def test_slicing(self):
tmpl = env.from_string('{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}')
assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
def test_attr(self):
tmpl = env.from_string("{{ foo.bar }}|{{ foo['bar'] }}")
assert tmpl.render(foo={'bar': 42}) == '42|42'
def test_subscript(self):
tmpl = env.from_string("{{ foo[0] }}|{{ foo[-1] }}")
assert tmpl.render(foo=[0, 1, 2]) == '0|2'
def test_tuple(self):
tmpl = env.from_string('{{ () }}|{{ (1,) }}|{{ (1, 2) }}')
assert tmpl.render() == '()|(1,)|(1, 2)'
def test_math(self):
tmpl = env.from_string('{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}')
assert tmpl.render() == '1.5|8'
def test_div(self):
tmpl = env.from_string('{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}')
assert tmpl.render() == '1|1.5|1'
def test_unary(self):
tmpl = env.from_string('{{ +3 }}|{{ -3 }}')
assert tmpl.render() == '3|-3'
def test_concat(self):
tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}")
assert tmpl.render() == '[1, 2]foo'
def test_compare(self):
tmpl = env.from_string('{{ 1 > 0 }}|{{ 1 >= 1 }}|{{ 2 < 3 }}|'
'{{ 2 == 2 }}|{{ 1 <= 1 }}')
assert tmpl.render() == 'True|True|True|True|True'
def test_inop(self):
tmpl = env.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}')
assert tmpl.render() == 'True|False'
def test_literals(self):
tmpl = env.from_string('{{ [] }}|{{ {} }}|{{ () }}')
assert tmpl.render().lower() == '[]|{}|()'
def test_bool(self):
tmpl = env.from_string('{{ true and false }}|{{ false '
'or true }}|{{ not false }}')
assert tmpl.render() == 'False|True|True'
def test_grouping(self):
tmpl = env.from_string('{{ (true and false) or (false and true) and not false }}')
assert tmpl.render() == 'False'
def test_django_attr(self):
tmpl = env.from_string('{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}')
assert tmpl.render() == '1|1'
def test_conditional_expression(self):
tmpl = env.from_string('''{{ 0 if true else 1 }}''')
assert tmpl.render() == '0'
def test_short_conditional_expression(self):
tmpl = env.from_string('<{{ 1 if false }}>')
assert tmpl.render() == '<>'
tmpl = env.from_string('<{{ (1 if false).bar }}>')
self.assert_raises(UndefinedError, tmpl.render)
def test_filter_priority(self):
tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}')
assert tmpl.render() == 'FOOBAR'
def test_function_calls(self):
tests = [
(True, '*foo, bar'),
(True, '*foo, *bar'),
(True, '*foo, bar=42'),
(True, '**foo, *bar'),
(True, '**foo, bar'),
(False, 'foo, bar'),
(False, 'foo, bar=42'),
(False, 'foo, bar=23, *args'),
(False, 'a, b=c, *d, **e'),
(False, '*foo, **bar')
]
for should_fail, sig in tests:
if should_fail:
self.assert_raises(TemplateSyntaxError,
env.from_string, '{{ foo(%s) }}' % sig)
else:
env.from_string('foo(%s)' % sig)
def test_tuple_expr(self):
for tmpl in [
'{{ () }}',
'{{ (1, 2) }}',
'{{ (1, 2,) }}',
'{{ 1, }}',
'{{ 1, 2 }}',
'{% for foo, bar in seq %}...{% endfor %}',
'{% for x in foo, bar %}...{% endfor %}',
'{% for x in foo, %}...{% endfor %}'
]:
assert env.from_string(tmpl)
def test_trailing_comma(self):
tmpl = env.from_string('{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}')
assert tmpl.render().lower() == '(1, 2)|[1, 2]|{1: 2}'
def test_block_end_name(self):
env.from_string('{% block foo %}...{% endblock foo %}')
self.assert_raises(TemplateSyntaxError, env.from_string,
'{% block x %}{% endblock y %}')
def test_contant_casing(self):
for const in True, False, None:
tmpl = env.from_string('{{ %s }}|{{ %s }}|{{ %s }}' % (
str(const), str(const).lower(), str(const).upper()
))
assert tmpl.render() == '%s|%s|' % (const, const)
def test_test_chaining(self):
self.assert_raises(TemplateSyntaxError, env.from_string,
'{{ foo is string is sequence }}')
env.from_string('{{ 42 is string or 42 is number }}'
).render() == 'True'
def test_string_concatenation(self):
tmpl = env.from_string('{{ "foo" "bar" "baz" }}')
assert tmpl.render() == 'foobarbaz'
def test_notin(self):
bar = xrange(100)
tmpl = env.from_string('''{{ not 42 in bar }}''')
assert tmpl.render(bar=bar) == unicode(not 42 in bar)
def test_implicit_subscribed_tuple(self):
class Foo(object):
def __getitem__(self, x):
return x
t = env.from_string('{{ foo[1, 2] }}')
assert t.render(foo=Foo()) == u'(1, 2)'
def test_raw2(self):
tmpl = env.from_string('{% raw %}{{ FOO }} and {% BAR %}{% endraw %}')
assert tmpl.render() == '{{ FOO }} and {% BAR %}'
def test_const(self):
tmpl = env.from_string('{{ true }}|{{ false }}|{{ none }}|'
'{{ none is defined }}|{{ missing is defined }}')
assert tmpl.render() == 'True|False|None|True|False'
def test_neg_filter_priority(self):
node = env.parse('{{ -1|foo }}')
assert isinstance(node.body[0].nodes[0], nodes.Filter)
assert isinstance(node.body[0].nodes[0].node, nodes.Neg)
def test_const_assign(self):
constass1 = '''{% set true = 42 %}'''
constass2 = '''{% for none in seq %}{% endfor %}'''
for tmpl in constass1, constass2:
self.assert_raises(TemplateSyntaxError, env.from_string, tmpl)
def test_localset(self):
tmpl = env.from_string('''{% set foo = 0 %}\
{% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
{{ foo }}''')
assert tmpl.render() == '0'
def test_parse_unary(self):
tmpl = env.from_string('{{ -foo["bar"] }}')
assert tmpl.render(foo={'bar': 42}) == '-42'
tmpl = env.from_string('{{ -foo["bar"]|abs }}')
assert tmpl.render(foo={'bar': 42}) == '42'
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(LexerTestCase))
suite.addTest(unittest.makeSuite(ParserTestCase))
suite.addTest(unittest.makeSuite(SyntaxTestCase))
return suite
-190
View File
@@ -1,190 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.loader
~~~~~~~~~~~~~~~~~~~~~~~
Test the loaders.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import os
import sys
import tempfile
import shutil
import unittest
from jinja2.testsuite import JinjaTestCase, dict_loader, \
package_loader, filesystem_loader, function_loader, \
choice_loader, prefix_loader
from jinja2 import Environment, loaders
from jinja2.loaders import split_template_path
from jinja2.exceptions import TemplateNotFound
class LoaderTestCase(JinjaTestCase):
def test_dict_loader(self):
env = Environment(loader=dict_loader)
tmpl = env.get_template('justdict.html')
assert tmpl.render().strip() == 'FOO'
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
def test_package_loader(self):
env = Environment(loader=package_loader)
tmpl = env.get_template('test.html')
assert tmpl.render().strip() == 'BAR'
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
def test_filesystem_loader(self):
env = Environment(loader=filesystem_loader)
tmpl = env.get_template('test.html')
assert tmpl.render().strip() == 'BAR'
tmpl = env.get_template('foo/test.html')
assert tmpl.render().strip() == 'FOO'
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
def test_choice_loader(self):
env = Environment(loader=choice_loader)
tmpl = env.get_template('justdict.html')
assert tmpl.render().strip() == 'FOO'
tmpl = env.get_template('test.html')
assert tmpl.render().strip() == 'BAR'
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
def test_function_loader(self):
env = Environment(loader=function_loader)
tmpl = env.get_template('justfunction.html')
assert tmpl.render().strip() == 'FOO'
self.assert_raises(TemplateNotFound, env.get_template, 'missing.html')
def test_prefix_loader(self):
env = Environment(loader=prefix_loader)
tmpl = env.get_template('a/test.html')
assert tmpl.render().strip() == 'BAR'
tmpl = env.get_template('b/justdict.html')
assert tmpl.render().strip() == 'FOO'
self.assert_raises(TemplateNotFound, env.get_template, 'missing')
def test_caching(self):
changed = False
class TestLoader(loaders.BaseLoader):
def get_source(self, environment, template):
return u'foo', None, lambda: not changed
env = Environment(loader=TestLoader(), cache_size=-1)
tmpl = env.get_template('template')
assert tmpl is env.get_template('template')
changed = True
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')
env = Environment(loader=TestLoader(), 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
def test_split_template_path(self):
assert split_template_path('foo/bar') == ['foo', 'bar']
assert split_template_path('./foo/bar') == ['foo', 'bar']
self.assert_raises(TemplateNotFound, split_template_path, '../foo')
class ModuleLoaderTestCase(JinjaTestCase):
archive = None
def compile_down(self, zip='deflated', py_compile=False):
super(ModuleLoaderTestCase, self).setup()
log = []
self.reg_env = Environment(loader=prefix_loader)
if zip is not None:
self.archive = tempfile.mkstemp(suffix='.zip')[1]
else:
self.archive = tempfile.mkdtemp()
self.reg_env.compile_templates(self.archive, zip=zip,
log_function=log.append,
py_compile=py_compile)
self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive))
return ''.join(log)
def teardown(self):
super(ModuleLoaderTestCase, self).teardown()
if hasattr(self, 'mod_env'):
if os.path.isfile(self.archive):
os.remove(self.archive)
else:
shutil.rmtree(self.archive)
self.archive = None
def test_log(self):
log = self.compile_down()
assert 'Compiled "a/foo/test.html" as ' \
'tmpl_a790caf9d669e39ea4d280d597ec891c4ef0404a' in log
assert 'Finished compiling templates' in log
assert 'Could not compile "a/syntaxerror.html": ' \
'Encountered unknown tag \'endif\'' in log
def _test_common(self):
tmpl1 = self.reg_env.get_template('a/test.html')
tmpl2 = self.mod_env.get_template('a/test.html')
assert tmpl1.render() == tmpl2.render()
tmpl1 = self.reg_env.get_template('b/justdict.html')
tmpl2 = self.mod_env.get_template('b/justdict.html')
assert tmpl1.render() == tmpl2.render()
def test_deflated_zip_compile(self):
self.compile_down(zip='deflated')
self._test_common()
def test_stored_zip_compile(self):
self.compile_down(zip='stored')
self._test_common()
def test_filesystem_compile(self):
self.compile_down(zip=None)
self._test_common()
def test_weak_references(self):
self.compile_down()
tmpl = self.mod_env.get_template('a/test.html')
key = loaders.ModuleLoader.get_template_key('a/test.html')
name = self.mod_env.loader.module.__name__
assert hasattr(self.mod_env.loader.module, key)
assert name in sys.modules
# unset all, ensure the module is gone from sys.modules
self.mod_env = tmpl = None
try:
import gc
gc.collect()
except:
pass
assert name not in sys.modules
def test_byte_compilation(self):
log = self.compile_down(py_compile=True)
assert 'Byte-compiled "a/test.html"' in log
tmpl1 = self.mod_env.get_template('a/test.html')
mod = self.mod_env.loader.module. \
tmpl_3c4ddf650c1a73df961a6d3d2ce2752f1b8fd490
assert mod.__file__.endswith('.pyc')
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(LoaderTestCase))
suite.addTest(unittest.makeSuite(ModuleLoaderTestCase))
return suite
-255
View File
@@ -1,255 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.regression
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests corner cases and bugs.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
TemplateNotFound, PrefixLoader
env = Environment()
class CornerTestCase(JinjaTestCase):
def test_assigned_scoping(self):
t = env.from_string('''
{%- for item in (1, 2, 3, 4) -%}
[{{ item }}]
{%- endfor %}
{{- item -}}
''')
assert t.render(item=42) == '[1][2][3][4]42'
t = env.from_string('''
{%- for item in (1, 2, 3, 4) -%}
[{{ item }}]
{%- endfor %}
{%- set item = 42 %}
{{- item -}}
''')
assert t.render() == '[1][2][3][4]42'
t = env.from_string('''
{%- set item = 42 %}
{%- for item in (1, 2, 3, 4) -%}
[{{ item }}]
{%- endfor %}
{{- item -}}
''')
assert t.render() == '[1][2][3][4]42'
def test_closure_scoping(self):
t = env.from_string('''
{%- set wrapper = "<FOO>" %}
{%- for item in (1, 2, 3, 4) %}
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
{{- wrapper() }}
{%- endfor %}
{{- wrapper -}}
''')
assert t.render() == '[1][2][3][4]<FOO>'
t = env.from_string('''
{%- for item in (1, 2, 3, 4) %}
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
{{- wrapper() }}
{%- endfor %}
{%- set wrapper = "<FOO>" %}
{{- wrapper -}}
''')
assert t.render() == '[1][2][3][4]<FOO>'
t = env.from_string('''
{%- for item in (1, 2, 3, 4) %}
{%- macro wrapper() %}[{{ item }}]{% endmacro %}
{{- wrapper() }}
{%- endfor %}
{{- wrapper -}}
''')
assert t.render(wrapper=23) == '[1][2][3][4]23'
class BugTestCase(JinjaTestCase):
def test_keyword_folding(self):
env = Environment()
env.filters['testing'] = lambda value, some: value + some
assert env.from_string("{{ 'test'|testing(some='stuff') }}") \
.render() == 'teststuff'
def test_extends_output_bugs(self):
env = Environment(loader=DictLoader({
'parent.html': '(({% block title %}{% endblock %}))'
}))
t = env.from_string('{% if expr %}{% extends "parent.html" %}{% endif %}'
'[[{% block title %}title{% endblock %}]]'
'{% for item in [1, 2, 3] %}({{ item }}){% endfor %}')
assert t.render(expr=False) == '[[title]](1)(2)(3)'
assert t.render(expr=True) == '((title))'
def test_urlize_filter_escaping(self):
tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}')
assert tmpl.render() == '<a href="http://www.example.org/&lt;foo">http://www.example.org/&lt;foo</a>'
def test_loop_call_loop(self):
tmpl = env.from_string('''
{% macro test() %}
{{ caller() }}
{% endmacro %}
{% for num1 in range(5) %}
{% call test() %}
{% for num2 in range(10) %}
{{ loop.index }}
{% endfor %}
{% endcall %}
{% endfor %}
''')
assert tmpl.render().split() == map(unicode, range(1, 11)) * 5
def test_weird_inline_comment(self):
env = Environment(line_statement_prefix='%')
self.assert_raises(TemplateSyntaxError, env.from_string,
'% for item in seq {# missing #}\n...% endfor')
def test_old_macro_loop_scoping_bug(self):
tmpl = env.from_string('{% for i in (1, 2) %}{{ i }}{% endfor %}'
'{% macro i() %}3{% endmacro %}{{ i() }}')
assert tmpl.render() == '123'
def test_partial_conditional_assignments(self):
tmpl = env.from_string('{% if b %}{% set a = 42 %}{% endif %}{{ a }}')
assert tmpl.render(a=23) == '23'
assert tmpl.render(b=True) == '42'
def test_stacked_locals_scoping_bug(self):
env = Environment(line_statement_prefix='#')
t = env.from_string('''\
# for j in [1, 2]:
# set x = 1
# for i in [1, 2]:
# print x
# if i % 2 == 0:
# set x = x + 1
# endif
# endfor
# endfor
# if a
# print 'A'
# elif b
# print 'B'
# elif c == d
# print 'C'
# else
# print 'D'
# endif
''')
assert t.render(a=0, b=False, c=42, d=42.0) == '1111C'
def test_stacked_locals_scoping_bug_twoframe(self):
t = Template('''
{% set x = 1 %}
{% for item in foo %}
{% if item == 1 %}
{% set x = 2 %}
{% endif %}
{% endfor %}
{{ x }}
''')
rv = t.render(foo=[1]).strip()
assert rv == u'1'
def test_call_with_args(self):
t = Template("""{% macro dump_users(users) -%}
<ul>
{%- for user in users -%}
<li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
{%- endfor -%}
</ul>
{%- endmacro -%}
{% call(user) dump_users(list_of_user) -%}
<dl>
<dl>Realname</dl>
<dd>{{ user.realname|e }}</dd>
<dl>Description</dl>
<dd>{{ user.description }}</dd>
</dl>
{% endcall %}""")
assert [x.strip() for x in t.render(list_of_user=[{
'username':'apo',
'realname':'something else',
'description':'test'
}]).splitlines()] == [
u'<ul><li><p>apo</p><dl>',
u'<dl>Realname</dl>',
u'<dd>something else</dd>',
u'<dl>Description</dl>',
u'<dd>test</dd>',
u'</dl>',
u'</li></ul>'
]
def test_empty_if_condition_fails(self):
self.assert_raises(TemplateSyntaxError, Template, '{% if %}....{% endif %}')
self.assert_raises(TemplateSyntaxError, Template, '{% if foo %}...{% elif %}...{% endif %}')
self.assert_raises(TemplateSyntaxError, Template, '{% for x in %}..{% endfor %}')
def test_recursive_loop_bug(self):
tpl1 = Template("""
{% for p in foo recursive%}
{{p.bar}}
{% for f in p.fields recursive%}
{{f.baz}}
{{p.bar}}
{% if f.rec %}
{{ loop(f.sub) }}
{% endif %}
{% endfor %}
{% endfor %}
""")
tpl2 = Template("""
{% for p in foo%}
{{p.bar}}
{% for f in p.fields recursive%}
{{f.baz}}
{{p.bar}}
{% if f.rec %}
{{ loop(f.sub) }}
{% endif %}
{% endfor %}
{% endfor %}
""")
def test_correct_prefix_loader_name(self):
env = Environment(loader=PrefixLoader({
'foo': DictLoader({})
}))
try:
env.get_template('foo/bar.html')
except TemplateNotFound, e:
assert e.name == 'foo/bar.html'
else:
assert False, 'expected error here'
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(CornerTestCase))
suite.addTest(unittest.makeSuite(BugTestCase))
return suite
@@ -1,3 +0,0 @@
Before
{{ fail() }}
After
@@ -1 +0,0 @@
FOO
@@ -1,4 +0,0 @@
Foo
{% for item in broken %}
...
{% endif %}
@@ -1 +0,0 @@
BAR
-165
View File
@@ -1,165 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.security
~~~~~~~~~~~~~~~~~~~~~~~~~
Checks the sandbox and other security features.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Environment
from jinja2.sandbox import SandboxedEnvironment, \
ImmutableSandboxedEnvironment, unsafe
from jinja2 import Markup, escape
from jinja2.exceptions import SecurityError, TemplateSyntaxError, \
TemplateRuntimeError
class PrivateStuff(object):
def bar(self):
return 23
@unsafe
def foo(self):
return 42
def __repr__(self):
return 'PrivateStuff'
class PublicStuff(object):
bar = lambda self: 23
_foo = lambda self: 42
def __repr__(self):
return 'PublicStuff'
class SandboxTestCase(JinjaTestCase):
def test_unsafe(self):
env = SandboxedEnvironment()
self.assert_raises(SecurityError, env.from_string("{{ foo.foo() }}").render,
foo=PrivateStuff())
self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PrivateStuff()), '23')
self.assert_raises(SecurityError, env.from_string("{{ foo._foo() }}").render,
foo=PublicStuff())
self.assert_equal(env.from_string("{{ foo.bar() }}").render(foo=PublicStuff()), '23')
self.assert_equal(env.from_string("{{ foo.__class__ }}").render(foo=42), '')
self.assert_equal(env.from_string("{{ foo.func_code }}").render(foo=lambda:None), '')
# security error comes from __class__ already.
self.assert_raises(SecurityError, env.from_string(
"{{ foo.__class__.__subclasses__() }}").render, foo=42)
def test_immutable_environment(self):
env = ImmutableSandboxedEnvironment()
self.assert_raises(SecurityError, env.from_string(
'{{ [].append(23) }}').render)
self.assert_raises(SecurityError, env.from_string(
'{{ {1:2}.clear() }}').render)
def test_restricted(self):
env = SandboxedEnvironment()
self.assert_raises(TemplateSyntaxError, env.from_string,
"{% for item.attribute in seq %}...{% endfor %}")
self.assert_raises(TemplateSyntaxError, env.from_string,
"{% for foo, bar.baz in seq %}...{% endfor %}")
def test_markup_operations(self):
# adding two strings should escape the unsafe one
unsafe = '<script type="application/x-some-script">alert("foo");</script>'
safe = Markup('<em>username</em>')
assert unsafe + safe == unicode(escape(unsafe)) + unicode(safe)
# string interpolations are safe to use too
assert Markup('<em>%s</em>') % '<bad user>' == \
'<em>&lt;bad user&gt;</em>'
assert Markup('<em>%(username)s</em>') % {
'username': '<bad user>'
} == '<em>&lt;bad user&gt;</em>'
# an escaped object is markup too
assert type(Markup('foo') + 'bar') is Markup
# and it implements __html__ by returning itself
x = Markup("foo")
assert x.__html__() is x
# it also knows how to treat __html__ objects
class Foo(object):
def __html__(self):
return '<em>awesome</em>'
def __unicode__(self):
return 'awesome'
assert Markup(Foo()) == '<em>awesome</em>'
assert Markup('<strong>%s</strong>') % Foo() == \
'<strong><em>awesome</em></strong>'
# escaping and unescaping
assert escape('"<>&\'') == '&#34;&lt;&gt;&amp;&#39;'
assert Markup("<em>Foo &amp; Bar</em>").striptags() == "Foo & Bar"
assert Markup("&lt;test&gt;").unescape() == "<test>"
def test_template_data(self):
env = Environment(autoescape=True)
t = env.from_string('{% macro say_hello(name) %}'
'<p>Hello {{ name }}!</p>{% endmacro %}'
'{{ say_hello("<blink>foo</blink>") }}')
escaped_out = '<p>Hello &lt;blink&gt;foo&lt;/blink&gt;!</p>'
assert t.render() == escaped_out
assert unicode(t.module) == escaped_out
assert escape(t.module) == escaped_out
assert t.module.say_hello('<blink>foo</blink>') == escaped_out
assert escape(t.module.say_hello('<blink>foo</blink>')) == escaped_out
def test_attr_filter(self):
env = SandboxedEnvironment()
tmpl = env.from_string('{{ cls|attr("__subclasses__")() }}')
self.assert_raises(SecurityError, tmpl.render, cls=int)
def test_binary_operator_intercepting(self):
def disable_op(left, right):
raise TemplateRuntimeError('that operator so does not work')
for expr, ctx, rv in ('1 + 2', {}, '3'), ('a + 2', {'a': 2}, '4'):
env = SandboxedEnvironment()
env.binop_table['+'] = disable_op
t = env.from_string('{{ %s }}' % expr)
assert t.render(ctx) == rv
env.intercepted_binops = frozenset(['+'])
t = env.from_string('{{ %s }}' % expr)
try:
t.render(ctx)
except TemplateRuntimeError, e:
pass
else:
self.fail('expected runtime error')
def test_unary_operator_intercepting(self):
def disable_op(arg):
raise TemplateRuntimeError('that operator so does not work')
for expr, ctx, rv in ('-1', {}, '-1'), ('-a', {'a': 2}, '-2'):
env = SandboxedEnvironment()
env.unop_table['-'] = disable_op
t = env.from_string('{{ %s }}' % expr)
assert t.render(ctx) == rv
env.intercepted_unops = frozenset(['-'])
t = env.from_string('{{ %s }}' % expr)
try:
t.render(ctx)
except TemplateRuntimeError, e:
pass
else:
self.fail('expected runtime error')
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(SandboxTestCase))
return suite
-93
View File
@@ -1,93 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.tests
~~~~~~~~~~~~~~~~~~~~~~
Who tests the tests?
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import unittest
from jinja2.testsuite import JinjaTestCase
from jinja2 import Markup, Environment
env = Environment()
class TestsTestCase(JinjaTestCase):
def test_defined(self):
tmpl = env.from_string('{{ missing is defined }}|{{ true is defined }}')
assert tmpl.render() == 'False|True'
def test_even(self):
tmpl = env.from_string('''{{ 1 is even }}|{{ 2 is even }}''')
assert tmpl.render() == 'False|True'
def test_odd(self):
tmpl = env.from_string('''{{ 1 is odd }}|{{ 2 is odd }}''')
assert tmpl.render() == 'True|False'
def test_lower(self):
tmpl = env.from_string('''{{ "foo" is lower }}|{{ "FOO" is lower }}''')
assert tmpl.render() == 'True|False'
def test_typechecks(self):
tmpl = env.from_string('''
{{ 42 is undefined }}
{{ 42 is defined }}
{{ 42 is none }}
{{ none is none }}
{{ 42 is number }}
{{ 42 is string }}
{{ "foo" is string }}
{{ "foo" is sequence }}
{{ [1] is sequence }}
{{ range is callable }}
{{ 42 is callable }}
{{ range(5) is iterable }}
{{ {} is mapping }}
{{ mydict is mapping }}
{{ [] is mapping }}
''')
class MyDict(dict):
pass
assert tmpl.render(mydict=MyDict()).split() == [
'False', 'True', 'False', 'True', 'True', 'False',
'True', 'True', 'True', 'True', 'False', 'True',
'True', 'True', 'False'
]
def test_sequence(self):
tmpl = env.from_string(
'{{ [1, 2, 3] is sequence }}|'
'{{ "foo" is sequence }}|'
'{{ 42 is sequence }}'
)
assert tmpl.render() == 'True|True|False'
def test_upper(self):
tmpl = env.from_string('{{ "FOO" is upper }}|{{ "foo" is upper }}')
assert tmpl.render() == 'True|False'
def test_sameas(self):
tmpl = env.from_string('{{ foo is sameas false }}|'
'{{ 0 is sameas false }}')
assert tmpl.render(foo=False) == 'True|False'
def test_no_paren_for_arg1(self):
tmpl = env.from_string('{{ foo is sameas none }}')
assert tmpl.render(foo=None) == 'True'
def test_escaped(self):
env = Environment(autoescape=True)
tmpl = env.from_string('{{ x is escaped }}|{{ y is escaped }}')
assert tmpl.render(x='foo', y=Markup('foo')) == 'False|True'
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestsTestCase))
return suite
-82
View File
@@ -1,82 +0,0 @@
# -*- coding: utf-8 -*-
"""
jinja2.testsuite.utils
~~~~~~~~~~~~~~~~~~~~~~
Tests utilities jinja uses.
:copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import gc
import unittest
import pickle
from jinja2.testsuite import JinjaTestCase
from jinja2.utils import LRUCache, escape, object_type_repr
class LRUCacheTestCase(JinjaTestCase):
def test_simple(self):
d = LRUCache(3)
d["a"] = 1
d["b"] = 2
d["c"] = 3
d["a"]
d["d"] = 4
assert len(d) == 3
assert 'a' in d and 'c' in d and 'd' in d and 'b' not in d
def test_pickleable(self):
cache = LRUCache(2)
cache["foo"] = 42
cache["bar"] = 23
cache["foo"]
for protocol in range(3):
copy = pickle.loads(pickle.dumps(cache, protocol))
assert copy.capacity == cache.capacity
assert copy._mapping == cache._mapping
assert copy._queue == cache._queue
class HelpersTestCase(JinjaTestCase):
def test_object_type_repr(self):
class X(object):
pass
self.assert_equal(object_type_repr(42), 'int object')
self.assert_equal(object_type_repr([]), 'list object')
self.assert_equal(object_type_repr(X()),
'jinja2.testsuite.utils.X object')
self.assert_equal(object_type_repr(None), 'None')
self.assert_equal(object_type_repr(Ellipsis), 'Ellipsis')
class MarkupLeakTestCase(JinjaTestCase):
def test_markup_leaks(self):
counts = set()
for count in xrange(20):
for item in xrange(1000):
escape("foo")
escape("<foo>")
escape(u"foo")
escape(u"<foo>")
counts.add(len(gc.get_objects()))
assert len(counts) == 1, 'ouch, c extension seems to leak objects'
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(LRUCacheTestCase))
suite.addTest(unittest.makeSuite(HelpersTestCase))
# this test only tests the c extension
if not hasattr(escape, 'func_code'):
suite.addTest(unittest.makeSuite(MarkupLeakTestCase))
return suite
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+1 -1
View File
@@ -19,7 +19,7 @@ import sys
# the version. Usually set automatically by a script.
__version__ = '0.8-dev'
__version__ = '0.9-dev'
# This import magic raises concerns quite often which is why the implementation
Regular → Executable
View File
View File
Regular → Executable
View File
Regular → Executable
+112 -81
View File
@@ -65,9 +65,14 @@ except ImportError:
from md5 import new as md5
from itertools import izip
from time import time
from cPickle import loads, dumps, load, dump, HIGHEST_PROTOCOL
from werkzeug.posixemulation import rename
try:
import cPickle as pickle
except ImportError:
import pickle
def _items(mappingorseq):
"""Wrapper for efficient iteration over mappings represented by dicts
or sequences::
@@ -82,6 +87,7 @@ def _items(mappingorseq):
return mappingorseq.iteritems() if hasattr(mappingorseq, 'iteritems') \
else mappingorseq
class BaseCache(object):
"""Baseclass for the cache systems. All the cache systems implement this
API or a superset of it.
@@ -242,20 +248,22 @@ class SimpleCache(BaseCache):
now = time()
expires, value = self._cache.get(key, (0, None))
if expires > time():
return loads(value)
return pickle.loads(value)
def set(self, key, value, timeout=None):
if timeout is None:
timeout = self.default_timeout
self._prune()
self._cache[key] = (time() + timeout, dumps(value, HIGHEST_PROTOCOL))
self._cache[key] = (time() + timeout, pickle.dumps(value,
pickle.HIGHEST_PROTOCOL))
def add(self, key, value, timeout=None):
if timeout is None:
timeout = self.default_timeout
if len(self._cache) > self._threshold:
self._prune()
item = (time() + timeout, dumps(value, HIGHEST_PROTOCOL))
item = (time() + timeout, pickle.dumps(value,
pickle.HIGHEST_PROTOCOL))
self._cache.setdefault(key, item)
def delete(self, key):
@@ -267,9 +275,10 @@ _test_memcached_key = re.compile(r'[^\x00-\x21\xff]{1,250}$').match
class MemcachedCache(BaseCache):
"""A cache that uses memcached as backend.
The first argument can either be a list or tuple of server addresses
in which case Werkzeug tries to import the memcache module and connect
to it, or an object that resembles the API of a :class:`memcache.Client`.
The first argument can either be an object that resembles the API of a
:class:`memcache.Client` or a tuple/list of server addresses. In the
event that a tuple/list is passed, Werkzeug tries to import the best
available memcache library.
Implementation notes: This cache backend works around some limitations in
memcached to simplify the interface. For example unicode keys are encoded
@@ -289,47 +298,19 @@ class MemcachedCache(BaseCache):
different prefix.
"""
def __init__(self, servers, default_timeout=300, key_prefix=None):
def __init__(self, servers=None, default_timeout=300, key_prefix=None):
BaseCache.__init__(self, default_timeout)
if isinstance(servers, (list, tuple)):
is_cmemcached = is_cmemcache = is_pylibmc = False
try:
import cmemcached as memcache
is_cmemcached = True
except ImportError:
try:
import cmemcache as memcache
is_cmemcache = True
except ImportError:
try:
import memcache
is_cmemcache = False
is_pylibmc = False
except ImportError:
try:
import pylibmc as memcache
is_cmemcache = False
is_pylibmc = True
except ImportError:
raise RuntimeError('no memcache module found')
if is_cmemcache:
# cmemcache has a bug that debuglog is not defined for the
# client. Whenever pickle fails you get a weird
# AttributeError.
client = memcache.Client(map(str, servers))
try:
client.debuglog = lambda *a: None
except Exception:
pass
elif is_pylibmc or is_cmemcached:
client = memcache.Client(servers, False)
else:
client = memcache.Client(servers, False, HIGHEST_PROTOCOL)
if servers is None or isinstance(servers, (list, tuple)):
if servers is None:
servers = ['127.0.0.1:11211']
self._client = self.import_preferred_memcache_lib(servers)
if self._client is None:
raise RuntimeError('no memcache module found')
else:
client = servers
# NOTE: servers is actually an already initialized memcache
# client.
self._client = servers
self._client = client
self.key_prefix = key_prefix
def get(self, key):
@@ -356,9 +337,6 @@ class MemcachedCache(BaseCache):
encoded_key = self.key_prefix + encoded_key
if _test_memcached_key(key):
key_mapping[encoded_key] = key
# the keys call here is important because otherwise cmemcache
# does ugly things. What exactly I don't know, I think it does
# Py_DECREF but quite frankly I don't care.
d = rv = self._client.get_multi(key_mapping.keys())
if have_encoded_keys or self.key_prefix:
rv = {}
@@ -440,23 +418,32 @@ class MemcachedCache(BaseCache):
key = self.key_prefix + key
self._client.decr(key, delta)
def import_preferred_memcache_lib(self, servers):
"""Returns an initialized memcache client. Used by the constructor."""
try:
import pylibmc
except ImportError:
pass
else:
return pylibmc.Client(servers)
class GAEMemcachedCache(MemcachedCache):
"""Connects to the Google appengine memcached Cache.
try:
from google.appengine.api import memcache
except ImportError:
pass
else:
return memcache.Client()
:param default_timeout: the default timeout that is used if no timeout is
specified on :meth:`~BaseCache.set`.
:param key_prefix: a prefix that is added before all keys. This makes it
possible to use the same memcached server for different
applications. Keep in mind that
:meth:`~BaseCache.clear` will also clear keys with a
different prefix.
"""
try:
import memcache
except ImportError:
pass
else:
return memcache.Client(servers)
def __init__(self, default_timeout=300, key_prefix=None):
from google.appengine.api import memcache
MemcachedCache.__init__(self, memcache.Client(),
default_timeout, key_prefix)
# backwards compatibility
GAEMemcachedCache = MemcachedCache
class RedisCache(BaseCache):
@@ -470,64 +457,109 @@ class RedisCache(BaseCache):
.. versionadded:: 0.7
.. versionadded:: 0.8
`key_prefix` was added.
.. versionchanged:: 0.8
This cache backend now properly serializes objects.
:param host: address of the Redis server or an object which API is
compatible with the official Python Redis client (redis-py).
:param port: port number on which Redis server listens for connections
:param default_timeout: the default timeout that is used if no timeout is
specified on :meth:`~BaseCache.set`.
:param key_prefix: A prefix that should be added to all keys.
"""
def __init__(self, host='localhost', port=6379, default_timeout=300):
def __init__(self, host='localhost', port=6379, password=None,
default_timeout=300, key_prefix=None):
BaseCache.__init__(self, default_timeout)
if isinstance(host, basestring):
try:
import redis
except ImportError:
raise RuntimeError('no redis module found')
self._client = redis.Redis(host=host, port=port)
self._client = redis.Redis(host=host, port=port, password=password)
else:
self._client = host
self.key_prefix = key_prefix or ''
def dump_object(self, value):
"""Dumps an object into a string for redis. By default it serializes
integers as regular string and pickle dumps everything else.
"""
if isinstance(value, (int, long)):
return str(value)
return '!' + pickle.dumps(value)
def load_object(self, value):
"""The reversal of :meth:`dump_object`. This might be callde with
None.
"""
if value is None:
return None
if value.startswith('!'):
return pickle.loads(value[1:])
try:
return int(value)
except ValueError:
# before 0.8 we did not have serialization. Still support that.
return value
def get(self, key):
return self._client.get(key)
return self.load_object(self._client.get(self.key_prefix + key))
def get_many(self, *keys):
return self._client.mget(keys)
if self.key_prefix:
keys = [self.key_prefix + key for key in keys]
return [self.load_object(x) for x in self._client.mget(keys)]
def set(self, key, value, timeout=None):
if timeout is None:
timeout = self.default_timeout
self._client.setex(key, value, timeout)
dump = self.dump_object(value)
self._client.setex(self.key_prefix + key, dump, timeout)
def add(self, key, value, timeout=None):
if timeout is None:
timeout = self.default_timeout
added = self._client.setnx(key, value)
dump = self.dump_object(value)
added = self._client.setnx(self.key_prefix + key, dump)
if added:
self._client.expire(key, timeout)
self._client.expire(self.key_prefix + key, timeout)
def set_many(self, mapping, timeout=None):
if timeout is None:
timeout = self.default_timeout
pipe = self._client.pipeline()
for key, value in _items(mapping):
pipe.setex(key, value, timeout)
dump = self.dump_object(value)
pipe.setex(self.key_prefix + key, dump, timeout)
pipe.execute()
def delete(self, key):
self._client.delete(key)
self._client.delete(self.key_prefix + key)
def delete_many(self, *keys):
if not keys:
return
if self.key_prefix:
keys = [self.key_prefix + key for key in keys]
self._client.delete(*keys)
def clear(self):
self._client.flushdb()
if self.key_prefix:
keys = self._client.keys(self.key_prefix + '*')
if keys:
self._client.delete(*keys)
else:
self._client.flushdb()
def inc(self, key, delta=1):
return self._client.incr(key, delta)
return self._client.incr(self.key_prefix + key, delta)
def dec(self, key, delta=1):
return self._client.decr(key, delta)
return self._client.decr(self.key_prefix + key, delta)
class FileSystemCache(BaseCache):
@@ -571,7 +603,7 @@ class FileSystemCache(BaseCache):
try:
try:
f = open(fname, 'rb')
expires = load(f)
expires = pickle.load(f)
remove = expires <= now or idx % 3 == 0
finally:
if f is not None:
@@ -600,8 +632,8 @@ class FileSystemCache(BaseCache):
try:
f = open(filename, 'rb')
try:
if load(f) >= time():
return load(f)
if pickle.load(f) >= time():
return pickle.load(f)
finally:
f.close()
os.remove(filename)
@@ -623,8 +655,8 @@ class FileSystemCache(BaseCache):
dir=self._path)
f = os.fdopen(fd, 'wb')
try:
dump(int(time() + timeout), f, 1)
dump(value, f, HIGHEST_PROTOCOL)
pickle.dump(int(time() + timeout), f, 1)
pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
finally:
f.close()
rename(tmp, filename)
@@ -637,4 +669,3 @@ class FileSystemCache(BaseCache):
os.remove(self._get_filename(key))
except (IOError, OSError):
pass
Regular → Executable
+19 -4
View File
@@ -95,18 +95,33 @@ class ProxyFix(object):
def __init__(self, app):
self.app = app
def get_remote_addr(self, forwarded_for):
"""Selects the new remote addr from the given list of ips in
X-Forwarded-For. By default the first one is picked.
.. versionadded:: 0.8
"""
if forwarded_for:
return forwarded_for[0]
def __call__(self, environ, start_response):
getter = environ.get
forwarded_proto = getter('HTTP_X_FORWARDED_PROTO', '')
forwarded_for = getter('HTTP_X_FORWARDED_FOR', '').split(',')
forwarded_host = getter('HTTP_X_FORWARDED_HOST', '')
environ.update({
'werkzeug.proxy_fix.orig_remote_addr': getter('REMOTE_ADDR'),
'werkzeug.proxy_fix.orig_http_host': getter('HTTP_HOST')
'werkzeug.proxy_fix.orig_wsgi_url_scheme': getter('wsgi.url_scheme'),
'werkzeug.proxy_fix.orig_remote_addr': getter('REMOTE_ADDR'),
'werkzeug.proxy_fix.orig_http_host': getter('HTTP_HOST')
})
if forwarded_for:
environ['REMOTE_ADDR'] = forwarded_for[0].strip()
forwarded_for = [x for x in [x.strip() for x in forwarded_for] if x]
remote_addr = self.get_remote_addr(forwarded_for)
if remote_addr is not None:
environ['REMOTE_ADDR'] = remote_addr
if forwarded_host:
environ['HTTP_HOST'] = forwarded_host
if forwarded_proto:
environ['wsgi.url_scheme'] = forwarded_proto
return self.app(environ, start_response)
Regular → Executable
+7 -11
View File
@@ -127,25 +127,22 @@ class IterI(IterIO):
if greenlet is None:
raise RuntimeError('IterI requires greenlet support')
stream = object.__new__(cls)
stream.__init__(greenlet.getcurrent())
stream._parent = greenlet.getcurrent()
stream._buffer = []
stream.closed = False
stream.pos = 0
def run():
func(stream)
stream.flush()
g = greenlet(run, stream._parent)
g = greenlet.greenlet(run, stream._parent)
while 1:
rv = g.switch()
if not rv:
return
yield rv[0]
def __init__(self, parent):
self._parent = parent
self._buffer = []
self.closed = False
self.pos = 0
def close(self):
if not self.closed:
self.closed = True
@@ -171,13 +168,12 @@ class IterO(IterIO):
"""Iter output. Wrap an iterator and give it a stream like interface."""
def __new__(cls, gen):
return object.__new__(cls)
def __init__(self, gen):
self = object.__new__(cls)
self._gen = gen
self._buf = ''
self.closed = False
self.pos = 0
return self
def __iter__(self):
return self
View File
View File
Regular → Executable
View File
Regular → Executable
View File
View File
+1 -16
View File
@@ -88,7 +88,6 @@ r"""
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
import sys
import cPickle as pickle
from hmac import new as hmac
from time import time
@@ -98,21 +97,7 @@ from werkzeug.contrib.sessions import ModificationTrackingDict
from werkzeug.security import safe_str_cmp
# rather ugly way to import the correct hash method. Because
# hmac either accepts modules with a new method (sha, md5 etc.)
# or a hashlib factory function we have to figure out what to
# pass to it. If we have 2.5 or higher (so not 2.4 with a
# custom hashlib) we import from hashlib and fail if it does
# not exist (have seen that in old OS X versions).
# in all other cases the now deprecated sha module is used.
_default_hash = None
if sys.version_info >= (2, 5):
try:
from hashlib import sha1 as _default_hash
except ImportError:
pass
if _default_hash is None:
import sha as _default_hash
from hashlib import sha1 as _default_hash
class UnquoteError(Exception):
+1 -4
View File
@@ -58,10 +58,7 @@ import tempfile
from os import path
from time import time
from random import random
try:
from hashlib import sha1
except ImportError:
from sha import new as sha1
from hashlib import sha1
from cPickle import dump, load, HIGHEST_PROTOCOL
from werkzeug.datastructures import CallbackDict
View File
View File
Regular → Executable
+66 -20
View File
@@ -50,6 +50,14 @@ class ImmutableListMixin(object):
:private:
"""
_hash_cache = None
def __hash__(self):
if self._hash_cache is not None:
return self._hash_cache
rv = self._hash_cache = hash(tuple(self))
return rv
def __reduce_ex__(self, protocol):
return type(self), (list(self),)
@@ -107,6 +115,8 @@ class ImmutableDictMixin(object):
:private:
"""
_hash_cache = None
@classmethod
def fromkeys(cls, keys, value=None):
instance = super(cls, cls).__new__(cls)
@@ -116,6 +126,15 @@ class ImmutableDictMixin(object):
def __reduce_ex__(self, protocol):
return type(self), (dict(self),)
def _iter_hashitems(self):
return self.iteritems()
def __hash__(self):
if self._hash_cache is not None:
return self._hash_cache
rv = self._hash_cache = hash(frozenset(self._iter_hashitems()))
return rv
def setdefault(self, key, default=None):
is_immutable(self)
@@ -149,6 +168,9 @@ class ImmutableMultiDictMixin(ImmutableDictMixin):
def __reduce_ex__(self, protocol):
return type(self), (self.items(multi=True),)
def _iter_hashitems(self):
return self.iteritems(multi=True)
def add(self, key, value):
is_immutable(self)
@@ -561,6 +583,9 @@ class MultiDict(TypeConversionDict):
except KeyError, e:
raise BadRequestKeyError(str(e))
def __copy__(self):
return self.copy()
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.items(multi=True))
@@ -1032,8 +1057,14 @@ class Headers(object):
"""
if kw:
_value = _options_header_vkw(_value, kw)
self._validate_value(_value)
self._list.append((_key, _value))
def _validate_value(self, value):
if isinstance(value, basestring) and ('\n' in value or '\r' in value):
raise ValueError('Detected newline in header value. This is '
'a potential security problem')
def add_header(self, _key, _value, **_kw):
"""Add a new header tuple to the list.
@@ -1063,18 +1094,21 @@ class Headers(object):
"""
if kw:
_value = _options_header_vkw(_value, kw)
self._validate_value(_value)
if not self._list:
return self.add(_key, _value)
lc_key = _key.lower()
for idx, (old_key, old_value) in enumerate(self._list):
if old_key.lower() == lc_key:
self._list.append((_key, _value))
return
listiter = iter(self._list)
ikey = _key.lower()
for idx, (old_key, old_value) in enumerate(listiter):
if old_key.lower() == ikey:
# replace first ocurrence
self._list[idx] = (_key, _value)
break
else:
return self.add(_key, _value)
self._list[idx + 1:] = [(k, v) for k, v in self._list[idx + 1:]
if k.lower() != lc_key]
self._list.append((_key, _value))
return
self._list[idx + 1:] = [t for t in listiter if t[0].lower() != ikey]
def setdefault(self, key, value):
"""Returns the value for the key if it is in the dict, otherwise it
@@ -1092,6 +1126,7 @@ class Headers(object):
def __setitem__(self, key, value):
"""Like :meth:`set` but also supports index/slice based setting."""
if isinstance(key, (slice, int, long)):
self._validate_value(value)
self._list[key] = value
else:
self.set(key, value)
@@ -1102,14 +1137,8 @@ class Headers(object):
:return: list
"""
result = []
for k, v in self:
if isinstance(v, unicode):
v = v.encode(charset)
else:
v = str(v)
result.append((k, v))
return result
return [(k, isinstance(v, unicode) and v.encode(charset) or str(v))
for k, v in self]
def copy(self):
return self.__class__(self._list)
@@ -1133,7 +1162,9 @@ class Headers(object):
class ImmutableHeadersMixin(object):
"""Makes a :class:`Headers` immutable.
"""Makes a :class:`Headers` immutable. We do not mark them as
hashable though since the only usecase for this datastructure
in Werkzeug is a view on a mutable structure.
.. versionadded:: 0.5
@@ -1426,6 +1457,9 @@ class ImmutableOrderedMultiDict(ImmutableMultiDictMixin, OrderedMultiDict):
.. versionadded:: 0.6
"""
def _iter_hashitems(self):
return enumerate(self.iteritems(multi=True))
def copy(self):
"""Return a shallow mutable copy of this object. Keep in mind that
the standard library's :func:`copy` function is a no-op for this class
@@ -2045,7 +2079,7 @@ class ETags(object):
return etag in self._strong
def __nonzero__(self):
return bool(self.star_tag or self._strong)
return bool(self.star_tag or self._strong or self._weak)
def __str__(self):
return self.to_header()
@@ -2422,7 +2456,7 @@ class FileStorage(object):
"""
def __init__(self, stream=None, filename=None, name=None,
content_type='application/octet-stream', content_length=-1,
content_type=None, content_length=None,
headers=None):
self.name = name
self.stream = stream or _empty_stream
@@ -2437,17 +2471,29 @@ class FileStorage(object):
filename = None
self.filename = filename
self.content_type = content_type
self.content_length = content_length
if headers is None:
headers = Headers()
self.headers = headers
if content_type is not None:
headers['Content-Type'] = content_type
if content_length is not None:
headers['Content-Length'] = str(content_length)
def _parse_content_type(self):
if not hasattr(self, '_parsed_content_type'):
self._parsed_content_type = \
parse_options_header(self.content_type)
@property
def content_type(self):
"""The file's content type. Usually not available"""
return self.headers.get('content-type')
@property
def content_length(self):
"""The file's content length. Usually not available"""
return int(self.headers.get('content-length') or 0)
@property
def mimetype(self):
"""Like :attr:`content_type` but without parameters (eg, without
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
View File
View File

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 507 B

View File
Vendored Regular → Executable
View File
View File

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

View File

Before

Width:  |  Height:  |  Size: 200 B

After

Width:  |  Height:  |  Size: 200 B

View File

Before

Width:  |  Height:  |  Size: 818 B

After

Width:  |  Height:  |  Size: 818 B

View File
View File
Regular → Executable
View File
Regular → Executable
+15
View File
@@ -179,6 +179,21 @@ class BadRequest(HTTPException):
)
class ClientDisconnected(BadRequest):
"""Internal exception that is raised if Werkzeug detects a disconnected
client. Since the client is already gone at that point attempting to
send the error message to the client might not work and might ultimately
result in another exception in the server. Mainly this is here so that
it is silenced by default as far as Werkzeug is concerned.
Since disconnections cannot be reliably detected and are unspecified
by WSGI to a large extend this might or might not be raised if a client
is gone.
.. versionadded:: 0.8
"""
class Unauthorized(HTTPException):
"""*401* `Unauthorized`
Regular → Executable
+265 -164
View File
@@ -13,9 +13,10 @@ import re
from cStringIO import StringIO
from tempfile import TemporaryFile
from itertools import chain, repeat
from functools import update_wrapper
from werkzeug._internal import _decode_unicode, _empty_stream
from werkzeug.urls import url_decode
from werkzeug.urls import url_decode_stream
from werkzeug.wsgi import LimitedStream, make_line_iter
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.datastructures import Headers, FileStorage, MultiDict
@@ -54,8 +55,7 @@ def parse_form_data(environ, stream_factory=None, charset='utf-8',
mimetype is unknown the input stream is wrapped and returned as first
argument, else the stream is empty.
This function does not raise exceptions, even if the input data is
malformed.
This is a shortcut for the common usage of :class:`FormDataParser`.
Have a look at :ref:`dealing-with-request-data` for more details.
@@ -86,56 +86,140 @@ def parse_form_data(environ, stream_factory=None, charset='utf-8',
:param silent: If set to False parsing errors will not be caught.
:return: A tuple in the form ``(stream, form, files)``.
"""
content_type, extra = parse_options_header(environ.get('CONTENT_TYPE', ''))
try:
content_length = int(environ['CONTENT_LENGTH'])
except (KeyError, ValueError):
content_length = 0
return FormDataParser(stream_factory, charset, errors,
max_form_memory_size, max_content_length,
cls, silent).parse_from_environ(environ)
if cls is None:
cls = MultiDict
if max_content_length is not None and content_length > max_content_length:
raise RequestEntityTooLarge()
stream = _empty_stream
files = ()
if content_type == 'multipart/form-data':
def exhaust_stream(f):
"""Helper decorator for methods that exhausts the stream on return."""
def wrapper(self, stream, *args, **kwargs):
try:
form, files = parse_multipart(environ['wsgi.input'],
extra.get('boundary'),
content_length, stream_factory,
charset, errors,
max_form_memory_size=max_form_memory_size)
except ValueError, e:
if not silent:
raise
form = cls()
else:
form = cls(form)
elif content_type == 'application/x-www-form-urlencoded' or \
content_type == 'application/x-url-encoded':
if max_form_memory_size is not None and \
content_length > max_form_memory_size:
raise RequestEntityTooLarge()
form = url_decode(environ['wsgi.input'].read(content_length),
charset, errors=errors, cls=cls)
else:
form = cls()
stream = LimitedStream(environ['wsgi.input'], content_length)
return stream, form, cls(files)
return f(self, stream, *args, **kwargs)
finally:
stream.exhaust()
return update_wrapper(wrapper, f)
def _fix_ie_filename(filename):
"""Internet Explorer 6 transmits the full file name if a file is
uploaded. This function strips the full path if it thinks the
filename is Windows-like absolute.
class FormDataParser(object):
"""This class implements parsing of form data for Werkzeug. By itself
it can parse multipart and url encoded form data. It can be subclasses
and extended but for most mimetypes it is a better idea to use the
untouched stream and expose it as separate attributes on a request
object.
.. versionadded:: 0.8
:param stream_factory: An optional callable that returns a new read and
writeable file descriptor. This callable works
the same as :meth:`~BaseResponse._get_file_stream`.
:param charset: The character set for URL and url encoded form data.
:param errors: The encoding error behavior.
:param max_form_memory_size: the maximum number of bytes to be accepted for
in-memory stored form data. If the data
exceeds the value specified an
:exc:`~exceptions.RequestURITooLarge`
exception is raised.
:param max_content_length: If this is provided and the transmitted data
is longer than this value an
:exc:`~exceptions.RequestEntityTooLarge`
exception is raised.
:param cls: an optional dict class to use. If this is not specified
or `None` the default :class:`MultiDict` is used.
:param silent: If set to False parsing errors will not be caught.
"""
if filename[1:3] == ':\\' or filename[:2] == '\\\\':
return filename.split('\\')[-1]
return filename
def __init__(self, stream_factory=None, charset='utf-8',
errors='replace', max_form_memory_size=None,
max_content_length=None, cls=None,
silent=True):
if stream_factory is None:
stream_factory = default_stream_factory
self.stream_factory = stream_factory
self.charset = charset
self.errors = errors
self.max_form_memory_size = max_form_memory_size
self.max_content_length = max_content_length
if cls is None:
cls = MultiDict
self.cls = cls
self.silent = silent
def get_parse_func(self, mimetype, options):
return self.parse_functions.get(mimetype)
def parse_from_environ(self, environ):
"""Parses the information from the environment as form data.
:param environ: the WSGI environment to be used for parsing.
:return: A tuple in the form ``(stream, form, files)``.
"""
content_type = environ.get('CONTENT_TYPE', '')
mimetype, options = parse_options_header(content_type)
try:
content_length = int(environ['CONTENT_LENGTH'])
except (KeyError, ValueError):
content_length = 0
stream = environ['wsgi.input']
return self.parse(stream, mimetype, content_length, options)
def parse(self, stream, mimetype, content_length, options=None):
"""Parses the information from the given stream, mimetype,
content length and mimetype parameters.
:param stream: an input stream
:param mimetype: the mimetype of the data
:param content_length: the content length of the incoming data
:param options: optional mimetype parameters (used for
the multipart boundary for instance)
:return: A tuple in the form ``(stream, form, files)``.
"""
if self.max_content_length is not None and \
content_length > self.max_content_length:
raise RequestEntityTooLarge()
if options is None:
options = {}
input_stream = LimitedStream(stream, content_length)
parse_func = self.get_parse_func(mimetype, options)
if parse_func is not None:
try:
return parse_func(self, input_stream, mimetype,
content_length, options)
except ValueError:
if not self.silent:
raise
return input_stream, self.cls(), self.cls()
@exhaust_stream
def _parse_multipart(self, stream, mimetype, content_length, options):
parser = MultiPartParser(self.stream_factory, self.charset, self.errors,
max_form_memory_size=self.max_form_memory_size,
cls=self.cls)
form, files = parser.parse(stream, options.get('boundary'),
content_length)
return _empty_stream, form, files
@exhaust_stream
def _parse_urlencoded(self, stream, mimetype, content_length, options):
if self.max_form_memory_size is not None and \
content_length > self.max_form_memory_size:
raise RequestEntityTooLarge()
form = url_decode_stream(stream, self.charset,
errors=self.errors, cls=self.cls)
return _empty_stream, form, self.cls()
#: mapping of mimetypes to parsing functions
parse_functions = {
'multipart/form-data': _parse_multipart,
'application/x-www-form-urlencoded': _parse_urlencoded,
'application/x-url-encoded': _parse_urlencoded
}
def is_valid_multipart_boundary(boundary):
"""Checks if the string given is a valid multipart boundary."""
return _multipart_boundary_re.match(boundary) is not None
def _line_parse(line):
@@ -149,97 +233,152 @@ def _line_parse(line):
return line, False
def _find_terminator(iterator):
"""The terminator might have some additional newlines before it.
There is at least one application that sends additional newlines
before headers (the python setuptools package).
def parse_multipart_headers(iterable):
"""Parses multipart headers from an iterable that yields lines (including
the trailing newline symbol. The iterable has to be newline terminated:
>>> parse_multipart_headers(['Foo: Bar\r\n', 'Test: Blub\r\n',
... '\r\n', 'More data'])
Headers([('Foo', 'Bar'), ('Test', 'Blub')])
:param iterable: iterable of strings that are newline terminated
"""
for line in iterator:
result = []
for line in iterable:
line, line_terminated = _line_parse(line)
if not line_terminated:
raise ValueError('unexpected end of line in multipart header')
if not line:
break
line = line.strip()
if line:
return line
return ''
elif line[0] in ' \t' and result:
key, value = result[-1]
result[-1] = (key, value + '\n ' + line[1:])
else:
parts = line.split(':', 1)
if len(parts) == 2:
result.append((parts[0].strip(), parts[1].strip()))
# we link the list to the headers, no need to create a copy, the
# list was not shared anyways.
return Headers.linked(result)
def is_valid_multipart_boundary(boundary):
"""Checks if the string given is a valid multipart boundary."""
return _multipart_boundary_re.match(boundary) is not None
class MultiPartParser(object):
def __init__(self, stream_factory=None, charset='utf-8', errors='replace',
max_form_memory_size=None, cls=None, buffer_size=10 * 1024):
self.stream_factory = stream_factory
self.charset = charset
self.errors = errors
self.max_form_memory_size = max_form_memory_size
if stream_factory is None:
stream_factory = default_stream_factory
if cls is None:
cls = MultiDict
self.cls = cls
def parse_multipart(file, boundary, content_length, stream_factory=None,
charset='utf-8', errors='replace', buffer_size=10 * 1024,
max_form_memory_size=None):
"""Parse a multipart/form-data stream. This is invoked by
:func:`utils.parse_form_data` if the content type matches. Currently it
exists for internal usage only, but could be exposed as separate
function if it turns out to be useful and if we consider the API stable.
"""
# XXX: this function does not support multipart/mixed. I don't know of
# any browser that supports this, but it should be implemented
# nonetheless.
# make sure the buffer size is divisible by four so that we can base64
# decode chunk by chunk
assert buffer_size % 4 == 0, 'buffer size has to be divisible by 4'
# also the buffer size has to be at least 1024 bytes long or long headers
# will freak out the system
assert buffer_size >= 1024, 'buffer size has to be at least 1KB'
# make sure the buffer size is divisible by four so that we can base64
# decode chunk by chunk
assert buffer_size % 4 == 0, 'buffer size has to be divisible by 4'
# also the buffer size has to be at least 1024 bytes long or long headers
# will freak out the system
assert buffer_size >= 1024, 'buffer size has to be at least 1KB'
self.buffer_size = buffer_size
if stream_factory is None:
stream_factory = default_stream_factory
def _fix_ie_filename(self, filename):
"""Internet Explorer 6 transmits the full file name if a file is
uploaded. This function strips the full path if it thinks the
filename is Windows-like absolute.
"""
if filename[1:3] == ':\\' or filename[:2] == '\\\\':
return filename.split('\\')[-1]
return filename
if not boundary:
raise ValueError('Missing boundary')
if not is_valid_multipart_boundary(boundary):
raise ValueError('Invalid boundary: %s' % boundary)
if len(boundary) > buffer_size: # pragma: no cover
# this should never happen because we check for a minimum size
# of 1024 and boundaries may not be longer than 200. The only
# situation when this happen is for non debug builds where
# the assert i skipped.
raise ValueError('Boundary longer than buffer size')
def _find_terminator(self, iterator):
"""The terminator might have some additional newlines before it.
There is at least one application that sends additional newlines
before headers (the python setuptools package).
"""
for line in iterator:
if not line:
break
line = line.strip()
if line:
return line
return ''
total_content_length = content_length
next_part = '--' + boundary
last_part = next_part + '--'
def fail(self, message):
raise ValueError(message)
form = []
files = []
in_memory = 0
def get_part_encoding(self, headers):
transfer_encoding = headers.get('content-transfer-encoding')
if transfer_encoding is not None and \
transfer_encoding in _supported_multipart_encodings:
return transfer_encoding
# convert the file into a limited stream with iteration capabilities
file = LimitedStream(file, content_length)
iterator = chain(make_line_iter(file, buffer_size=buffer_size),
_empty_string_iter)
def get_part_charset(self, headers):
# Figure out input charset for current part
content_type = headers.get('content-type')
if content_type:
mimetype, ct_params = parse_options_header(content_type)
return ct_params.get('charset', self.charset)
return self.charset
try:
terminator = _find_terminator(iterator)
def start_file_streaming(self, filename, headers, total_content_length):
filename = _decode_unicode(filename, self.charset, self.errors)
filename = self._fix_ie_filename(filename)
content_type = headers.get('content_type')
try:
content_length = int(headers['content-length'])
except (KeyError, ValueError):
content_length = 0
container = self.stream_factory(total_content_length, content_type,
filename, content_length)
return filename, container
def in_memory_threshold_reached(self, bytes):
raise RequestEntityTooLarge()
def validate_boundary(self, boundary):
if not boundary:
self.fail('Missing boundary')
if not is_valid_multipart_boundary(boundary):
self.fail('Invalid boundary: %s' % boundary)
if len(boundary) > self.buffer_size: # pragma: no cover
# this should never happen because we check for a minimum size
# of 1024 and boundaries may not be longer than 200. The only
# situation when this happen is for non debug builds where
# the assert i skipped.
self.fail('Boundary longer than buffer size')
def parse(self, file, boundary, content_length):
next_part = '--' + boundary
last_part = next_part + '--'
form = []
files = []
in_memory = 0
iterator = chain(make_line_iter(file, limit=content_length,
buffer_size=self.buffer_size),
_empty_string_iter)
terminator = self._find_terminator(iterator)
if terminator != next_part:
raise ValueError('Expected boundary at start of multipart data')
self.fail('Expected boundary at start of multipart data')
while terminator != last_part:
headers = parse_multipart_headers(iterator)
disposition = headers.get('content-disposition')
if disposition is None:
raise ValueError('Missing Content-Disposition header')
self.fail('Missing Content-Disposition header')
disposition, extra = parse_options_header(disposition)
transfer_encoding = self.get_part_encoding(headers)
name = extra.get('name')
transfer_encoding = headers.get('content-transfer-encoding')
try_decode = transfer_encoding is not None and \
transfer_encoding in _supported_multipart_encodings
filename = extra.get('filename')
# Figure out input charset for current part
content_type = headers.get('content-type')
if content_type:
mimetype, ct_params = parse_options_header(content_type)
part_charset = ct_params.get("charset", charset)
else:
part_charset = charset
part_charset = self.get_part_charset(headers)
# if no content type is given we stream into memory. A list is
# used as a temporary container.
@@ -247,41 +386,32 @@ def parse_multipart(file, boundary, content_length, stream_factory=None,
is_file = False
container = []
_write = container.append
guard_memory = max_form_memory_size is not None
guard_memory = self.max_form_memory_size is not None
# otherwise we parse the rest of the headers and ask the stream
# factory for something we can write in.
else:
is_file = True
guard_memory = False
if filename is not None:
filename = _fix_ie_filename(_decode_unicode(filename,
charset,
errors))
try:
content_length = int(headers['content-length'])
except (KeyError, ValueError):
content_length = 0
container = stream_factory(total_content_length, content_type,
filename, content_length)
filename, container = self.start_file_streaming(
filename, headers, content_length)
_write = container.write
buf = ''
for line in iterator:
if not line:
raise ValueError('unexpected end of stream')
self.fail('unexpected end of stream')
if line[:2] == '--':
terminator = line.rstrip()
if terminator in (next_part, last_part):
break
if try_decode:
if transfer_encoding is not None:
try:
line = line.decode(transfer_encoding)
except Exception:
raise ValueError('could not decode transfer '
'encoded chunk')
self.fail('could not decode transfer encoded chunk')
# we have something in the buffer from the last iteration.
# this is usually a newline delimiter.
@@ -311,9 +441,8 @@ def parse_multipart(file, boundary, content_length, stream_factory=None,
# there is too much data in memory.
if guard_memory:
in_memory += len(line)
if in_memory > max_form_memory_size:
from werkzeug.exceptions import RequestEntityTooLarge
raise RequestEntityTooLarge()
if in_memory > self.max_form_memory_size:
self.in_memory_threshold_reached(in_memory)
else: # pragma: no cover
raise ValueError('unexpected end of part')
@@ -326,37 +455,9 @@ def parse_multipart(file, boundary, content_length, stream_factory=None,
if is_file:
container.seek(0)
files.append((name, FileStorage(container, filename, name,
content_type,
content_length, headers)))
headers=headers)))
else:
form.append((name, _decode_unicode(''.join(container),
part_charset, errors)))
finally:
# make sure the whole input stream is read
file.exhaust()
part_charset, self.errors)))
return form, files
def parse_multipart_headers(iterable):
"""Parses multipart headers from an iterable that yields lines (including
the trailing newline symbol.
"""
result = []
for line in iterable:
line, line_terminated = _line_parse(line)
if not line_terminated:
raise ValueError('unexpected end of line in multipart header')
if not line:
break
elif line[0] in ' \t' and result:
key, value = result[-1]
result[-1] = (key, value + '\n ' + line[1:])
else:
parts = line.split(':', 1)
if len(parts) == 2:
result.append((parts[0].strip(), parts[1].strip()))
# we link the list to the headers, no need to create a copy, the
# list was not shared anyways.
return Headers.linked(result)
return self.cls(form), self.cls(files)
Regular → Executable
+6 -4
View File
@@ -50,7 +50,7 @@ _entity_headers = frozenset([
'content-location', 'content-md5', 'content-range', 'content-type',
'expires', 'last-modified'
])
_hop_by_pop_headers = frozenset([
_hop_by_hop_headers = frozenset([
'connection', 'keep-alive', 'proxy-authenticate',
'proxy-authorization', 'te', 'trailers', 'transfer-encoding',
'upgrade'
@@ -357,10 +357,12 @@ def parse_authorization_header(value):
'password': password})
elif auth_type == 'digest':
auth_map = parse_dict_header(auth_info)
for key in 'username', 'realm', 'nonce', 'uri', 'nc', 'cnonce', \
'response':
for key in 'username', 'realm', 'nonce', 'uri', 'response':
if not key in auth_map:
return
if 'qop' in auth_map:
if not auth_map.get('nc') or not auth_map.get('cnonce'):
return
return Authorization('digest', auth_map)
@@ -708,7 +710,7 @@ def is_hop_by_hop_header(header):
:param header: the header to test.
:return: `True` if it's an entity header, `False` otherwise.
"""
return header.lower() in _hop_by_pop_headers
return header.lower() in _hop_by_hop_headers
def parse_cookie(header, charset='utf-8', errors='replace',

Some files were not shown because too many files have changed in this diff Show More