Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/websdk/flask
diff options
context:
space:
mode:
Diffstat (limited to 'websdk/flask')
-rw-r--r--websdk/flask/__init__.py41
-rw-r--r--websdk/flask/app.py1518
-rw-r--r--websdk/flask/blueprints.py321
-rw-r--r--websdk/flask/config.py169
-rw-r--r--websdk/flask/ctx.py175
-rw-r--r--websdk/flask/debughelpers.py79
-rw-r--r--websdk/flask/ext/__init__.py29
-rw-r--r--websdk/flask/exthook.py119
-rw-r--r--websdk/flask/globals.py28
-rw-r--r--websdk/flask/helpers.py649
-rw-r--r--websdk/flask/logging.py43
-rw-r--r--websdk/flask/module.py42
-rw-r--r--websdk/flask/session.py19
-rw-r--r--websdk/flask/sessions.py205
-rw-r--r--websdk/flask/signals.py51
-rw-r--r--websdk/flask/templating.py138
-rw-r--r--websdk/flask/testing.py118
-rw-r--r--websdk/flask/testsuite/__init__.py221
-rw-r--r--websdk/flask/testsuite/basic.py1051
-rw-r--r--websdk/flask/testsuite/blueprints.py512
-rw-r--r--websdk/flask/testsuite/config.py182
-rw-r--r--websdk/flask/testsuite/deprecations.py41
-rw-r--r--websdk/flask/testsuite/examples.py38
-rw-r--r--websdk/flask/testsuite/ext.py123
-rw-r--r--websdk/flask/testsuite/helpers.py298
-rw-r--r--websdk/flask/testsuite/signals.py103
-rw-r--r--websdk/flask/testsuite/static/index.html1
-rw-r--r--websdk/flask/testsuite/subclassing.py46
-rw-r--r--websdk/flask/testsuite/templates/_macro.html1
-rw-r--r--websdk/flask/testsuite/templates/context_template.html1
-rw-r--r--websdk/flask/testsuite/templates/escaping_template.html6
-rw-r--r--websdk/flask/testsuite/templates/mail.txt1
-rw-r--r--websdk/flask/testsuite/templates/nested/nested.txt1
-rw-r--r--websdk/flask/testsuite/templates/simple_template.html1
-rw-r--r--websdk/flask/testsuite/templates/template_filter.html1
-rw-r--r--websdk/flask/testsuite/templating.py144
-rw-r--r--websdk/flask/testsuite/test_apps/blueprintapp/__init__.py7
-rw-r--r--websdk/flask/testsuite/test_apps/blueprintapp/apps/__init__.py0
-rw-r--r--websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py15
-rw-r--r--websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css1
-rw-r--r--websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt1
-rw-r--r--websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html1
-rw-r--r--websdk/flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py8
-rw-r--r--websdk/flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html1
-rw-r--r--websdk/flask/testsuite/test_apps/config_module_app.py4
-rw-r--r--websdk/flask/testsuite/test_apps/config_package_app/__init__.py4
-rw-r--r--websdk/flask/testsuite/test_apps/flask_broken/__init__.py2
-rw-r--r--websdk/flask/testsuite/test_apps/flask_broken/b.py0
-rw-r--r--websdk/flask/testsuite/test_apps/flask_newext_package/__init__.py1
-rw-r--r--websdk/flask/testsuite/test_apps/flask_newext_package/submodule.py2
-rw-r--r--websdk/flask/testsuite/test_apps/flask_newext_simple.py1
-rw-r--r--websdk/flask/testsuite/test_apps/flaskext/__init__.py0
-rw-r--r--websdk/flask/testsuite/test_apps/flaskext/oldext_package/__init__.py1
-rw-r--r--websdk/flask/testsuite/test_apps/flaskext/oldext_package/submodule.py2
-rw-r--r--websdk/flask/testsuite/test_apps/flaskext/oldext_simple.py1
-rw-r--r--websdk/flask/testsuite/test_apps/moduleapp/__init__.py7
-rw-r--r--websdk/flask/testsuite/test_apps/moduleapp/apps/__init__.py0
-rw-r--r--websdk/flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py14
-rw-r--r--websdk/flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css1
-rw-r--r--websdk/flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt1
-rw-r--r--websdk/flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html1
-rw-r--r--websdk/flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py9
-rw-r--r--websdk/flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html1
-rw-r--r--websdk/flask/testsuite/test_apps/subdomaintestmodule/__init__.py4
-rw-r--r--websdk/flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt1
-rw-r--r--websdk/flask/testsuite/testing.py171
-rw-r--r--websdk/flask/testsuite/views.py152
-rw-r--r--websdk/flask/views.py151
-rw-r--r--websdk/flask/wrappers.py138
69 files changed, 7219 insertions, 0 deletions
diff --git a/websdk/flask/__init__.py b/websdk/flask/__init__.py
new file mode 100644
index 0000000..c1076c3
--- /dev/null
+++ b/websdk/flask/__init__.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+"""
+ flask
+ ~~~~~
+
+ A microframework based on Werkzeug. It's extensively documented
+ and follows best practice patterns.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+__version__ = '0.8-dev'
+
+# utilities we import from Werkzeug and Jinja2 that are unused
+# in the module but are exported as public interface.
+from werkzeug.exceptions import abort
+from werkzeug.utils import redirect
+from jinja2 import Markup, escape
+
+from .app import Flask, Request, Response
+from .config import Config
+from .helpers import url_for, jsonify, json_available, flash, \
+ send_file, send_from_directory, get_flashed_messages, \
+ get_template_attribute, make_response, safe_join
+from .globals import current_app, g, request, session, _request_ctx_stack
+from .ctx import has_request_context
+from .module import Module
+from .blueprints import Blueprint
+from .templating import render_template, render_template_string
+
+# the signals
+from .signals import signals_available, template_rendered, request_started, \
+ request_finished, got_request_exception, request_tearing_down
+
+# only import json if it's available
+if json_available:
+ from .helpers import json
+
+# backwards compat, goes away in 1.0
+from .sessions import SecureCookieSession as Session
diff --git a/websdk/flask/app.py b/websdk/flask/app.py
new file mode 100644
index 0000000..ebf4e6a
--- /dev/null
+++ b/websdk/flask/app.py
@@ -0,0 +1,1518 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.app
+ ~~~~~~~~~
+
+ This module implements the central WSGI application object.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import os
+import sys
+from threading import Lock
+from datetime import timedelta
+from itertools import chain
+from functools import update_wrapper
+
+from werkzeug.datastructures import ImmutableDict
+from werkzeug.routing import Map, Rule, RequestRedirect
+from werkzeug.exceptions import HTTPException, InternalServerError, \
+ MethodNotAllowed, BadRequest
+
+from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
+ locked_cached_property, _tojson_filter, _endpoint_from_view_func, \
+ find_package
+from .wrappers import Request, Response
+from .config import ConfigAttribute, Config
+from .ctx import RequestContext
+from .globals import _request_ctx_stack, request
+from .sessions import SecureCookieSessionInterface
+from .module import blueprint_is_module
+from .templating import DispatchingJinjaLoader, Environment, \
+ _default_template_ctx_processor
+from .signals import request_started, request_finished, got_request_exception, \
+ request_tearing_down
+
+# a lock used for logger initialization
+_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.
+ """
+ def wrapper_func(self, *args, **kwargs):
+ if self.debug and self._got_first_request:
+ raise AssertionError('A setup function was called after the '
+ 'first request was handled. This usually indicates a bug '
+ 'in the application where a module was not imported '
+ 'and decorators or other functionality was called too late.\n'
+ 'To fix this make sure to import all your view modules, '
+ 'database models and everything related at a central place '
+ 'before the application starts serving requests.')
+ return f(self, *args, **kwargs)
+ return update_wrapper(wrapper_func, f)
+
+
+class Flask(_PackageBoundObject):
+ """The flask object implements a WSGI application and acts as the central
+ object. It is passed the name of the module or package of the
+ application. Once it is created it will act as a central registry for
+ the view functions, the URL rules, template configuration and much more.
+
+ The name of the package is used to resolve resources from inside the
+ package or the folder the module is contained in depending on if the
+ package parameter resolves to an actual python package (a folder with
+ an `__init__.py` file inside) or a standard module (just a `.py` file).
+
+ For more information about resource loading, see :func:`open_resource`.
+
+ Usually you create a :class:`Flask` instance in your main module or
+ in the `__init__.py` file of your package like this::
+
+ from flask import Flask
+ app = Flask(__name__)
+
+ .. admonition:: About the First Parameter
+
+ The idea of the first parameter is to give Flask an idea what
+ belongs to your application. This name is used to find resources
+ on the file system, can be used by extensions to improve debugging
+ information and a lot more.
+
+ So it's important what you provide there. If you are using a single
+ module, `__name__` is always the correct value. If you however are
+ using a package, it's usually recommended to hardcode the name of
+ your package there.
+
+ For example if your application is defined in `yourapplication/app.py`
+ you should create it with one of the two versions below::
+
+ app = Flask('yourapplication')
+ app = Flask(__name__.split('.')[0])
+
+ Why is that? The application will work even with `__name__`, thanks
+ to how resources are looked up. However it will make debugging more
+ painful. Certain extensions can make assumptions based on the
+ import name of your application. For example the Flask-SQLAlchemy
+ extension will look for the code in your application that triggered
+ an SQL query in debug mode. If the import name is not properly set
+ up, that debugging information is lost. (For example it would only
+ pick up SQL queries in `yourapplication.app` and not
+ `yourapplication.views.frontend`)
+
+ .. versionadded:: 0.7
+ The `static_url_path`, `static_folder`, and `template_folder`
+ parameters were added.
+
+ .. versionadded:: 0.8
+ The `instance_path` and `instance_relative_config` parameters were
+ added.
+
+ :param import_name: the name of the application package
+ :param static_url_path: can be used to specify a different path for the
+ static files on the web. Defaults to the name
+ of the `static_folder` folder.
+ :param static_folder: the folder with static files that should be served
+ at `static_url_path`. Defaults to the ``'static'``
+ folder in the root path of the application.
+ :param template_folder: the folder that contains the templates that should
+ be used by the application. Defaults to
+ ``'templates'`` folder in the root path of the
+ application.
+ :param instance_path: An alternative instance path for the application.
+ By default the folder ``'instance'`` next to the
+ package or module is assumed to be the instance
+ path.
+ :param instance_relative_config: if set to `True` relative filenames
+ for loading the config are assumed to
+ be relative to the instance path instead
+ of the application root.
+ """
+
+ #: The class that is used for request objects. See :class:`~flask.Request`
+ #: for more information.
+ request_class = Request
+
+ #: The class that is used for response objects. See
+ #: :class:`~flask.Response` for more information.
+ response_class = Response
+
+ #: The debug flag. Set this to `True` to enable debugging of the
+ #: application. In debug mode the debugger will kick in when an unhandled
+ #: exception ocurrs and the integrated server will automatically reload
+ #: the application if changes in the code are detected.
+ #:
+ #: This attribute can also be configured from the config with the `DEBUG`
+ #: configuration key. Defaults to `False`.
+ debug = ConfigAttribute('DEBUG')
+
+ #: The testing flag. Set this to `True` to enable the test mode of
+ #: Flask extensions (and in the future probably also Flask itself).
+ #: For example this might activate unittest helpers that have an
+ #: additional runtime cost which should not be enabled by default.
+ #:
+ #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
+ #: default it's implicitly enabled.
+ #:
+ #: This attribute can also be configured from the config with the
+ #: `TESTING` configuration key. Defaults to `False`.
+ testing = ConfigAttribute('TESTING')
+
+ #: If a secret key is set, cryptographic components can use this to
+ #: sign cookies and other things. Set this to a complex random value
+ #: when you want to use the secure cookie for instance.
+ #:
+ #: This attribute can also be configured from the config with the
+ #: `SECRET_KEY` configuration key. Defaults to `None`.
+ secret_key = ConfigAttribute('SECRET_KEY')
+
+ #: The secure cookie uses this for the name of the session cookie.
+ #:
+ #: This attribute can also be configured from the config with the
+ #: `SESSION_COOKIE_NAME` configuration key. Defaults to ``'session'``
+ session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME')
+
+ #: A :class:`~datetime.timedelta` which is used to set the expiration
+ #: date of a permanent session. The default is 31 days which makes a
+ #: permanent session survive for roughly one month.
+ #:
+ #: 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',
+ 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
+ #: sent with the :func:`send_file` method.
+ #:
+ #: .. versionadded:: 0.2
+ #:
+ #: This attribute can also be configured from the config with the
+ #: `USE_X_SENDFILE` configuration key. Defaults to `False`.
+ use_x_sendfile = ConfigAttribute('USE_X_SENDFILE')
+
+ #: The name of the logger to use. By default the logger name is the
+ #: package name passed to the constructor.
+ #:
+ #: .. versionadded:: 0.4
+ logger_name = ConfigAttribute('LOGGER_NAME')
+
+ #: Enable the deprecated module support? This is active by default
+ #: in 0.7 but will be changed to False in 0.8. With Flask 1.0 modules
+ #: will be removed in favor of Blueprints
+ enable_modules = True
+
+ #: The logging format used for the debug logger. This is only used when
+ #: the application is in debug mode, otherwise the attached logging
+ #: handler does the formatting.
+ #:
+ #: .. versionadded:: 0.3
+ debug_log_format = (
+ '-' * 80 + '\n' +
+ '%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n' +
+ '%(message)s\n' +
+ '-' * 80
+ )
+
+ #: Options that are passed directly to the Jinja2 environment.
+ jinja_options = ImmutableDict(
+ extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
+ )
+
+ #: Default configuration parameters.
+ default_config = ImmutableDict({
+ 'DEBUG': False,
+ 'TESTING': False,
+ 'PROPAGATE_EXCEPTIONS': None,
+ 'PRESERVE_CONTEXT_ON_EXCEPTION': None,
+ 'SECRET_KEY': None,
+ '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
+ })
+
+ #: The rule object to use for URL rules created. This is used by
+ #: :meth:`add_url_rule`. Defaults to :class:`werkzeug.routing.Rule`.
+ #:
+ #: .. versionadded:: 0.7
+ url_rule_class = Rule
+
+ #: the test client that is used with when `test_client` is used.
+ #:
+ #: .. versionadded:: 0.7
+ test_client_class = None
+
+ #: the session interface to use. By default an instance of
+ #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here.
+ #:
+ #: .. versionadded:: 0.8
+ session_interface = SecureCookieSessionInterface()
+
+ def __init__(self, import_name, static_path=None, static_url_path=None,
+ static_folder='static', template_folder='templates',
+ instance_path=None, instance_relative_config=False):
+ _PackageBoundObject.__init__(self, import_name,
+ template_folder=template_folder)
+ if static_path is not None:
+ from warnings import warn
+ warn(DeprecationWarning('static_path is now called '
+ 'static_url_path'), stacklevel=2)
+ static_url_path = static_path
+
+ if static_url_path is not None:
+ self.static_url_path = static_url_path
+ if static_folder is not None:
+ self.static_folder = static_folder
+ if instance_path is None:
+ instance_path = self.auto_find_instance_path()
+ elif not os.path.isabs(instance_path):
+ raise ValueError('If an instance path is provided it must be '
+ 'absolute. A relative path was given instead.')
+
+ #: Holds the path to the instance folder.
+ #:
+ #: .. versionadded:: 0.8
+ self.instance_path = instance_path
+
+ #: The configuration dictionary as :class:`Config`. This behaves
+ #: exactly like a regular dictionary but supports additional methods
+ #: to load a config from files.
+ self.config = self.make_config(instance_relative_config)
+
+ # Prepare the deferred setup of the logger.
+ self._logger = None
+ self.logger_name = self.import_name
+
+ #: A dictionary of all view functions registered. The keys will
+ #: be function names which are also used to generate URLs and
+ #: the values are the function objects themselves.
+ #: To register a view function, use the :meth:`route` decorator.
+ self.view_functions = {}
+
+ # support for the now deprecated `error_handlers` attribute. The
+ # :attr:`error_handler_spec` shall be used now.
+ self._error_handlers = {}
+
+ #: A dictionary of all registered error handlers. The key is `None`
+ #: for error handlers active on the application, otherwise the key is
+ #: the name of the blueprint. Each key points to another dictionary
+ #: where they key is the status code of the http exception. The
+ #: special key `None` points to a list of tuples where the first item
+ #: is the class for the instance check and the second the error handler
+ #: function.
+ #:
+ #: To register a error handler, use the :meth:`errorhandler`
+ #: decorator.
+ self.error_handler_spec = {None: self._error_handlers}
+
+ #: A dictionary with lists of functions that should be called at the
+ #: beginning of the request. The key of the dictionary is the name of
+ #: the blueprint this function is active for, `None` for all requests.
+ #: This can for example be used to open database connections or
+ #: getting hold of the currently logged in user. To register a
+ #: function here, use the :meth:`before_request` decorator.
+ self.before_request_funcs = {}
+
+ #: A lists of functions that should be called at the beginning of the
+ #: first request to this instance. To register a function here, use
+ #: the :meth:`before_first_request` decorator.
+ #:
+ #: .. versionadded:: 0.8
+ self.before_first_request_funcs = []
+
+ #: A dictionary with lists of functions that should be called after
+ #: each request. The key of the dictionary is the name of the blueprint
+ #: this function is active for, `None` for all requests. This can for
+ #: example be used to open database connections or getting hold of the
+ #: currently logged in user. To register a function here, use the
+ #: :meth:`after_request` decorator.
+ self.after_request_funcs = {}
+
+ #: A dictionary with lists of functions that are called after
+ #: each request, even if an exception has occurred. The key of the
+ #: dictionary is the name of the blueprint this function is active for,
+ #: `None` for all requests. These functions are not allowed to modify
+ #: the request, and their return values are ignored. If an exception
+ #: occurred while processing the request, it gets passed to each
+ #: teardown_request function. To register a function here, use the
+ #: :meth:`teardown_request` decorator.
+ #:
+ #: .. versionadded:: 0.7
+ self.teardown_request_funcs = {}
+
+ #: A dictionary with lists of functions that can be used as URL
+ #: value processor functions. Whenever a URL is built these functions
+ #: are called to modify the dictionary of values in place. The key
+ #: `None` here is used for application wide
+ #: callbacks, otherwise the key is the name of the blueprint.
+ #: Each of these functions has the chance to modify the dictionary
+ #:
+ #: .. versionadded:: 0.7
+ self.url_value_preprocessors = {}
+
+ #: A dictionary with lists of functions that can be used as URL value
+ #: preprocessors. The key `None` here is used for application wide
+ #: callbacks, otherwise the key is the name of the blueprint.
+ #: Each of these functions has the chance to modify the dictionary
+ #: of URL values before they are used as the keyword arguments of the
+ #: view function. For each function registered this one should also
+ #: provide a :meth:`url_defaults` function that adds the parameters
+ #: automatically again that were removed that way.
+ #:
+ #: .. versionadded:: 0.7
+ self.url_default_functions = {}
+
+ #: A dictionary with list of functions that are called without argument
+ #: to populate the template context. The key of the dictionary is the
+ #: name of the blueprint this function is active for, `None` for all
+ #: requests. Each returns a dictionary that the template context is
+ #: updated with. To register a function here, use the
+ #: :meth:`context_processor` decorator.
+ self.template_context_processors = {
+ None: [_default_template_ctx_processor]
+ }
+
+ #: all the attached blueprints in a directory by name. Blueprints
+ #: can be attached multiple times so this dictionary does not tell
+ #: you how often they got attached.
+ #:
+ #: .. versionadded:: 0.7
+ self.blueprints = {}
+
+ #: a place where extensions can store application specific state. For
+ #: example this is where an extension could store database engines and
+ #: similar things. For backwards compatibility extensions should register
+ #: themselves like this::
+ #:
+ #: if not hasattr(app, 'extensions'):
+ #: app.extensions = {}
+ #: app.extensions['extensionname'] = SomeObject()
+ #:
+ #: The key must match the name of the `flaskext` module. For example in
+ #: case of a "Flask-Foo" extension in `flaskext.foo`, the key would be
+ #: ``'foo'``.
+ #:
+ #: .. versionadded:: 0.7
+ self.extensions = {}
+
+ #: The :class:`~werkzeug.routing.Map` for this instance. You can use
+ #: this to change the routing converters after the class was created
+ #: but before any routes are connected. Example::
+ #:
+ #: from werkzeug.routing import BaseConverter
+ #:
+ #: class ListConverter(BaseConverter):
+ #: def to_python(self, value):
+ #: return value.split(',')
+ #: def to_url(self, values):
+ #: return ','.join(BaseConverter.to_url(value)
+ #: for value in values)
+ #:
+ #: app = Flask(__name__)
+ #: app.url_map.converters['list'] = ListConverter
+ self.url_map = Map()
+
+ # tracks internally if the application already handled at least one
+ # request.
+ self._got_first_request = False
+ self._before_request_lock = Lock()
+
+ # register the static folder for the application. Do that even
+ # if the folder does not exist. First of all it might be created
+ # while the server is running (usually happens during development)
+ # but also because google appengine stores static files somewhere
+ # else when mapped with the .yml file.
+ if self.has_static_folder:
+ self.add_url_rule(self.static_url_path + '/<path:filename>',
+ endpoint='static',
+ view_func=self.send_static_file)
+
+ def _get_error_handlers(self):
+ from warnings import warn
+ warn(DeprecationWarning('error_handlers is deprecated, use the '
+ 'new error_handler_spec attribute instead.'), stacklevel=1)
+ return self._error_handlers
+ def _set_error_handlers(self, value):
+ self._error_handlers = value
+ self.error_handler_spec[None] = value
+ error_handlers = property(_get_error_handlers, _set_error_handlers)
+ del _get_error_handlers, _set_error_handlers
+
+ @locked_cached_property
+ def name(self):
+ """The name of the application. This is usually the import name
+ with the difference that it's guessed from the run file if the
+ import name is main. This name is used as a display name when
+ Flask needs the name of the application. It can be set and overriden
+ to change the value.
+
+ .. versionadded:: 0.8
+ """
+ if self.import_name == '__main__':
+ fn = getattr(sys.modules['__main__'], '__file__', None)
+ if fn is None:
+ return '__main__'
+ return os.path.splitext(os.path.basename(fn))[0]
+ return self.import_name
+
+ @property
+ def propagate_exceptions(self):
+ """Returns the value of the `PROPAGATE_EXCEPTIONS` configuration
+ value in case it's set, otherwise a sensible default is returned.
+
+ .. versionadded:: 0.7
+ """
+ rv = self.config['PROPAGATE_EXCEPTIONS']
+ if rv is not None:
+ return rv
+ return self.testing or self.debug
+
+ @property
+ def preserve_context_on_exception(self):
+ """Returns the value of the `PRESERVE_CONTEXT_ON_EXCEPTION`
+ configuration value in case it's set, otherwise a sensible default
+ is returned.
+
+ .. versionadded:: 0.7
+ """
+ rv = self.config['PRESERVE_CONTEXT_ON_EXCEPTION']
+ if rv is not None:
+ return rv
+ return self.debug
+
+ @property
+ def logger(self):
+ """A :class:`logging.Logger` object for this application. The
+ default configuration is to log to stderr if the application is
+ in debug mode. This logger can be used to (surprise) log messages.
+ Here some examples::
+
+ app.logger.debug('A value for debugging')
+ app.logger.warning('A warning ocurred (%d apples)', 42)
+ app.logger.error('An error occoured')
+
+ .. versionadded:: 0.3
+ """
+ if self._logger and self._logger.name == self.logger_name:
+ return self._logger
+ with _logger_lock:
+ if self._logger and self._logger.name == self.logger_name:
+ return self._logger
+ from flask.logging import create_logger
+ self._logger = rv = create_logger(self)
+ return rv
+
+ @locked_cached_property
+ def jinja_env(self):
+ """The Jinja2 environment used to load templates."""
+ rv = self.create_jinja_environment()
+
+ # Hack to support the init_jinja_globals method which is supported
+ # until 1.0 but has an API deficiency.
+ if getattr(self.init_jinja_globals, 'im_func', None) is not \
+ Flask.init_jinja_globals.im_func:
+ from warnings import warn
+ warn(DeprecationWarning('This flask class uses a customized '
+ 'init_jinja_globals() method which is deprecated. '
+ 'Move the code from that method into the '
+ 'create_jinja_environment() method instead.'))
+ self.__dict__['jinja_env'] = rv
+ self.init_jinja_globals()
+
+ return rv
+
+ @property
+ def got_first_request(self):
+ """This attribute is set to `True` if the application started
+ handling the first request.
+
+ .. versionadded:: 0.8
+ """
+ return self._got_first_request
+
+ def make_config(self, instance_relative=False):
+ """Used to create the config attribute by the Flask constructor.
+ The `instance_relative` parameter is passed in from the constructor
+ of Flask (there named `instance_relative_config`) and indicates if
+ the config should be relative to the instance path or the root path
+ of the application.
+
+ .. versionadded:: 0.8
+ """
+ root_path = self.root_path
+ if instance_relative:
+ root_path = self.instance_path
+ return Config(root_path, self.default_config)
+
+ def auto_find_instance_path(self):
+ """Tries to locate the instance path if it was not provided to the
+ constructor of the application class. It will basically calculate
+ the path to a folder named ``instance`` next to your main file or
+ the package.
+
+ .. versionadded:: 0.8
+ """
+ prefix, package_path = find_package(self.import_name)
+ if prefix is None:
+ return os.path.join(package_path, 'instance')
+ return os.path.join(prefix, 'var', self.name + '-instance')
+
+ def open_instance_resource(self, resource, mode='rb'):
+ """Opens a resource from the application's instance folder
+ (:attr:`instance_path`). Otherwise works like
+ :meth:`open_resource`. Instance resources can also be opened for
+ writing.
+
+ :param resource: the name of the resource. To access resources within
+ subfolders use forward slashes as separator.
+ """
+ return open(os.path.join(self.instance_path, resource), mode)
+
+ def create_jinja_environment(self):
+ """Creates the Jinja2 environment based on :attr:`jinja_options`
+ and :meth:`select_jinja_autoescape`. Since 0.7 this also adds
+ the Jinja2 globals and filters after initialization. Override
+ this function to customize the behavior.
+
+ .. versionadded:: 0.5
+ """
+ options = dict(self.jinja_options)
+ if 'autoescape' not in options:
+ options['autoescape'] = self.select_jinja_autoescape
+ rv = Environment(self, **options)
+ rv.globals.update(
+ url_for=url_for,
+ get_flashed_messages=get_flashed_messages
+ )
+ rv.filters['tojson'] = _tojson_filter
+ return rv
+
+ def create_global_jinja_loader(self):
+ """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:`jinja_loader` function instead.
+
+ The global loader dispatches between the loaders of the application
+ and the individual blueprints.
+
+ .. versionadded:: 0.7
+ """
+ return DispatchingJinjaLoader(self)
+
+ def init_jinja_globals(self):
+ """Deprecated. Used to initialize the Jinja2 globals.
+
+ .. versionadded:: 0.5
+ .. versionchanged:: 0.7
+ This method is deprecated with 0.7. Override
+ :meth:`create_jinja_environment` instead.
+ """
+
+ def select_jinja_autoescape(self, filename):
+ """Returns `True` if autoescaping should be active for the given
+ template name.
+
+ .. versionadded:: 0.5
+ """
+ if filename is None:
+ return False
+ return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
+
+ def update_template_context(self, context):
+ """Update the template context with some commonly used variables.
+ This injects request, session, config and g into the template
+ context as well as everything template context processors want
+ to inject. Note that the as of Flask 0.6, the original values
+ in the context will not be overriden if a context processor
+ decides to return a value with the same key.
+
+ :param context: the context as a dictionary that is updated in place
+ to add extra variables.
+ """
+ funcs = self.template_context_processors[None]
+ bp = _request_ctx_stack.top.request.blueprint
+ if bp is not None and bp in self.template_context_processors:
+ funcs = chain(funcs, self.template_context_processors[bp])
+ orig_ctx = context.copy()
+ for func in funcs:
+ context.update(func())
+ # make sure the original values win. This makes it possible to
+ # easier add new variables in context processors without breaking
+ # existing views.
+ context.update(orig_ctx)
+
+ 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.
+
+ If you want to run the application in debug mode, but disable the
+ code execution on the interactive debugger, you can pass
+ ``use_evalex=False`` as parameter. This will keep the debugger's
+ traceback screen active, but disable code execution.
+
+ .. admonition:: Keep in Mind
+
+ Flask will suppress any server error with a generic error page
+ unless it is in debug mode. As such to enable just the
+ interactive debugger without the code reloading, you have to
+ invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``.
+ Setting ``use_debugger`` to `True` without being in debug mode
+ won't catch any exceptions because there won't be any to
+ catch.
+
+ :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 is not None:
+ self.debug = bool(debug)
+ options.setdefault('use_reloader', self.debug)
+ options.setdefault('use_debugger', self.debug)
+ try:
+ run_simple(host, port, self, **options)
+ finally:
+ # reset the first request information if the development server
+ # resetted normally. This makes it possible to restart the server
+ # without reloader and that stuff from an interactive shell.
+ self._got_first_request = False
+
+ def test_client(self, use_cookies=True):
+ """Creates a test client for this application. For information
+ about unit testing head over to :ref:`testing`.
+
+ The test client can be used in a `with` block to defer the closing down
+ of the context until the end of the `with` block. This is useful if
+ you want to access the context locals for testing::
+
+ with app.test_client() as c:
+ 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.
+
+ .. versionadded:: 0.7
+ The `use_cookies` parameter was added as well as the ability
+ to override the client to be used by setting the
+ :attr:`test_client_class` attribute.
+ """
+ cls = self.test_client_class
+ if cls is None:
+ from flask.testing import FlaskClient as cls
+ return cls(self, self.response_class, use_cookies=use_cookies)
+
+ def open_session(self, request):
+ """Creates or opens a new session. Default implementation stores all
+ session data in a signed cookie. This requires that the
+ :attr:`secret_key` is set. Instead of overriding this method
+ we recommend replacing the :class:`session_interface`.
+
+ :param request: an instance of :attr:`request_class`.
+ """
+ return self.session_interface.open_session(self, request)
+
+ def save_session(self, session, response):
+ """Saves the session if it needs updates. For the default
+ implementation, check :meth:`open_session`. Instead of overriding this
+ method we recommend replacing the :class:`session_interface`.
+
+ :param session: the session to be saved (a
+ :class:`~werkzeug.contrib.securecookie.SecureCookie`
+ object)
+ :param response: an instance of :attr:`response_class`
+ """
+ return self.session_interface.save_session(self, session, response)
+
+ def make_null_session(self):
+ """Creates a new instance of a missing session. Instead of overriding
+ this method we recommend replacing the :class:`session_interface`.
+
+ .. versionadded:: 0.7
+ """
+ return self.session_interface.make_null_session(self)
+
+ def register_module(self, module, **options):
+ """Registers a module with this application. The keyword argument
+ of this function are the same as the ones for the constructor of the
+ :class:`Module` class and will override the values of the module if
+ provided.
+
+ .. versionchanged:: 0.7
+ The module system was deprecated in favor for the blueprint
+ system.
+ """
+ assert blueprint_is_module(module), 'register_module requires ' \
+ 'actual module objects. Please upgrade to blueprints though.'
+ if not self.enable_modules:
+ raise RuntimeError('Module support was disabled but code '
+ 'attempted to register a module named %r' % module)
+ else:
+ from warnings import warn
+ warn(DeprecationWarning('Modules are deprecated. Upgrade to '
+ 'using blueprints. Have a look into the documentation for '
+ 'more information. If this module was registered by a '
+ 'Flask-Extension upgrade the extension or contact the author '
+ 'of that extension instead. (Registered %r)' % module),
+ stacklevel=2)
+
+ self.register_blueprint(module, **options)
+
+ @setupmethod
+ def register_blueprint(self, blueprint, **options):
+ """Registers a blueprint on the application.
+
+ .. versionadded:: 0.7
+ """
+ first_registration = False
+ if blueprint.name in self.blueprints:
+ assert self.blueprints[blueprint.name] is blueprint, \
+ 'A blueprint\'s name collision ocurred between %r and ' \
+ '%r. Both share the same name "%s". Blueprints that ' \
+ 'are created on the fly need unique names.' % \
+ (blueprint, self.blueprints[blueprint.name], blueprint.name)
+ else:
+ self.blueprints[blueprint.name] = blueprint
+ first_registration = True
+ blueprint.register(self, options, first_registration)
+
+ @setupmethod
+ def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
+ """Connects a URL rule. Works exactly like the :meth:`route`
+ decorator. If a view_func is provided it will be registered with the
+ endpoint.
+
+ Basically this example::
+
+ @app.route('/')
+ def index():
+ pass
+
+ Is equivalent to the following::
+
+ def index():
+ pass
+ app.add_url_rule('/', 'index', index)
+
+ If the view_func is not provided you will need to connect the endpoint
+ to a view function like so::
+
+ app.view_functions['index'] = index
+
+ 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.
+
+ .. versionchanged:: 0.6
+ `OPTIONS` is added automatically as method.
+
+ :param rule: the URL rule as string
+ :param endpoint: the endpoint for the registered URL rule. Flask
+ itself assumes the name of the view function as
+ endpoint
+ :param 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.
+ """
+ if endpoint is None:
+ endpoint = _endpoint_from_view_func(view_func)
+ options['endpoint'] = endpoint
+ methods = options.pop('methods', None)
+
+ # if the methods are not given and the view_func object knows its
+ # methods we can use that instead. If neither exists, we go with
+ # a tuple of only `GET` as default.
+ if methods is None:
+ methods = getattr(view_func, 'methods', None) or ('GET',)
+
+ # starting with Flask 0.8 the view_func object can disable and
+ # force-enable the automatic options handling.
+ provide_automatic_options = getattr(view_func,
+ 'provide_automatic_options', None)
+
+ if provide_automatic_options is None:
+ if 'OPTIONS' not in methods:
+ methods = tuple(methods) + ('OPTIONS',)
+ provide_automatic_options = True
+ else:
+ provide_automatic_options = False
+
+ # due to a werkzeug bug we need to make sure that the defaults are
+ # None if they are an empty dictionary. This should not be necessary
+ # with Werkzeug 0.7
+ options['defaults'] = options.get('defaults') or None
+
+ rule = self.url_rule_class(rule, methods=methods, **options)
+ rule.provide_automatic_options = provide_automatic_options
+ self.url_map.add(rule)
+ if view_func is not None:
+ self.view_functions[endpoint] = view_func
+
+ def route(self, rule, **options):
+ """A decorator that is used to register a view function for a
+ 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'
+
+ For more information refer to :ref:`url-route-registrations`.
+
+ :param rule: the URL rule as string
+ :param endpoint: the endpoint for the registered URL rule. Flask
+ itself assumes the name of the view function as
+ endpoint
+ :param 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.
+ """
+ def decorator(f):
+ endpoint = options.pop('endpoint', None)
+ self.add_url_rule(rule, endpoint, f, **options)
+ return f
+ return decorator
+
+ @setupmethod
+ def endpoint(self, endpoint):
+ """A decorator to register a function as an endpoint.
+ Example::
+
+ @app.endpoint('example.endpoint')
+ def example():
+ return "example"
+
+ :param endpoint: the name of the endpoint
+ """
+ def decorator(f):
+ self.view_functions[endpoint] = f
+ return f
+ return decorator
+
+ @setupmethod
+ def errorhandler(self, code_or_exception):
+ """A decorator that is used to register a function give a given
+ error code. Example::
+
+ @app.errorhandler(404)
+ def page_not_found(error):
+ return 'This page does not exist', 404
+
+ You can also register handlers for arbitrary exceptions::
+
+ @app.errorhandler(DatabaseError)
+ def special_exception_handler(error):
+ return 'Database connection failed', 500
+
+ You can also register a function as error handler without using
+ the :meth:`errorhandler` decorator. The following example is
+ equivalent to the one above::
+
+ def page_not_found(error):
+ return 'This page does not exist', 404
+ app.error_handler_spec[None][404] = page_not_found
+
+ Setting error handlers via assignments to :attr:`error_handler_spec`
+ however is discouraged as it requires fidling with nested dictionaries
+ and the special case for arbitrary exception types.
+
+ The first `None` refers to the active blueprint. If the error
+ handler should be application wide `None` shall be used.
+
+ .. versionadded:: 0.7
+ One can now additionally also register custom exception types
+ that do not necessarily have to be a subclass of the
+ :class:`~werkzeug.exceptions.HTTPException` class.
+
+ :param code: the code as integer for the handler
+ """
+ def decorator(f):
+ self._register_error_handler(None, code_or_exception, f)
+ return f
+ return decorator
+
+ def register_error_handler(self, code_or_exception, f):
+ """Alternative error attach function to the :meth:`errorhandler`
+ decorator that is more straightforward to use for non decorator
+ usage.
+
+ .. versionadded:: 0.7
+ """
+ self._register_error_handler(None, code_or_exception, f)
+
+ @setupmethod
+ def _register_error_handler(self, key, code_or_exception, f):
+ if isinstance(code_or_exception, HTTPException):
+ code_or_exception = code_or_exception.code
+ if isinstance(code_or_exception, (int, long)):
+ assert code_or_exception != 500 or key is None, \
+ 'It is currently not possible to register a 500 internal ' \
+ 'server error on a per-blueprint level.'
+ self.error_handler_spec.setdefault(key, {})[code_or_exception] = f
+ else:
+ self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
+ .append((code_or_exception, f))
+
+ @setupmethod
+ def template_filter(self, name=None):
+ """A decorator that is used to register custom template filter.
+ You can specify a name for the filter, otherwise the function
+ name will be used. Example::
+
+ @app.template_filter()
+ def reverse(s):
+ return s[::-1]
+
+ :param name: the optional name of the filter, otherwise the
+ function name will be used.
+ """
+ def decorator(f):
+ self.jinja_env.filters[name or f.__name__] = f
+ return f
+ return decorator
+
+ @setupmethod
+ def before_request(self, f):
+ """Registers a function to run before each request."""
+ self.before_request_funcs.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def before_first_request(self, f):
+ """Registers a function to be run before the first request to this
+ instance of the application.
+
+ .. versionadded:: 0.8
+ """
+ self.before_first_request_funcs.append(f)
+
+ @setupmethod
+ def after_request(self, f):
+ """Register a function to be run after each request. Your function
+ must take one parameter, a :attr:`response_class` object and return
+ a new response object or the same (see :meth:`process_response`).
+
+ As of Flask 0.7 this function might not be executed at the end of the
+ request in case an unhandled exception ocurred.
+ """
+ self.after_request_funcs.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def teardown_request(self, f):
+ """Register a function to be run at the end of each request,
+ regardless of whether there was an exception or not. These functions
+ are executed when the request context is popped, even if not an
+ actual request was performed.
+
+ Example::
+
+ ctx = app.test_request_context()
+ ctx.push()
+ ...
+ ctx.pop()
+
+ When ``ctx.pop()`` is executed in the above example, the teardown
+ functions are called just before the request context moves from the
+ stack of active contexts. This becomes relevant if you are using
+ such constructs in tests.
+
+ Generally teardown functions must take every necesary step to avoid
+ that they will fail. If they do execute code that might fail they
+ will have to surround the execution of these code by try/except
+ statements and log ocurring errors.
+ """
+ self.teardown_request_funcs.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def context_processor(self, f):
+ """Registers a template context processor function."""
+ self.template_context_processors[None].append(f)
+ return f
+
+ @setupmethod
+ def url_value_preprocessor(self, f):
+ """Registers a function as URL value preprocessor for all view
+ functions of the application. It's called before the view functions
+ are called and can modify the url values provided.
+ """
+ self.url_value_preprocessors.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def url_defaults(self, f):
+ """Callback function for URL defaults for all view functions of the
+ application. It's called with the endpoint and values and should
+ update the values passed in place.
+ """
+ self.url_default_functions.setdefault(None, []).append(f)
+ return f
+
+ def handle_http_exception(self, e):
+ """Handles an HTTP exception. By default this will invoke the
+ registered error handlers and fall back to returning the
+ exception as response.
+
+ .. versionadded: 0.3
+ """
+ handlers = self.error_handler_spec.get(request.blueprint)
+ if handlers and e.code in handlers:
+ handler = handlers[e.code]
+ else:
+ handler = self.error_handler_spec[None].get(e.code)
+ if handler is None:
+ return e
+ return handler(e)
+
+ def trap_http_exception(self, e):
+ """Checks if an HTTP exception should be trapped or not. By default
+ this will return `False` for all exceptions except for a bad request
+ key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to `True`. It
+ also returns `True` if ``TRAP_HTTP_EXCEPTIONS`` is set to `True`.
+
+ This is called for all HTTP exceptions raised by a view function.
+ If it returns `True` for any exception the error handler for this
+ exception is not called and it shows up as regular exception in the
+ traceback. This is helpful for debugging implicitly raised HTTP
+ exceptions.
+
+ .. versionadded:: 0.8
+ """
+ if self.config['TRAP_HTTP_EXCEPTIONS']:
+ return True
+ if self.config['TRAP_BAD_REQUEST_ERRORS']:
+ return isinstance(e, BadRequest)
+ return False
+
+ def handle_user_exception(self, e):
+ """This method is called whenever an exception occurs that should be
+ handled. A special case are
+ :class:`~werkzeug.exception.HTTPException`\s which are forwarded by
+ this function to the :meth:`handle_http_exception` method. This
+ function will either return a response value or reraise the
+ exception with the same traceback.
+
+ .. versionadded:: 0.7
+ """
+ exc_type, exc_value, tb = sys.exc_info()
+ assert exc_value is e
+
+ # ensure not to trash sys.exc_info() at that point in case someone
+ # wants the traceback preserved in handle_http_exception. Of course
+ # we cannot prevent users from trashing it themselves in a custom
+ # trap_http_exception method so that's their fault then.
+ if isinstance(e, HTTPException) and not self.trap_http_exception(e):
+ return self.handle_http_exception(e)
+
+ blueprint_handlers = ()
+ handlers = self.error_handler_spec.get(request.blueprint)
+ if handlers is not None:
+ blueprint_handlers = handlers.get(None, ())
+ app_handlers = self.error_handler_spec[None].get(None, ())
+ for typecheck, handler in chain(blueprint_handlers, app_handlers):
+ if isinstance(e, typecheck):
+ return handler(e)
+
+ raise exc_type, exc_value, tb
+
+ def handle_exception(self, e):
+ """Default exception handling that kicks in when an exception
+ occours that is not caught. In debug mode the exception will
+ be re-raised immediately, otherwise it is logged and the handler
+ for a 500 internal server error is used. If no such handler
+ exists, a default 500 internal server error message is displayed.
+
+ .. versionadded: 0.3
+ """
+ exc_type, exc_value, tb = sys.exc_info()
+
+ got_request_exception.send(self, exception=e)
+ handler = self.error_handler_spec[None].get(500)
+
+ if self.propagate_exceptions:
+ # if we want to repropagate the exception, we can attempt to
+ # raise it with the whole traceback in case we can do that
+ # (the function was actually called from the except part)
+ # otherwise, we just raise the error again
+ if exc_value is e:
+ raise exc_type, exc_value, tb
+ else:
+ raise e
+
+ 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
+ for non ``GET``, ``HEAD``, or ``OPTIONS`` requests and we're raising
+ a different error instead to help debug situations.
+
+ :internal:
+ """
+ if not self.debug \
+ or not isinstance(request.routing_exception, RequestRedirect) \
+ or request.method in ('GET', 'HEAD', 'OPTIONS'):
+ raise request.routing_exception
+
+ from .debughelpers import FormDataRoutingRedirect
+ raise FormDataRoutingRedirect(request)
+
+ def dispatch_request(self):
+ """Does the request dispatching. Matches the URL and returns the
+ return value of the view or error handler. This does not have to
+ be a response object. In order to convert the return value to a
+ proper response object, call :func:`make_response`.
+
+ .. versionchanged:: 0.7
+ This no longer does the exception handling, this code was
+ moved to the new :meth:`full_dispatch_request`.
+ """
+ req = _request_ctx_stack.top.request
+ if req.routing_exception is not None:
+ self.raise_routing_exception(req)
+ rule = req.url_rule
+ # if we provide automatic options for this URL and the
+ # request came with the OPTIONS method, reply automatically
+ if getattr(rule, 'provide_automatic_options', False) \
+ and req.method == 'OPTIONS':
+ return self.make_default_options_response()
+ # otherwise dispatch to the handler for that endpoint
+ return self.view_functions[rule.endpoint](**req.view_args)
+
+ def full_dispatch_request(self):
+ """Dispatches the request and on top of that performs request
+ pre and postprocessing as well as HTTP exception catching and
+ error handling.
+
+ .. versionadded:: 0.7
+ """
+ self.try_trigger_before_first_request_functions()
+ try:
+ request_started.send(self)
+ rv = self.preprocess_request()
+ if rv is None:
+ rv = self.dispatch_request()
+ except Exception, e:
+ rv = self.handle_user_exception(e)
+ response = self.make_response(rv)
+ response = self.process_response(response)
+ request_finished.send(self, response=response)
+ return response
+
+ def try_trigger_before_first_request_functions(self):
+ """Called before each request and will ensure that it triggers
+ the :attr:`before_first_request_funcs` and only exactly once per
+ application instance (which means process usually).
+
+ :internal:
+ """
+ if self._got_first_request:
+ return
+ with self._before_request_lock:
+ if self._got_first_request:
+ return
+ self._got_first_request = True
+ for func in self.before_first_request_funcs:
+ func()
+
+ def make_default_options_response(self):
+ """This method is called to create the default `OPTIONS` response.
+ This can be changed through subclassing to change the default
+ behaviour of `OPTIONS` responses.
+
+ .. versionadded:: 0.7
+ """
+ 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
+
+ def make_response(self, rv):
+ """Converts the return value from a view function to a real
+ response object that is an instance of :attr:`response_class`.
+
+ The following types are allowed for `rv`:
+
+ .. tabularcolumns:: |p{3.5cm}|p{9.5cm}|
+
+ ======================= ===========================================
+ :attr:`response_class` the object is returned unchanged
+ :class:`str` a response object is created with the
+ string as body
+ :class:`unicode` a response object is created with the
+ string encoded to utf-8 as body
+ :class:`tuple` the response object is created with the
+ contents of the tuple as arguments
+ a WSGI function the function is called as WSGI application
+ and buffered as response object
+ ======================= ===========================================
+
+ :param rv: the return value from the view function
+ """
+ if rv is None:
+ raise ValueError('View function did not return a response')
+ if isinstance(rv, self.response_class):
+ return rv
+ if isinstance(rv, basestring):
+ return self.response_class(rv)
+ if isinstance(rv, tuple):
+ return self.response_class(*rv)
+ return self.response_class.force_type(rv, request.environ)
+
+ def create_url_adapter(self, request):
+ """Creates a URL adapter for the given request. The URL adapter
+ is created at a point where the request context is not yet set up
+ so the request is passed explicitly.
+
+ .. versionadded:: 0.6
+ """
+ return self.url_map.bind_to_environ(request.environ,
+ server_name=self.config['SERVER_NAME'])
+
+ def inject_url_defaults(self, endpoint, values):
+ """Injects the URL defaults for the given endpoint directly into
+ the values dictionary passed. This is used internally and
+ automatically called on URL building.
+
+ .. versionadded:: 0.7
+ """
+ funcs = self.url_default_functions.get(None, ())
+ if '.' in endpoint:
+ bp = endpoint.split('.', 1)[0]
+ funcs = chain(funcs, self.url_default_functions.get(bp, ()))
+ for func in funcs:
+ func(endpoint, values)
+
+ def preprocess_request(self):
+ """Called before the actual request dispatching and will
+ call every as :meth:`before_request` decorated function.
+ If any of these function returns a value it's handled as
+ if it was the return value from the view and further
+ request handling is stopped.
+
+ This also triggers the :meth:`url_value_processor` functions before
+ the actualy :meth:`before_request` functions are called.
+ """
+ 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:
+ funcs = chain(funcs, self.url_value_preprocessors[bp])
+ for func in funcs:
+ func(request.endpoint, request.view_args)
+
+ funcs = self.before_request_funcs.get(None, ())
+ if bp is not None and bp in self.before_request_funcs:
+ funcs = chain(funcs, self.before_request_funcs[bp])
+ for func in funcs:
+ rv = func()
+ if rv is not None:
+ return rv
+
+ def process_response(self, response):
+ """Can be overridden in order to modify the response object
+ before it's sent to the WSGI server. By default this will
+ call all the :meth:`after_request` decorated functions.
+
+ .. versionchanged:: 0.5
+ As of Flask 0.5 the functions registered for after request
+ execution are called in reverse order of registration.
+
+ :param response: a :attr:`response_class` object.
+ :return: a new response object or the same, has to be an
+ instance of :attr:`response_class`.
+ """
+ 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])
+ if None in self.after_request_funcs:
+ funcs = chain(funcs, reversed(self.after_request_funcs[None]))
+ for handler in funcs:
+ response = handler(response)
+ return response
+
+ def do_teardown_request(self):
+ """Called after the actual request dispatching and will
+ call every as :meth:`teardown_request` decorated function. This is
+ not actually called by the :class:`Flask` object itself but is always
+ triggered when the request context is popped. That way we have a
+ tighter control over certain resources under testing environments.
+ """
+ funcs = reversed(self.teardown_request_funcs.get(None, ()))
+ 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]
+ for func in funcs:
+ rv = func(exc)
+ if rv is not None:
+ return rv
+ request_tearing_down.send(self)
+
+ def request_context(self, environ):
+ """Creates a :class:`~flask.ctx.RequestContext` from the given
+ environment and binds it to the current context. This must be used in
+ combination with the `with` statement because the request is only bound
+ to the current context for the duration of the `with` block.
+
+ Example usage::
+
+ with app.request_context(environ):
+ do_something_with(request)
+
+ The object returned can also be used without the `with` statement
+ which is useful for working in the shell. The example above is
+ doing exactly the same as this code::
+
+ ctx = app.request_context(environ)
+ ctx.push()
+ try:
+ do_something_with(request)
+ finally:
+ ctx.pop()
+
+ .. versionchanged:: 0.3
+ Added support for non-with statement usage and `with` statement
+ is now passed the ctx object.
+
+ :param environ: a WSGI environment
+ """
+ return RequestContext(self, environ)
+
+ def test_request_context(self, *args, **kwargs):
+ """Creates a WSGI environment from the given values (see
+ :func:`werkzeug.test.EnvironBuilder` for more information, this
+ function accepts the same arguments).
+ """
+ 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
+ `__call__` so that middlewares can be applied without losing a
+ reference to the class. So instead of doing this::
+
+ app = MyMiddleware(app)
+
+ It's a better idea to do this instead::
+
+ app.wsgi_app = MyMiddleware(app.wsgi_app)
+
+ Then you still have the original application object around and
+ can continue to call methods on it.
+
+ .. versionchanged:: 0.7
+ The behavior of the before and after request callbacks was changed
+ under error conditions and a new callback was added that will
+ always execute at the end of the request, independent on if an
+ error ocurred or not. See :ref:`callbacks-and-errors`.
+
+ :param environ: a WSGI environment
+ :param start_response: a callable accepting a status code,
+ a list of headers and an optional
+ exception context to start the response
+ """
+ with self.request_context(environ):
+ try:
+ response = self.full_dispatch_request()
+ except Exception, e:
+ response = self.make_response(self.handle_exception(e))
+ return response(environ, start_response)
+
+ @property
+ def modules(self):
+ from warnings import warn
+ warn(DeprecationWarning('Flask.modules is deprecated, use '
+ 'Flask.blueprints instead'), stacklevel=2)
+ return self.blueprints
+
+ def __call__(self, environ, start_response):
+ """Shortcut for :attr:`wsgi_app`."""
+ return self.wsgi_app(environ, start_response)
diff --git a/websdk/flask/blueprints.py b/websdk/flask/blueprints.py
new file mode 100644
index 0000000..ccdda38
--- /dev/null
+++ b/websdk/flask/blueprints.py
@@ -0,0 +1,321 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.blueprints
+ ~~~~~~~~~~~~~~~~
+
+ Blueprints are the recommended way to implement larger or more
+ pluggable applications in Flask 0.7 and later.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from functools import update_wrapper
+
+from .helpers import _PackageBoundObject, _endpoint_from_view_func
+
+
+class BlueprintSetupState(object):
+ """Temporary holder object for registering a blueprint with the
+ application. An instance of this class is created by the
+ :meth:`~flask.Blueprint.make_setup_state` method and later passed
+ to all register callback functions.
+ """
+
+ def __init__(self, blueprint, app, options, first_registration):
+ #: a reference to the current application
+ self.app = app
+
+ #: a reference to the blurprint that created this setup state.
+ self.blueprint = blueprint
+
+ #: a dictionary with all options that were passed to the
+ #: :meth:`~flask.Flask.register_blueprint` method.
+ self.options = options
+
+ #: as blueprints can be registered multiple times with the
+ #: application and not everything wants to be registered
+ #: multiple times on it, this attribute can be used to figure
+ #: out if the blueprint was registered in the past already.
+ self.first_registration = first_registration
+
+ subdomain = self.options.get('subdomain')
+ if subdomain is None:
+ subdomain = self.blueprint.subdomain
+
+ #: The subdomain that the blueprint should be active for, `None`
+ #: otherwise.
+ self.subdomain = subdomain
+
+ url_prefix = self.options.get('url_prefix')
+ if url_prefix is None:
+ url_prefix = self.blueprint.url_prefix
+
+ #: The prefix that should be used for all URLs defined on the
+ #: blueprint.
+ self.url_prefix = url_prefix
+
+ #: A dictionary with URL defaults that is added to each and every
+ #: URL that was defined with the blueprint.
+ 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):
+ """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.
+ """
+ if self.url_prefix:
+ rule = self.url_prefix + rule
+ options.setdefault('subdomain', self.subdomain)
+ if endpoint is None:
+ endpoint = _endpoint_from_view_func(view_func)
+ defaults = self.url_defaults
+ 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)
+
+
+class Blueprint(_PackageBoundObject):
+ """Represents a blueprint. A blueprint is an object that records
+ functions that will be called with the
+ :class:`~flask.blueprint.BlueprintSetupState` later to register functions
+ or other things on the main application. See :ref:`blueprints` for more
+ information.
+
+ .. versionadded:: 0.7
+ """
+
+ 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):
+ _PackageBoundObject.__init__(self, import_name, template_folder)
+ self.name = name
+ self.url_prefix = url_prefix
+ self.subdomain = subdomain
+ self.static_folder = static_folder
+ self.static_url_path = static_url_path
+ self.deferred_functions = []
+ self.view_functions = {}
+ if url_defaults is None:
+ url_defaults = {}
+ self.url_values_defaults = url_defaults
+
+ def record(self, func):
+ """Registers a function that is called when the blueprint is
+ registered on the application. This function is called with the
+ state as argument as returned by the :meth:`make_setup_state`
+ method.
+ """
+ if self._got_registered_once and self.warn_on_modifications:
+ from warnings import warn
+ warn(Warning('The blueprint was already registered once '
+ 'but is getting modified now. These changes '
+ 'will not show up.'))
+ self.deferred_functions.append(func)
+
+ def record_once(self, func):
+ """Works like :meth:`record` but wraps the function in another
+ function that will ensure the function is only called once. If the
+ blueprint is registered a second time on the application, the
+ function passed is not called.
+ """
+ def wrapper(state):
+ if state.first_registration:
+ func(state)
+ return self.record(update_wrapper(wrapper, func))
+
+ 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):
+ """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
+ :func:`~flask.Flask.register_blueprint` are directly forwarded to this
+ method in the `options` dictionary.
+ """
+ self._got_registered_once = True
+ 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')
+
+ for deferred in self.deferred_functions:
+ deferred(state)
+
+ def route(self, rule, **options):
+ """Like :meth:`Flask.route` but for a blueprint. The endpoint for the
+ :func:`url_for` function is prefixed with the name of the blueprint.
+ """
+ def decorator(f):
+ 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):
+ """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))
+
+ def endpoint(self, endpoint):
+ """Like :meth:`Flask.endpoint` but for a blueprint. This does not
+ prefix the endpoint with the blueprint name, this has to be done
+ explicitly by the user of this method. If the endpoint is prefixed
+ with a `.` it will be registered to the current blueprint, otherwise
+ it's an application independent endpoint.
+ """
+ def decorator(f):
+ def register_endpoint(state):
+ state.app.view_functions[endpoint] = f
+ self.record_once(register_endpoint)
+ return f
+ return decorator
+
+ def before_request(self, f):
+ """Like :meth:`Flask.before_request` but for a blueprint. This function
+ is only executed before each request that is handled by a function of
+ that blueprint.
+ """
+ self.record_once(lambda s: s.app.before_request_funcs
+ .setdefault(self.name, []).append(f))
+ return f
+
+ def before_app_request(self, f):
+ """Like :meth:`Flask.before_request`. Such a function is executed
+ before each request, even if outside of a blueprint.
+ """
+ self.record_once(lambda s: s.app.before_request_funcs
+ .setdefault(None, []).append(f))
+ return f
+
+ def before_app_first_request(self, f):
+ """Like :meth:`Flask.before_first_request`. Such a function is
+ executed before the first request to the application.
+ """
+ self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
+ return f
+
+ def after_request(self, f):
+ """Like :meth:`Flask.after_request` but for a blueprint. This function
+ is only executed after each request that is handled by a function of
+ that blueprint.
+ """
+ self.record_once(lambda s: s.app.after_request_funcs
+ .setdefault(self.name, []).append(f))
+ return f
+
+ def after_app_request(self, f):
+ """Like :meth:`Flask.after_request` but for a blueprint. Such a function
+ is executed after each request, even if outside of the blueprint.
+ """
+ self.record_once(lambda s: s.app.after_request_funcs
+ .setdefault(None, []).append(f))
+ return f
+
+ def teardown_request(self, f):
+ """Like :meth:`Flask.teardown_request` but for a blueprint. This
+ function is only executed when tearing down requests handled by a
+ function of that blueprint. Teardown request functions are executed
+ when the request context is popped, even when no actual request was
+ performed.
+ """
+ self.record_once(lambda s: s.app.teardown_request_funcs
+ .setdefault(self.name, []).append(f))
+ return f
+
+ def teardown_app_request(self, f):
+ """Like :meth:`Flask.teardown_request` but for a blueprint. Such a
+ function is executed when tearing down each request, even if outside of
+ the blueprint.
+ """
+ self.record_once(lambda s: s.app.teardown_request_funcs
+ .setdefault(None, []).append(f))
+ return f
+
+ def context_processor(self, f):
+ """Like :meth:`Flask.context_processor` but for a blueprint. This
+ function is only executed for requests handled by a blueprint.
+ """
+ self.record_once(lambda s: s.app.template_context_processors
+ .setdefault(self.name, []).append(f))
+ return f
+
+ def app_context_processor(self, f):
+ """Like :meth:`Flask.context_processor` but for a blueprint. Such a
+ function is executed each request, even if outside of the blueprint.
+ """
+ self.record_once(lambda s: s.app.template_context_processors
+ .setdefault(None, []).append(f))
+ return f
+
+ def app_errorhandler(self, code):
+ """Like :meth:`Flask.errorhandler` but for a blueprint. This
+ handler is used for all requests, even if outside of the blueprint.
+ """
+ def decorator(f):
+ self.record_once(lambda s: s.app.errorhandler(code)(f))
+ return f
+ return decorator
+
+ def url_value_preprocessor(self, f):
+ """Registers a function as URL value preprocessor for this
+ blueprint. It's called before the view functions are called and
+ can modify the url values provided.
+ """
+ self.record_once(lambda s: s.app.url_value_preprocessors
+ .setdefault(self.name, []).append(f))
+ return f
+
+ def url_defaults(self, f):
+ """Callback function for URL defaults for this blueprint. It's called
+ with the endpoint and values and should update the values passed
+ in place.
+ """
+ self.record_once(lambda s: s.app.url_default_functions
+ .setdefault(self.name, []).append(f))
+ return f
+
+ 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_preprocessors
+ .setdefault(None, []).append(f))
+ return f
+
+ def app_url_defaults(self, f):
+ """Same as :meth:`url_defaults` but application wide.
+ """
+ self.record_once(lambda s: s.app.url_default_functions
+ .setdefault(None, []).append(f))
+ return f
+
+ def errorhandler(self, code_or_exception):
+ """Registers an error handler that becomes active for this blueprint
+ only. Please be aware that routing does not happen local to a
+ blueprint so an error handler for 404 usually is not handled by
+ a blueprint unless it is caused inside a view function. Another
+ special case is the 500 internal server error which is always looked
+ up from the application.
+
+ Otherwise works as the :meth:`~flask.Flask.errorhandler` decorator
+ of the :class:`~flask.Flask` object.
+ """
+ def decorator(f):
+ self.record_once(lambda s: s.app._register_error_handler(
+ self.name, code_or_exception, f))
+ return f
+ return decorator
diff --git a/websdk/flask/config.py b/websdk/flask/config.py
new file mode 100644
index 0000000..67dbf9b
--- /dev/null
+++ b/websdk/flask/config.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.config
+ ~~~~~~~~~~~~
+
+ Implements the configuration related objects.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import imp
+import os
+import errno
+
+from werkzeug.utils import import_string
+
+
+class ConfigAttribute(object):
+ """Makes an attribute forward to the config"""
+
+ 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
+ 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
+
+
+class Config(dict):
+ """Works exactly like a dict but provides ways to fill it from files
+ or special dictionaries. There are two common patterns to populate the
+ config.
+
+ Either you can fill the config from a config file::
+
+ app.config.from_pyfile('yourconfig.cfg')
+
+ Or alternatively you can define the configuration options in the
+ module that calls :meth:`from_object` or provide an import path to
+ a module that should be loaded. It is also possible to tell it to
+ use the same module and with that provide the configuration values
+ just before the call::
+
+ DEBUG = True
+ SECRET_KEY = 'development key'
+ app.config.from_object(__name__)
+
+ In both cases (loading from any Python file or loading from modules),
+ only uppercase keys are added to the config. This makes it possible to use
+ lowercase values in the config file for temporary values that are not added
+ to the config or to define the config keys in the same file that implements
+ the application.
+
+ Probably the most interesting way to load configurations is from an
+ environment variable pointing to a file::
+
+ app.config.from_envvar('YOURAPPLICATION_SETTINGS')
+
+ In this case before launching the application you have to set this
+ environment variable to the file you want to use. On Linux and OS X
+ use the export statement::
+
+ export YOURAPPLICATION_SETTINGS='/path/to/config/file'
+
+ On windows use `set` instead.
+
+ :param root_path: path to which files are read relative from. When the
+ config object is created by the application, this is
+ the application's :attr:`~flask.Flask.root_path`.
+ :param defaults: an optional dictionary of default values
+ """
+
+ def __init__(self, root_path, defaults=None):
+ dict.__init__(self, defaults or {})
+ self.root_path = root_path
+
+ def from_envvar(self, variable_name, silent=False):
+ """Loads a configuration from an environment variable pointing to
+ a configuration file. This is basically just a shortcut with nicer
+ error messages for this line of code::
+
+ app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
+
+ :param variable_name: name of the environment variable
+ :param silent: set to `True` if you want silent failure for missing
+ files.
+ :return: bool. `True` if able to load config, `False` otherwise.
+ """
+ rv = os.environ.get(variable_name)
+ if not rv:
+ if silent:
+ return False
+ raise RuntimeError('The environment variable %r is not set '
+ 'and as such configuration could not be '
+ 'loaded. Set this variable and make it '
+ 'point to a configuration file' %
+ variable_name)
+ self.from_pyfile(rv)
+ return True
+
+ def from_pyfile(self, filename, silent=False):
+ """Updates the values in the config from a Python file. This function
+ behaves as if the file was imported as module with the
+ :meth:`from_object` function.
+
+ :param filename: the filename of the config. This can either be an
+ absolute filename or a filename relative to the
+ root path.
+ :param silent: set to `True` if you want silent failure for missing
+ files.
+
+ .. versionadded:: 0.7
+ `silent` parameter.
+ """
+ filename = os.path.join(self.root_path, filename)
+ d = imp.new_module('config')
+ d.__file__ = filename
+ try:
+ execfile(filename, d.__dict__)
+ except IOError, e:
+ if silent and e.errno in (errno.ENOENT, errno.EISDIR):
+ return False
+ e.strerror = 'Unable to load configuration file (%s)' % e.strerror
+ raise
+ self.from_object(d)
+ return True
+
+ def from_object(self, obj):
+ """Updates the values from the given object. An object can be of one
+ of the following two types:
+
+ - a string: in this case the object with that name will be imported
+ - an actual object reference: that object is used directly
+
+ Objects are usually either modules or classes.
+
+ Just the uppercase variables in that object are stored in the config.
+ Example usage::
+
+ app.config.from_object('yourapplication.default_config')
+ from yourapplication import default_config
+ app.config.from_object(default_config)
+
+ You should not use this function to load the actual configuration but
+ rather configuration defaults. The actual config should be loaded
+ with :meth:`from_pyfile` and ideally from a location not within the
+ package because the package might be installed system wide.
+
+ :param obj: an import name or object
+ """
+ if isinstance(obj, basestring):
+ obj = import_string(obj)
+ for key in dir(obj):
+ if key.isupper():
+ self[key] = getattr(obj, key)
+
+ def __repr__(self):
+ return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
diff --git a/websdk/flask/ctx.py b/websdk/flask/ctx.py
new file mode 100644
index 0000000..26781db
--- /dev/null
+++ b/websdk/flask/ctx.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.ctx
+ ~~~~~~~~~
+
+ Implements the objects required to keep the context.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from werkzeug.exceptions import HTTPException
+
+from .globals import _request_ctx_stack
+from .module import blueprint_is_module
+
+
+class _RequestGlobals(object):
+ pass
+
+
+def has_request_context():
+ """If you have code that wants to test if a request context is there or
+ not this function can be used. For instance if you want to take advantage
+ of request information is it's available but fail silently if the request
+ object is unavailable.
+
+ ::
+
+ class User(db.Model):
+
+ def __init__(self, username, remote_addr=None):
+ self.username = username
+ if remote_addr is None and has_request_context():
+ remote_addr = request.remote_addr
+ self.remote_addr = remote_addr
+
+ Alternatively you can also just test any of the context bound objects
+ (such as :class:`request` or :class:`g` for truthness)::
+
+ class User(db.Model):
+
+ def __init__(self, username, remote_addr=None):
+ self.username = username
+ if remote_addr is None and request:
+ remote_addr = request.remote_addr
+ self.remote_addr = remote_addr
+
+ .. versionadded:: 0.7
+ """
+ return _request_ctx_stack.top is not None
+
+
+class RequestContext(object):
+ """The request context contains all request relevant information. It is
+ created at the beginning of the request and pushed to the
+ `_request_ctx_stack` and removed at the end of it. It will create the
+ URL adapter and request object for the WSGI environment provided.
+
+ Do not attempt to use this class directly, instead use
+ :meth:`~flask.Flask.test_request_context` and
+ :meth:`~flask.Flask.request_context` to create this object.
+
+ When the request context is popped, it will evaluate all the
+ functions registered on the application for teardown execution
+ (:meth:`~flask.Flask.teardown_request`).
+
+ The request context is automatically popped at the end of the request
+ for you. In debug mode the request context is kept around if
+ exceptions happen so that interactive debuggers have a chance to
+ introspect the data. With 0.4 this can also be forced for requests
+ that did not fail and outside of `DEBUG` mode. By setting
+ ``'flask._preserve_context'`` to `True` on the WSGI environment the
+ context will not pop itself at the end of the request. This is used by
+ the :meth:`~flask.Flask.test_client` for example to implement the
+ deferred cleanup functionality.
+
+ You might find this helpful for unittests where you need the
+ information from the context local around for a little longer. Make
+ sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
+ that situation, otherwise your unittests will leak memory.
+ """
+
+ def __init__(self, app, environ):
+ self.app = app
+ self.request = app.request_class(environ)
+ self.url_adapter = app.create_url_adapter(self.request)
+ self.g = _RequestGlobals()
+ 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()
+
+ # XXX: Support for deprecated functionality. This is doing away with
+ # Flask 1.0
+ blueprint = self.request.blueprint
+ if blueprint is not None:
+ # better safe than sorry, we don't want to break code that
+ # already worked
+ bp = app.blueprints.get(blueprint)
+ if bp is not None and blueprint_is_module(bp):
+ self.request._is_old_module = True
+
+ def match_request(self):
+ """Can be overridden by a subclass to hook into the matching
+ of the request.
+ """
+ try:
+ url_rule, self.request.view_args = \
+ self.url_adapter.match(return_rule=True)
+ self.request.url_rule = url_rule
+ except HTTPException, e:
+ self.request.routing_exception = e
+
+ 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
+ # available. This allows a custom open_session method to use the
+ # request context (e.g. flask-sqlalchemy).
+ self.session = self.app.open_session(self.request)
+ if self.session is None:
+ self.session = self.app.make_null_session()
+
+ def pop(self):
+ """Pops the request context and unbinds it by doing that. This will
+ also trigger the execution of functions registered by the
+ :meth:`~flask.Flask.teardown_request` decorator.
+ """
+ self.preserved = False
+ self.app.do_teardown_request()
+ rv = _request_ctx_stack.pop()
+ assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
+ % (rv, self)
+
+ def __enter__(self):
+ self.push()
+ return self
+
+ def __exit__(self, exc_type, exc_value, tb):
+ # do not pop the request stack if we are in debug mode and an
+ # exception happened. This will allow the debugger to still
+ # 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 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
+ )
diff --git a/websdk/flask/debughelpers.py b/websdk/flask/debughelpers.py
new file mode 100644
index 0000000..edf8c11
--- /dev/null
+++ b/websdk/flask/debughelpers.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.debughelpers
+ ~~~~~~~~~~~~~~~~~~
+
+ Various helpers to make the development experience better.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+
+class DebugFilesKeyError(KeyError, AssertionError):
+ """Raised from request.files during debugging. The idea is that it can
+ provide a better error message than just a generic KeyError/BadRequest.
+ """
+
+ def __init__(self, request, key):
+ form_matches = request.form.getlist(key)
+ buf = ['You tried to access the file "%s" in the request.files '
+ 'dictionary but it does not exist. The mimetype for the request '
+ 'is "%s" instead of "multipart/form-data" which means that no '
+ 'file contents were transmitted. To fix this error you should '
+ 'provide enctype="multipart/form-data" in your form.' %
+ (key, request.mimetype)]
+ if form_matches:
+ buf.append('\n\nThe browser instead transmitted some file names. '
+ 'This was submitted: %s' % ', '.join('"%s"' % x
+ for x in form_matches))
+ self.msg = ''.join(buf).encode('utf-8')
+
+ def __str__(self):
+ return self.msg
+
+
+class FormDataRoutingRedirect(AssertionError):
+ """This exception is raised by Flask in debug mode if it detects a
+ redirect caused by the routing system when the request method is not
+ GET, HEAD or OPTIONS. Reasoning: form data will be dropped.
+ """
+
+ def __init__(self, request):
+ exc = request.routing_exception
+ buf = ['A request was sent to this URL (%s) but a redirect was '
+ 'issued automatically by the routing system to "%s".'
+ % (request.url, exc.new_url)]
+
+ # In case just a slash was appended we can be extra helpful
+ if request.base_url + '/' == exc.new_url.split('?')[0]:
+ buf.append(' The URL was defined with a trailing slash so '
+ 'Flask will automatically redirect to the URL '
+ 'with the trailing slash if it was accessed '
+ 'without one.')
+
+ 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 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'))
+
+
+def attach_enctype_error_multidict(request):
+ """Since Flask 0.8 we're monkeypatching the files object in case a
+ request is detected that does not use multipart form data but the files
+ object is accessed.
+ """
+ oldcls = request.files.__class__
+ class newcls(oldcls):
+ def __getitem__(self, key):
+ try:
+ return oldcls.__getitem__(self, key)
+ except KeyError, e:
+ if key not in request.form:
+ raise
+ raise DebugFilesKeyError(request, key)
+ newcls.__name__ = oldcls.__name__
+ newcls.__module__ = oldcls.__module__
+ request.files.__class__ = newcls
diff --git a/websdk/flask/ext/__init__.py b/websdk/flask/ext/__init__.py
new file mode 100644
index 0000000..f29958a
--- /dev/null
+++ b/websdk/flask/ext/__init__.py
@@ -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
diff --git a/websdk/flask/exthook.py b/websdk/flask/exthook.py
new file mode 100644
index 0000000..bb1deb2
--- /dev/null
+++ b/websdk/flask/exthook.py
@@ -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
diff --git a/websdk/flask/globals.py b/websdk/flask/globals.py
new file mode 100644
index 0000000..16580d1
--- /dev/null
+++ b/websdk/flask/globals.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.globals
+ ~~~~~~~~~~~~~
+
+ Defines all the global objects that are proxies to the current
+ active context.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from functools import partial
+from werkzeug.local import LocalStack, LocalProxy
+
+def _lookup_object(name):
+ top = _request_ctx_stack.top
+ if top is None:
+ raise RuntimeError('working outside of request context')
+ return getattr(top, name)
+
+
+# context locals
+_request_ctx_stack = LocalStack()
+current_app = LocalProxy(partial(_lookup_object, 'app'))
+request = LocalProxy(partial(_lookup_object, 'request'))
+session = LocalProxy(partial(_lookup_object, 'session'))
+g = LocalProxy(partial(_lookup_object, 'g'))
diff --git a/websdk/flask/helpers.py b/websdk/flask/helpers.py
new file mode 100644
index 0000000..72c8f17
--- /dev/null
+++ b/websdk/flask/helpers.py
@@ -0,0 +1,649 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.helpers
+ ~~~~~~~~~~~~~
+
+ Implements various helpers.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import os
+import sys
+import posixpath
+import mimetypes
+from time import time
+from zlib import adler32
+from threading import RLock
+
+# try to load the best simplejson implementation available. If JSON
+# is not installed, we add a failing class.
+json_available = True
+json = None
+try:
+ import simplejson as json
+except ImportError:
+ try:
+ import json
+ except ImportError:
+ try:
+ # Google Appengine offers simplejson via django
+ from django.utils import simplejson as json
+ except ImportError:
+ json_available = False
+
+
+from werkzeug.datastructures import Headers
+from werkzeug.exceptions import NotFound
+
+# this was moved in 0.7
+try:
+ from werkzeug.wsgi import wrap_file
+except ImportError:
+ from werkzeug.utils import wrap_file
+
+from jinja2 import FileSystemLoader
+
+from .globals import session, _request_ctx_stack, current_app, request
+
+
+def _assert_have_json():
+ """Helper function that fails if JSON is unavailable."""
+ 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('/'):
+
+ def _tojson_filter(*args, **kwargs):
+ if __debug__:
+ _assert_have_json()
+ return json.dumps(*args, **kwargs).replace('/', '\\/')
+else:
+ _tojson_filter = json.dumps
+
+
+# sentinel
+_missing = object()
+
+
+# what separators does this operating system provide that are not a slash?
+# this is used by the send_from_directory function to ensure that nobody is
+# able to access files from outside the filesystem.
+_os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
+ if sep not in (None, '/'))
+
+
+def _endpoint_from_view_func(view_func):
+ """Internal helper that returns the default endpoint for a given
+ function. This always is the function name.
+ """
+ assert view_func is not None, 'expected view func if endpoint ' \
+ 'is not provided.'
+ return view_func.__name__
+
+
+def jsonify(*args, **kwargs):
+ """Creates a :class:`~flask.Response` with the JSON representation of
+ the given arguments with an `application/json` mimetype. The arguments
+ to this function are the same as to the :class:`dict` constructor.
+
+ Example usage::
+
+ @app.route('/_get_current_user')
+ def get_current_user():
+ return jsonify(username=g.user.username,
+ email=g.user.email,
+ id=g.user.id)
+
+ This will send a JSON response like this to the browser::
+
+ {
+ "username": "admin",
+ "email": "admin@localhost",
+ "id": 42
+ }
+
+ This requires Python 2.6 or an installed version of simplejson. For
+ security reasons only objects are supported toplevel. For more
+ information about this, have a look at :ref:`json-security`.
+
+ .. versionadded:: 0.2
+ """
+ if __debug__:
+ _assert_have_json()
+ return current_app.response_class(json.dumps(dict(*args, **kwargs),
+ indent=None if request.is_xhr else 2), mimetype='application/json')
+
+
+def make_response(*args):
+ """Sometimes it is necessary to set additional headers in a view. Because
+ views do not have to return response objects but can return a value that
+ is converted into a response object by Flask itself, it becomes tricky to
+ add headers to it. This function can be called instead of using a return
+ and you will get a response object which you can use to attach headers.
+
+ If view looked like this and you want to add a new header::
+
+ def index():
+ return render_template('index.html', foo=42)
+
+ You can now do something like this::
+
+ def index():
+ response = make_response(render_template('index.html', foo=42))
+ response.headers['X-Parachutes'] = 'parachutes are cool'
+ return response
+
+ This function accepts the very same arguments you can return from a
+ view function. This for example creates a response with a 404 error
+ code::
+
+ 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
+ - if one argument is passed, :meth:`flask.Flask.make_response`
+ is invoked with it.
+ - if more than one argument is passed, the arguments are passed
+ to the :meth:`flask.Flask.make_response` function as tuple.
+
+ .. versionadded:: 0.6
+ """
+ if not args:
+ return current_app.response_class()
+ if len(args) == 1:
+ args = args[0]
+ return current_app.make_response(args)
+
+
+def url_for(endpoint, **values):
+ """Generates a URL to the given endpoint with the method provided.
+
+ Variable arguments that are unknown to the target endpoint are appended
+ to the generated URL as query arguments. If the value of a query argument
+ is `None`, the whole pair is skipped. In case blueprints are active
+ you can shortcut references to the same blueprint by prefixing the
+ local endpoint with a dot (``.``).
+
+ This will reference the index function local to the current blueprint::
+
+ url_for('.index')
+
+ For more information, head over to the :ref:`Quickstart <url-building>`.
+
+ :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.
+ """
+ ctx = _request_ctx_stack.top
+ blueprint_name = request.blueprint
+ if not ctx.request._is_old_module:
+ if endpoint[:1] == '.':
+ if blueprint_name is not None:
+ endpoint = blueprint_name + endpoint
+ else:
+ endpoint = endpoint[1:]
+ else:
+ # TODO: get rid of this deprecated functionality in 1.0
+ if '.' not in endpoint:
+ if blueprint_name is not None:
+ endpoint = blueprint_name + '.' + endpoint
+ elif endpoint.startswith('.'):
+ endpoint = endpoint[1:]
+ external = values.pop('_external', False)
+ ctx.app.inject_url_defaults(endpoint, values)
+ return ctx.url_adapter.build(endpoint, values, force_external=external)
+
+
+def get_template_attribute(template_name, attribute):
+ """Loads a macro (or variable) a template exports. This can be used to
+ invoke a macro from within Python code. If you for example have a
+ template named `_cider.html` with the following contents:
+
+ .. sourcecode:: html+jinja
+
+ {% macro hello(name) %}Hello {{ name }}!{% endmacro %}
+
+ You can access this from Python code like this::
+
+ hello = get_template_attribute('_cider.html', 'hello')
+ return hello('World')
+
+ .. versionadded:: 0.2
+
+ :param template_name: the name of the template
+ :param attribute: the name of the variable of macro to acccess
+ """
+ return getattr(current_app.jinja_env.get_template(template_name).module,
+ attribute)
+
+
+def flash(message, category='message'):
+ """Flashes a message to the next request. In order to remove the
+ flashed message from the session and to display it to the user,
+ the template has to call :func:`get_flashed_messages`.
+
+ .. versionchanged: 0.3
+ `category` parameter added.
+
+ :param message: the message to be flashed.
+ :param category: the category for the message. The following values
+ are recommended: ``'message'`` for any kind of message,
+ ``'error'`` for errors, ``'info'`` for information
+ messages and ``'warning'`` for warnings. However any
+ kind of string can be used as category.
+ """
+ session.setdefault('_flashes', []).append((category, message))
+
+
+def get_flashed_messages(with_categories=False):
+ """Pulls all flashed messages from the session and returns them.
+ Further calls in the same request to the function will return
+ the same messages. By default just the messages are returned,
+ but when `with_categories` is set to `True`, the return value will
+ be a list of tuples in the form ``(category, message)`` instead.
+
+ Example usage:
+
+ .. sourcecode:: html+jinja
+
+ {% for category, msg in get_flashed_messages(with_categories=true) %}
+ <p class=flash-{{ category }}>{{ msg }}
+ {% endfor %}
+
+ .. versionchanged:: 0.3
+ `with_categories` parameter added.
+
+ :param with_categories: set to `True` to also receive categories.
+ """
+ flashes = _request_ctx_stack.top.flashes
+ if flashes is None:
+ _request_ctx_stack.top.flashes = flashes = session.pop('_flashes') \
+ if '_flashes' in session else []
+ if not with_categories:
+ return [x[1] for x in flashes]
+ return flashes
+
+
+def send_file(filename_or_fp, mimetype=None, as_attachment=False,
+ attachment_filename=None, add_etags=True,
+ cache_timeout=60 * 60 * 12, conditional=False):
+ """Sends the contents of a file to the client. This will use the
+ most efficient method available and configured. By default it will
+ try to use the WSGI server's file_wrapper support. Alternatively
+ you can set the application's :attr:`~Flask.use_x_sendfile` attribute
+ to ``True`` to directly emit an `X-Sendfile` header. This however
+ requires support of the underlying webserver for `X-Sendfile`.
+
+ By default it will try to guess the mimetype for you, but you can
+ also explicitly provide one. For extra security you probably want
+ to send certain files as attachment (HTML for instance). The mimetype
+ guessing requires a `filename` or an `attachment_filename` to be
+ provided.
+
+ Please never pass filenames to this function from user sources without
+ checking them first. Something like this is usually sufficient to
+ avoid security problems::
+
+ if '..' in filename or filename.startswith('/'):
+ abort(404)
+
+ .. versionadded:: 0.2
+
+ .. versionadded:: 0.5
+ The `add_etags`, `cache_timeout` and `conditional` parameters were
+ added. The default behaviour is now to attach etags.
+
+ .. versionchanged:: 0.7
+ mimetype guessing and etag support for file objects was
+ deprecated because it was unreliable. Pass a filename if you are
+ able to, otherwise attach an etag yourself. This functionality
+ will be removed in Flask 1.0
+
+ :param filename_or_fp: the filename of the file to send. This is
+ relative to the :attr:`~Flask.root_path` if a
+ relative path is specified.
+ Alternatively a file object might be provided
+ in which case `X-Sendfile` might not work and
+ fall back to the traditional method. Make sure
+ that the file pointer is positioned at the start
+ of data to send before calling :func:`send_file`.
+ :param mimetype: the mimetype of the file if provided, otherwise
+ auto detection happens.
+ :param as_attachment: set to `True` if you want to send this file with
+ a ``Content-Disposition: attachment`` header.
+ :param attachment_filename: the filename for the attachment if it
+ differs from the file's filename.
+ :param add_etags: set to `False` to disable attaching of etags.
+ :param conditional: set to `True` to enable conditional responses.
+ :param cache_timeout: the timeout in seconds for the headers.
+ """
+ mtime = None
+ if isinstance(filename_or_fp, basestring):
+ filename = filename_or_fp
+ file = None
+ else:
+ from warnings import warn
+ file = filename_or_fp
+ filename = getattr(file, 'name', None)
+
+ # XXX: this behaviour is now deprecated because it was unreliable.
+ # removed in Flask 1.0
+ if not attachment_filename and not mimetype \
+ and isinstance(filename, basestring):
+ warn(DeprecationWarning('The filename support for file objects '
+ 'passed to send_file is now deprecated. Pass an '
+ 'attach_filename if you want mimetypes to be guessed.'),
+ stacklevel=2)
+ if add_etags:
+ warn(DeprecationWarning('In future flask releases etags will no '
+ 'longer be generated for file objects passed to the send_file '
+ 'function because this behaviour was unreliable. Pass '
+ 'filenames instead if possible, otherwise attach an etag '
+ 'yourself based on another value'), stacklevel=2)
+
+ if filename is not None:
+ if not os.path.isabs(filename):
+ filename = os.path.join(current_app.root_path, filename)
+ if mimetype is None and (filename or attachment_filename):
+ mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
+ if mimetype is None:
+ mimetype = 'application/octet-stream'
+
+ headers = Headers()
+ if as_attachment:
+ if attachment_filename is None:
+ if filename is None:
+ raise TypeError('filename unavailable, required for '
+ 'sending as attachment')
+ attachment_filename = os.path.basename(filename)
+ headers.add('Content-Disposition', 'attachment',
+ filename=attachment_filename)
+
+ if current_app.use_x_sendfile and filename:
+ if file is not None:
+ file.close()
+ headers['X-Sendfile'] = filename
+ data = None
+ else:
+ if file is None:
+ file = open(filename, 'rb')
+ mtime = os.path.getmtime(filename)
+ data = wrap_file(request.environ, file)
+
+ rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
+ direct_passthrough=True)
+
+ # if we know the file modification date, we can store it as the
+ # the time of the last modification.
+ if mtime is not None:
+ rv.last_modified = int(mtime)
+
+ rv.cache_control.public = True
+ if cache_timeout:
+ rv.cache_control.max_age = cache_timeout
+ rv.expires = int(time() + cache_timeout)
+
+ if add_etags and filename is not None:
+ rv.set_etag('flask-%s-%s-%s' % (
+ os.path.getmtime(filename),
+ os.path.getsize(filename),
+ adler32(
+ filename.encode('utf8') if isinstance(filename, unicode)
+ else filename
+ ) & 0xffffffff
+ ))
+ if conditional:
+ rv = rv.make_conditional(request)
+ # make sure we don't send x-sendfile for servers that
+ # ignore the 304 status code for x-sendfile.
+ if rv.status_code == 304:
+ rv.headers.pop('x-sendfile', None)
+ return rv
+
+
+def safe_join(directory, filename):
+ """Safely join `directory` and `filename`.
+
+ Example usage::
+
+ @app.route('/wiki/<path:filename>')
+ def wiki_page(filename):
+ filename = safe_join(app.config['WIKI_FOLDER'], filename)
+ with open(filename, 'rb') as fd:
+ content = fd.read() # Read and process the file content...
+
+ :param directory: the base directory.
+ :param filename: the untrusted filename relative to that directory.
+ :raises: :class:`~werkzeug.exceptions.NotFound` if the resulting path
+ would fall out of `directory`.
+ """
+ filename = posixpath.normpath(filename)
+ for sep in _os_alt_seps:
+ if sep in filename:
+ raise NotFound()
+ if os.path.isabs(filename) or filename.startswith('../'):
+ raise NotFound()
+ return os.path.join(directory, filename)
+
+
+def send_from_directory(directory, filename, **options):
+ """Send a file from a given directory with :func:`send_file`. This
+ is a secure way to quickly expose static files from an upload folder
+ or something similar.
+
+ Example usage::
+
+ @app.route('/uploads/<path:filename>')
+ def download_file(filename):
+ return send_from_directory(app.config['UPLOAD_FOLDER'],
+ filename, as_attachment=True)
+
+ .. admonition:: Sending files and Performance
+
+ It is strongly recommended to activate either `X-Sendfile` support in
+ your webserver or (if no authentication happens) to tell the webserver
+ to serve files for the given path on its own without calling into the
+ web application for improved performance.
+
+ .. versionadded:: 0.5
+
+ :param directory: the directory where all the files are stored.
+ :param filename: the filename relative to that directory to
+ download.
+ :param options: optional keyword arguments that are directly
+ forwarded to :func:`send_file`.
+ """
+ filename = safe_join(directory, filename)
+ if not os.path.isfile(filename):
+ raise NotFound()
+ return send_file(filename, conditional=True, **options)
+
+
+def get_root_path(import_name):
+ """Returns the path to a package or cwd if that cannot be found. This
+ returns the path of a package or the folder that contains a module.
+
+ Not to be confused with the package path returned by :func:`find_package`.
+ """
+ __import__(import_name)
+ try:
+ 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()
+
+
+def find_package(import_name):
+ """Finds a package and returns the prefix (or None if the package is
+ not installed) as well as the folder that contains the package or
+ module as a tuple. The package path returned is the module that would
+ have to be added to the pythonpath in order to make it possible to
+ import the module. The prefix is the path below which a UNIX like
+ folder structure exists (lib, share etc.).
+ """
+ __import__(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))
+ if hasattr(root_mod, '__path__'):
+ package_path = os.path.dirname(package_path)
+
+ # leave the egg wrapper folder or the actual .egg on the filesystem
+ test_package_path = package_path
+ if os.path.basename(test_package_path).endswith('.egg'):
+ test_package_path = os.path.dirname(test_package_path)
+
+ site_parent, site_folder = os.path.split(test_package_path)
+ py_prefix = os.path.abspath(sys.prefix)
+ if test_package_path.startswith(py_prefix):
+ return py_prefix, package_path
+ elif site_folder.lower() == 'site-packages':
+ parent, folder = os.path.split(site_parent)
+ # Windows like installations
+ if folder.lower() == 'lib':
+ base_dir = parent
+ # UNIX like installations
+ elif os.path.basename(parent).lower() == 'lib':
+ base_dir = os.path.dirname(parent)
+ else:
+ base_dir = site_parent
+ return base_dir, package_path
+ return None, package_path
+
+
+class locked_cached_property(object):
+ """A decorator that converts a function into a lazy property. The
+ function wrapped is called the first time to retrieve the result
+ and then that calculated result is used the next time you access
+ the value. Works like the one in Werkzeug but has a lock for
+ thread safety.
+ """
+
+ def __init__(self, func, name=None, doc=None):
+ self.__name__ = name or func.__name__
+ self.__module__ = func.__module__
+ self.__doc__ = doc or func.__doc__
+ self.func = func
+ self.lock = RLock()
+
+ def __get__(self, obj, type=None):
+ if obj is None:
+ return self
+ with self.lock:
+ value = obj.__dict__.get(self.__name__, _missing)
+ if value is _missing:
+ value = self.func(obj)
+ obj.__dict__[self.__name__] = value
+ return value
+
+
+class _PackageBoundObject(object):
+
+ def __init__(self, import_name, template_folder=None):
+ #: The name of the package or module. Do not change this once
+ #: it was set by the constructor.
+ self.import_name = import_name
+
+ #: location of the templates. `None` if templates should not be
+ #: exposed.
+ self.template_folder = template_folder
+
+ #: Where is the app root located?
+ self.root_path = get_root_path(self.import_name)
+
+ self._static_folder = None
+ self._static_url_path = None
+
+ def _get_static_folder(self):
+ if self._static_folder is not None:
+ return os.path.join(self.root_path, self._static_folder)
+ def _set_static_folder(self, value):
+ self._static_folder = value
+ static_folder = property(_get_static_folder, _set_static_folder)
+ del _get_static_folder, _set_static_folder
+
+ def _get_static_url_path(self):
+ if self._static_url_path is None:
+ if self.static_folder is None:
+ return None
+ return '/' + os.path.basename(self.static_folder)
+ return self._static_url_path
+ def _set_static_url_path(self, value):
+ self._static_url_path = value
+ static_url_path = property(_get_static_url_path, _set_static_url_path)
+ del _get_static_url_path, _set_static_url_path
+
+ @property
+ def has_static_folder(self):
+ """This is `True` if the package bound object's container has a
+ folder named ``'static'``.
+
+ .. versionadded:: 0.5
+ """
+ return self.static_folder is not None
+
+ @locked_cached_property
+ def jinja_loader(self):
+ """The Jinja loader for this package bound object.
+
+ .. versionadded:: 0.5
+ """
+ if self.template_folder is not None:
+ return FileSystemLoader(os.path.join(self.root_path,
+ self.template_folder))
+
+ def send_static_file(self, filename):
+ """Function used internally to send static files from the static
+ folder to the browser.
+
+ .. versionadded:: 0.5
+ """
+ if not self.has_static_folder:
+ raise RuntimeError('No static folder for this object')
+ return send_from_directory(self.static_folder, filename)
+
+ def open_resource(self, resource, mode='rb'):
+ """Opens a resource from the application's resource folder. To see
+ how this works, consider the following folder structure::
+
+ /myapplication.py
+ /schema.sql
+ /static
+ /style.css
+ /templates
+ /layout.html
+ /index.html
+
+ If you want to open the `schema.sql` file you would do the
+ following::
+
+ with app.open_resource('schema.sql') as f:
+ contents = f.read()
+ do_something_with(contents)
+
+ :param resource: the name of the resource. To access resources within
+ subfolders use forward slashes as separator.
+ """
+ if mode not in ('r', 'rb'):
+ raise ValueError('Resources can only be opened for reading')
+ return open(os.path.join(self.root_path, resource), mode)
diff --git a/websdk/flask/logging.py b/websdk/flask/logging.py
new file mode 100644
index 0000000..b992aef
--- /dev/null
+++ b/websdk/flask/logging.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.logging
+ ~~~~~~~~~~~~~
+
+ Implements the logging support for Flask.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import absolute_import
+
+from logging import getLogger, StreamHandler, Formatter, getLoggerClass, DEBUG
+
+
+def create_logger(app):
+ """Creates a logger for the given application. This logger works
+ similar to a regular Python logger but changes the effective logging
+ level based on the application's debug flag. Furthermore this
+ function also removes all attached handlers in case there was a
+ logger with the log name before.
+ """
+ Logger = getLoggerClass()
+
+ class DebugLogger(Logger):
+ def getEffectiveLevel(x):
+ return DEBUG if app.debug else Logger.getEffectiveLevel(x)
+
+ class DebugHandler(StreamHandler):
+ def emit(x, record):
+ StreamHandler.emit(x, record) if app.debug else None
+
+ handler = DebugHandler()
+ handler.setLevel(DEBUG)
+ handler.setFormatter(Formatter(app.debug_log_format))
+ logger = getLogger(app.logger_name)
+ # just in case that was not a new logger, get rid of all the handlers
+ # already attached to it.
+ del logger.handlers[:]
+ logger.__class__ = DebugLogger
+ logger.addHandler(handler)
+ return logger
diff --git a/websdk/flask/module.py b/websdk/flask/module.py
new file mode 100644
index 0000000..1c4f466
--- /dev/null
+++ b/websdk/flask/module.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.module
+ ~~~~~~~~~~~~
+
+ Implements a class that represents module blueprints.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+import os
+
+from .blueprints import Blueprint
+
+
+def blueprint_is_module(bp):
+ """Used to figure out if something is actually a module"""
+ return isinstance(bp, Module)
+
+
+class Module(Blueprint):
+ """Deprecated module support. Until Flask 0.6 modules were a different
+ name of the concept now available as blueprints in Flask. They are
+ essentially doing the same but have some bad semantics for templates and
+ static files that were fixed with blueprints.
+
+ .. versionchanged:: 0.7
+ Modules were deprecated in favor for blueprints.
+ """
+
+ def __init__(self, import_name, name=None, url_prefix=None,
+ static_path=None, subdomain=None):
+ if name is None:
+ assert '.' in import_name, 'name required if package name ' \
+ 'does not point to a submodule'
+ name = import_name.rsplit('.', 1)[1]
+ Blueprint.__init__(self, name, import_name, url_prefix=url_prefix,
+ subdomain=subdomain, template_folder='templates')
+
+ if os.path.isdir(os.path.join(self.root_path, 'static')):
+ self._static_folder = 'static'
diff --git a/websdk/flask/session.py b/websdk/flask/session.py
new file mode 100644
index 0000000..4d4d2cd
--- /dev/null
+++ b/websdk/flask/session.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.session
+ ~~~~~~~~~~~~~
+
+ This module used to flask with the session global so we moved it
+ over to flask.sessions
+
+ :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 *
+
+Session = SecureCookieSession
+_NullSession = NullSession
diff --git a/websdk/flask/sessions.py b/websdk/flask/sessions.py
new file mode 100644
index 0000000..2795bb1
--- /dev/null
+++ b/websdk/flask/sessions.py
@@ -0,0 +1,205 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.sessions
+ ~~~~~~~~~~~~~~
+
+ Implements cookie based sessions based on Werkzeug's secure cookie
+ system.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from datetime import datetime
+from werkzeug.contrib.securecookie import SecureCookie
+
+
+class SessionMixin(object):
+ """Expands a basic dictionary with an accessors that are expected
+ by Flask extensions and users for the session.
+ """
+
+ def _get_permanent(self):
+ return self.get('_permanent', False)
+
+ def _set_permanent(self, value):
+ self['_permanent'] = bool(value)
+
+ #: this reflects the ``'_permanent'`` key in the dict.
+ permanent = property(_get_permanent, _set_permanent)
+ del _get_permanent, _set_permanent
+
+ #: some session backends can tell you if a session is new, but that is
+ #: not necessarily guaranteed. Use with caution. The default mixin
+ #: implementation just hardcodes `False` in.
+ new = False
+
+ #: for some backends this will always be `True`, but some backends will
+ #: default this to false and detect changes in the dictionary for as
+ #: long as changes do not happen on mutable structures in the session.
+ #: The default mixin implementation just hardcodes `True` in.
+ modified = True
+
+
+class SecureCookieSession(SecureCookie, SessionMixin):
+ """Expands the session with support for switching between permanent
+ and non-permanent sessions.
+ """
+
+
+class NullSession(SecureCookieSession):
+ """Class used to generate nicer error messages if sessions are not
+ available. Will still allow read-only access to the empty session
+ but fail on setting.
+ """
+
+ def _fail(self, *args, **kwargs):
+ raise RuntimeError('the session is unavailable because no secret '
+ 'key was set. Set the secret_key on the '
+ 'application to something unique and secret.')
+ __setitem__ = __delitem__ = clear = pop = popitem = \
+ update = setdefault = _fail
+ del _fail
+
+
+class SessionInterface(object):
+ """The basic interface you have to implement in order to replace the
+ default session interface which uses werkzeug's securecookie
+ implementation. The only methods you have to implement are
+ :meth:`open_session` and :meth:`save_session`, the others have
+ useful defaults which you don't need to change.
+
+ The session object returned by the :meth:`open_session` method has to
+ provide a dictionary like interface plus the properties and methods
+ from the :class:`SessionMixin`. We recommend just subclassing a dict
+ and adding that mixin::
+
+ class Session(dict, SessionMixin):
+ pass
+
+ If :meth:`open_session` returns `None` Flask will call into
+ :meth:`make_null_session` to create a session that acts as replacement
+ if the session support cannot work because some requirement is not
+ fulfilled. The default :class:`NullSession` class that is created
+ will complain that the secret key was not set.
+
+ To replace the session interface on an application all you have to do
+ is to assign :attr:`flask.Flask.session_interface`::
+
+ app = Flask(__name__)
+ app.session_interface = MySessionInterface()
+
+ .. versionadded:: 0.8
+ """
+
+ #: :meth:`make_null_session` will look here for the class that should
+ #: be created when a null session is requested. Likewise the
+ #: :meth:`is_null_session` method will perform a typecheck against
+ #: this type.
+ null_session_class = NullSession
+
+ def make_null_session(self, app):
+ """Creates a null session which acts as a replacement object if the
+ real session support could not be loaded due to a configuration
+ error. This mainly aids the user experience because the job of the
+ null session is to still support lookup without complaining but
+ modifications are answered with a helpful error message of what
+ failed.
+
+ This creates an instance of :attr:`null_session_class` by default.
+ """
+ return self.null_session_class()
+
+ def is_null_session(self, obj):
+ """Checks if a given object is a null session. Null sessions are
+ not asked to be saved.
+
+ This checks if the object is an instance of :attr:`null_session_class`
+ by default.
+ """
+ return isinstance(obj, self.null_session_class)
+
+ def get_cookie_domain(self, app):
+ """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
+ default implementation returns now + the permanent session
+ lifetime configured on the application.
+ """
+ if session.permanent:
+ return datetime.utcnow() + app.permanent_session_lifetime
+
+ def open_session(self, app, request):
+ """This method has to be implemented and must either return `None`
+ in case the loading failed because of a configuration error or an
+ instance of a session object which implements a dictionary like
+ interface + the methods and attributes on :class:`SessionMixin`.
+ """
+ raise NotImplementedError()
+
+ def save_session(self, app, session, response):
+ """This is called for actual sessions returned by :meth:`open_session`
+ at the end of the request. This is still called during a request
+ context so if you absolutely need access to the request you can do
+ that.
+ """
+ raise NotImplementedError()
+
+
+class SecureCookieSessionInterface(SessionInterface):
+ """The cookie session interface that uses the Werkzeug securecookie
+ as client side session backend.
+ """
+ session_class = SecureCookieSession
+
+ def open_session(self, app, request):
+ key = app.secret_key
+ if key is not None:
+ return self.session_class.load_cookie(request,
+ app.session_cookie_name,
+ secret_key=key)
+
+ 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, path=path,
+ domain=domain)
+ else:
+ session.save_cookie(response, app.session_cookie_name, path=path,
+ expires=expires, httponly=httponly,
+ secure=secure, domain=domain)
diff --git a/websdk/flask/signals.py b/websdk/flask/signals.py
new file mode 100644
index 0000000..eeb763d
--- /dev/null
+++ b/websdk/flask/signals.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.signals
+ ~~~~~~~~~~~~~
+
+ Implements signals based on blinker if available, otherwise
+ falls silently back to a noop
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+signals_available = False
+try:
+ from blinker import Namespace
+ signals_available = True
+except ImportError:
+ class Namespace(object):
+ def signal(self, name, doc=None):
+ return _FakeSignal(name, doc)
+
+ class _FakeSignal(object):
+ """If blinker is unavailable, create a fake class with the same
+ interface that allows sending of signals but will fail with an
+ error on anything else. Instead of doing anything on send, it
+ will just ignore the arguments and do nothing instead.
+ """
+
+ def __init__(self, name, doc=None):
+ self.name = name
+ self.__doc__ = doc
+ def _fail(self, *args, **kwargs):
+ raise RuntimeError('signalling support is unavailable '
+ 'because the blinker library is '
+ 'not installed.')
+ send = lambda *a, **kw: None
+ connect = disconnect = has_receivers_for = receivers_for = \
+ temporarily_connected_to = connected_to = _fail
+ del _fail
+
+# the namespace for code signals. If you are not flask code, do
+# not put signals in here. Create your own namespace instead.
+_signals = Namespace()
+
+
+# core signals. For usage examples grep the sourcecode or consult
+# the API documentation in docs/api.rst as well as docs/signals.rst
+template_rendered = _signals.signal('template-rendered')
+request_started = _signals.signal('request-started')
+request_finished = _signals.signal('request-finished')
+request_tearing_down = _signals.signal('request-tearing-down')
+got_request_exception = _signals.signal('got-request-exception')
diff --git a/websdk/flask/templating.py b/websdk/flask/templating.py
new file mode 100644
index 0000000..90e8772
--- /dev/null
+++ b/websdk/flask/templating.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.templating
+ ~~~~~~~~~~~~~~~~
+
+ Implements the bridge to Jinja2.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import posixpath
+from jinja2 import BaseLoader, Environment as BaseEnvironment, \
+ TemplateNotFound
+
+from .globals import _request_ctx_stack
+from .signals import template_rendered
+from .module import blueprint_is_module
+
+
+def _default_template_ctx_processor():
+ """Default template context processor. Injects `request`,
+ `session` and `g`.
+ """
+ reqctx = _request_ctx_stack.top
+ return dict(
+ config=reqctx.app.config,
+ request=reqctx.request,
+ session=reqctx.session,
+ g=reqctx.g
+ )
+
+
+class Environment(BaseEnvironment):
+ """Works like a regular Jinja2 environment but has some additional
+ knowledge of how Flask's blueprint works so that it can prepend the
+ name of the blueprint to referenced templates if necessary.
+ """
+
+ def __init__(self, app, **options):
+ if 'loader' not in options:
+ options['loader'] = app.create_global_jinja_loader()
+ BaseEnvironment.__init__(self, **options)
+ self.app = app
+
+
+class DispatchingJinjaLoader(BaseLoader):
+ """A loader that looks for templates in the application and all
+ the blueprint folders.
+ """
+
+ def __init__(self, app):
+ self.app = app
+
+ def get_source(self, environment, template):
+ for loader, local_name in self._iter_loaders(template):
+ try:
+ return loader.get_source(environment, local_name)
+ except TemplateNotFound:
+ pass
+
+ raise TemplateNotFound(template)
+
+ def _iter_loaders(self, template):
+ loader = self.app.jinja_loader
+ if loader is not None:
+ yield loader, template
+
+ # old style module based loaders in case we are dealing with a
+ # blueprint that is an old style module
+ try:
+ module, local_name = posixpath.normpath(template).split('/', 1)
+ blueprint = self.app.blueprints[module]
+ if blueprint_is_module(blueprint):
+ loader = blueprint.jinja_loader
+ if loader is not None:
+ yield loader, local_name
+ except (ValueError, KeyError):
+ pass
+
+ for blueprint in self.app.blueprints.itervalues():
+ if blueprint_is_module(blueprint):
+ continue
+ loader = blueprint.jinja_loader
+ if loader is not None:
+ yield loader, template
+
+ def list_templates(self):
+ result = set()
+ loader = self.app.jinja_loader
+ if loader is not None:
+ result.update(loader.list_templates())
+
+ for name, blueprint in self.app.blueprints.iteritems():
+ loader = blueprint.jinja_loader
+ if loader is not None:
+ for template in loader.list_templates():
+ prefix = ''
+ if blueprint_is_module(blueprint):
+ prefix = name + '/'
+ result.add(prefix + template)
+
+ return list(result)
+
+
+def _render(template, context, app):
+ """Renders the template and fires the signal"""
+ rv = template.render(context)
+ template_rendered.send(app, template=template, context=context)
+ return rv
+
+
+def render_template(template_name, **context):
+ """Renders a template from the template folder with the given
+ context.
+
+ :param template_name: the name of the template to be rendered
+ :param context: the variables that should be available in the
+ context of the template.
+ """
+ ctx = _request_ctx_stack.top
+ ctx.app.update_template_context(context)
+ return _render(ctx.app.jinja_env.get_template(template_name),
+ context, ctx.app)
+
+
+def render_template_string(source, **context):
+ """Renders a template from the given template source string
+ with the given context.
+
+ :param template_name: the sourcecode of the template to be
+ rendered
+ :param context: the variables that should be available in the
+ context of the template.
+ """
+ ctx = _request_ctx_stack.top
+ ctx.app.update_template_context(context)
+ return _render(ctx.app.jinja_env.from_string(source),
+ context, ctx.app)
diff --git a/websdk/flask/testing.py b/websdk/flask/testing.py
new file mode 100644
index 0000000..782b40f
--- /dev/null
+++ b/websdk/flask/testing.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testing
+ ~~~~~~~~~~~~~
+
+ Implements test support helpers. This module is lazily imported
+ and usually not used in production environments.
+
+ :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. 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 = 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):
+ 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)
+
+ 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
+
+ # 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()
diff --git a/websdk/flask/testsuite/__init__.py b/websdk/flask/testsuite/__init__.py
new file mode 100644
index 0000000..76a4d72
--- /dev/null
+++ b/websdk/flask/testsuite/__init__.py
@@ -0,0 +1,221 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite
+ ~~~~~~~~~~~~~~~
+
+ Tests Flask itself. The majority of Flask is already tested
+ as part of Werkzeug.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import os
+import sys
+import flask
+import warnings
+import unittest
+from StringIO import StringIO
+from functools import update_wrapper
+from contextlib import contextmanager
+from werkzeug.utils import import_string, find_modules
+
+
+def add_to_path(path):
+ """Adds an entry to sys.path if it's not already there. This does
+ not append it but moves it to the front so that we can be sure it
+ is loaded.
+ """
+ if not os.path.isdir(path):
+ raise RuntimeError('Tried to add nonexisting path')
+
+ def _samefile(x, y):
+ try:
+ return os.path.samefile(x, y)
+ except (IOError, OSError):
+ return False
+ sys.path[:] = [x for x in sys.path if not _samefile(path, x)]
+ sys.path.insert(0, path)
+
+
+def iter_suites():
+ """Yields all testsuites."""
+ for module in find_modules(__name__):
+ mod = import_string(module)
+ if hasattr(mod, 'suite'):
+ yield mod.suite()
+
+
+def find_all_tests(suite):
+ """Yields all the tests and their names from a given suite."""
+ suites = [suite]
+ while suites:
+ s = suites.pop()
+ try:
+ suites.extend(s)
+ except TypeError:
+ yield s, '%s.%s.%s' % (
+ s.__class__.__module__,
+ s.__class__.__name__,
+ s._testMethodName
+ )
+
+
+@contextmanager
+def catch_warnings():
+ """Catch warnings in a with block in a list"""
+ # make sure deprecation warnings are active in tests
+ warnings.simplefilter('default', category=DeprecationWarning)
+
+ filters = warnings.filters
+ warnings.filters = filters[:]
+ old_showwarning = warnings.showwarning
+ log = []
+ def showwarning(message, category, filename, lineno, file=None, line=None):
+ log.append(locals())
+ try:
+ warnings.showwarning = showwarning
+ yield log
+ finally:
+ warnings.filters = filters
+ warnings.showwarning = old_showwarning
+
+
+@contextmanager
+def catch_stderr():
+ """Catch stderr in a StringIO"""
+ old_stderr = sys.stderr
+ sys.stderr = rv = StringIO()
+ try:
+ yield rv
+ finally:
+ sys.stderr = old_stderr
+
+
+def emits_module_deprecation_warning(f):
+ def new_f(self, *args, **kwargs):
+ with catch_warnings() as log:
+ f(self, *args, **kwargs)
+ self.assert_(log, 'expected deprecation warning')
+ for entry in log:
+ self.assert_('Modules are deprecated' in str(entry['message']))
+ return update_wrapper(new_f, f)
+
+
+class FlaskTestCase(unittest.TestCase):
+ """Baseclass for all the tests that Flask uses. Use these methods
+ for testing instead of the camelcased ones in the baseclass for
+ consistency.
+ """
+
+ def ensure_clean_request_context(self):
+ # make sure we're not leaking a request context since we are
+ # testing flask internally in debug mode in a few cases
+ self.assert_equal(flask._request_ctx_stack.top, None)
+
+ def setup(self):
+ pass
+
+ def teardown(self):
+ pass
+
+ def setUp(self):
+ self.setup()
+
+ def tearDown(self):
+ unittest.TestCase.tearDown(self)
+ self.ensure_clean_request_context()
+ self.teardown()
+
+ def assert_equal(self, x, y):
+ return self.assertEqual(x, y)
+
+ def assert_raises(self, exc_type, callable=None, *args, **kwargs):
+ catcher = _ExceptionCatcher(self, exc_type)
+ if callable is None:
+ return catcher
+ with catcher:
+ callable(*args, **kwargs)
+
+
+class _ExceptionCatcher(object):
+
+ def __init__(self, test_case, exc_type):
+ self.test_case = test_case
+ self.exc_type = exc_type
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, tb):
+ exception_name = self.exc_type.__name__
+ if exc_type is None:
+ self.test_case.fail('Expected exception of type %r' %
+ exception_name)
+ elif not issubclass(exc_type, self.exc_type):
+ raise exc_type, exc_value, tb
+ return True
+
+
+class BetterLoader(unittest.TestLoader):
+ """A nicer loader that solves two problems. First of all we are setting
+ up tests from different sources and we're doing this programmatically
+ which breaks the default loading logic so this is required anyways.
+ Secondly this loader has a nicer interpolation for test names than the
+ default one so you can just do ``run-tests.py ViewTestCase`` and it
+ will work.
+ """
+
+ def getRootSuite(self):
+ return suite()
+
+ def loadTestsFromName(self, name, module=None):
+ root = self.getRootSuite()
+ if name == 'suite':
+ return root
+
+ all_tests = []
+ for testcase, testname in find_all_tests(root):
+ if testname == name or \
+ testname.endswith('.' + name) or \
+ ('.' + name + '.') in testname or \
+ testname.startswith(name + '.'):
+ all_tests.append(testcase)
+
+ if not all_tests:
+ raise LookupError('could not find test case for "%s"' % name)
+
+ if len(all_tests) == 1:
+ return all_tests[0]
+ rv = unittest.TestSuite()
+ for test in all_tests:
+ rv.addTest(test)
+ return rv
+
+
+def setup_path():
+ add_to_path(os.path.abspath(os.path.join(
+ os.path.dirname(__file__), 'test_apps')))
+
+
+def suite():
+ """A testsuite that has all the Flask tests. You can use this
+ function to integrate the Flask tests into your own testsuite
+ in case you want to test that monkeypatches to Flask do not
+ break it.
+ """
+ setup_path()
+ suite = unittest.TestSuite()
+ for other_suite in iter_suites():
+ suite.addTest(other_suite)
+ return suite
+
+
+def main():
+ """Runs the testsuite as command line application."""
+ try:
+ unittest.main(testLoader=BetterLoader(), defaultTest='suite')
+ except Exception, e:
+ print 'Error: %s' % e
diff --git a/websdk/flask/testsuite/basic.py b/websdk/flask/testsuite/basic.py
new file mode 100644
index 0000000..1733f0a
--- /dev/null
+++ b/websdk/flask/testsuite/basic.py
@@ -0,0 +1,1051 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.basic
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ The basic functionality.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import re
+import flask
+import unittest
+from datetime import datetime
+from threading import Thread
+from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
+from werkzeug.exceptions import BadRequest, NotFound
+from werkzeug.http import parse_date
+
+
+class BasicFunctionalityTestCase(FlaskTestCase):
+
+ def test_options_work(self):
+ app = flask.Flask(__name__)
+ @app.route('/', methods=['GET', 'POST'])
+ def index():
+ return 'Hello World'
+ rv = app.test_client().open('/', method='OPTIONS')
+ self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST'])
+ self.assert_equal(rv.data, '')
+
+ def test_options_on_multiple_rules(self):
+ app = flask.Flask(__name__)
+ @app.route('/', methods=['GET', 'POST'])
+ def index():
+ return 'Hello World'
+ @app.route('/', methods=['PUT'])
+ def index_put():
+ return 'Aha!'
+ rv = app.test_client().open('/', method='OPTIONS')
+ self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'])
+
+ def test_options_handling_disabled(self):
+ app = flask.Flask(__name__)
+ def index():
+ return 'Hello World!'
+ index.provide_automatic_options = False
+ app.route('/')(index)
+ rv = app.test_client().open('/', method='OPTIONS')
+ self.assert_equal(rv.status_code, 405)
+
+ app = flask.Flask(__name__)
+ def index2():
+ return 'Hello World!'
+ index2.provide_automatic_options = True
+ app.route('/', methods=['OPTIONS'])(index2)
+ rv = app.test_client().open('/', method='OPTIONS')
+ self.assert_equal(sorted(rv.allow), ['OPTIONS'])
+
+ def test_request_dispatching(self):
+ app = flask.Flask(__name__)
+ @app.route('/')
+ def index():
+ return flask.request.method
+ @app.route('/more', methods=['GET', 'POST'])
+ def more():
+ return flask.request.method
+
+ c = app.test_client()
+ self.assert_equal(c.get('/').data, 'GET')
+ rv = c.post('/')
+ self.assert_equal(rv.status_code, 405)
+ self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS'])
+ rv = c.head('/')
+ self.assert_equal(rv.status_code, 200)
+ self.assert_(not rv.data) # head truncates
+ self.assert_equal(c.post('/more').data, 'POST')
+ self.assert_equal(c.get('/more').data, 'GET')
+ rv = c.delete('/more')
+ self.assert_equal(rv.status_code, 405)
+ self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST'])
+
+ def test_url_mapping(self):
+ app = flask.Flask(__name__)
+ def index():
+ return flask.request.method
+ def more():
+ return flask.request.method
+
+ app.add_url_rule('/', 'index', index)
+ app.add_url_rule('/more', 'more', more, methods=['GET', 'POST'])
+
+ c = app.test_client()
+ self.assert_equal(c.get('/').data, 'GET')
+ rv = c.post('/')
+ self.assert_equal(rv.status_code, 405)
+ self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS'])
+ rv = c.head('/')
+ self.assert_equal(rv.status_code, 200)
+ self.assert_(not rv.data) # head truncates
+ self.assert_equal(c.post('/more').data, 'POST')
+ self.assert_equal(c.get('/more').data, 'GET')
+ rv = c.delete('/more')
+ self.assert_equal(rv.status_code, 405)
+ self.assert_equal(sorted(rv.allow), ['GET', 'HEAD', 'OPTIONS', 'POST'])
+
+ def test_werkzeug_routing(self):
+ from werkzeug.routing import Submount, Rule
+ app = flask.Flask(__name__)
+ app.url_map.add(Submount('/foo', [
+ Rule('/bar', endpoint='bar'),
+ Rule('/', endpoint='index')
+ ]))
+ def bar():
+ return 'bar'
+ def index():
+ return 'index'
+ app.view_functions['bar'] = bar
+ app.view_functions['index'] = index
+
+ c = app.test_client()
+ self.assert_equal(c.get('/foo/').data, 'index')
+ self.assert_equal(c.get('/foo/bar').data, 'bar')
+
+ def test_endpoint_decorator(self):
+ from werkzeug.routing import Submount, Rule
+ app = flask.Flask(__name__)
+ app.url_map.add(Submount('/foo', [
+ Rule('/bar', endpoint='bar'),
+ Rule('/', endpoint='index')
+ ]))
+
+ @app.endpoint('bar')
+ def bar():
+ return 'bar'
+
+ @app.endpoint('index')
+ def index():
+ return 'index'
+
+ c = app.test_client()
+ self.assert_equal(c.get('/foo/').data, 'index')
+ self.assert_equal(c.get('/foo/bar').data, 'bar')
+
+ def test_session(self):
+ app = flask.Flask(__name__)
+ app.secret_key = 'testkey'
+ @app.route('/set', methods=['POST'])
+ def set():
+ flask.session['value'] = flask.request.form['value']
+ return 'value set'
+ @app.route('/get')
+ def get():
+ return flask.session['value']
+
+ c = app.test_client()
+ self.assert_equal(c.post('/set', data={'value': '42'}).data, 'value set')
+ self.assert_equal(c.get('/get').data, '42')
+
+ def test_session_using_server_name(self):
+ app = flask.Flask(__name__)
+ app.config.update(
+ SECRET_KEY='foo',
+ SERVER_NAME='example.com'
+ )
+ @app.route('/')
+ def index():
+ flask.session['testing'] = 42
+ return 'Hello World'
+ rv = app.test_client().get('/', 'http://example.com/')
+ self.assert_('domain=.example.com' in rv.headers['set-cookie'].lower())
+ self.assert_('httponly' in rv.headers['set-cookie'].lower())
+
+ def test_session_using_server_name_and_port(self):
+ app = flask.Flask(__name__)
+ app.config.update(
+ SECRET_KEY='foo',
+ SERVER_NAME='example.com:8080'
+ )
+ @app.route('/')
+ def index():
+ flask.session['testing'] = 42
+ return 'Hello World'
+ rv = app.test_client().get('/', 'http://example.com:8080/')
+ self.assert_('domain=.example.com' in rv.headers['set-cookie'].lower())
+ self.assert_('httponly' in rv.headers['set-cookie'].lower())
+
+ def test_session_using_application_root(self):
+ class PrefixPathMiddleware(object):
+ def __init__(self, app, prefix):
+ self.app = app
+ self.prefix = prefix
+ def __call__(self, environ, start_response):
+ environ['SCRIPT_NAME'] = self.prefix
+ return self.app(environ, start_response)
+
+ app = flask.Flask(__name__)
+ app.wsgi_app = PrefixPathMiddleware(app.wsgi_app, '/bar')
+ app.config.update(
+ SECRET_KEY='foo',
+ APPLICATION_ROOT='/bar'
+ )
+ @app.route('/')
+ def index():
+ flask.session['testing'] = 42
+ return 'Hello World'
+ rv = app.test_client().get('/', 'http://example.com:8080/')
+ self.assert_('path=/bar' in rv.headers['set-cookie'].lower())
+
+ def test_session_using_session_settings(self):
+ app = flask.Flask(__name__)
+ app.config.update(
+ SECRET_KEY='foo',
+ SERVER_NAME='www.example.com:8080',
+ APPLICATION_ROOT='/test',
+ SESSION_COOKIE_DOMAIN='.example.com',
+ SESSION_COOKIE_HTTPONLY=False,
+ SESSION_COOKIE_SECURE=True,
+ SESSION_COOKIE_PATH='/'
+ )
+ @app.route('/')
+ def index():
+ flask.session['testing'] = 42
+ return 'Hello World'
+ rv = app.test_client().get('/', 'http://www.example.com:8080/test/')
+ cookie = rv.headers['set-cookie'].lower()
+ self.assert_('domain=.example.com' in cookie)
+ self.assert_('path=/;' in cookie)
+ self.assert_('secure' in cookie)
+ self.assert_('httponly' not in cookie)
+
+ def test_missing_session(self):
+ app = flask.Flask(__name__)
+ def expect_exception(f, *args, **kwargs):
+ try:
+ f(*args, **kwargs)
+ except RuntimeError, e:
+ self.assert_(e.args and 'session is unavailable' in e.args[0])
+ else:
+ self.assert_(False, 'expected exception')
+ with app.test_request_context():
+ self.assert_(flask.session.get('missing_key') is None)
+ expect_exception(flask.session.__setitem__, 'foo', 42)
+ expect_exception(flask.session.pop, 'foo')
+
+ def test_session_expiration(self):
+ permanent = True
+ app = flask.Flask(__name__)
+ app.secret_key = 'testkey'
+ @app.route('/')
+ def index():
+ flask.session['test'] = 42
+ flask.session.permanent = permanent
+ return ''
+
+ @app.route('/test')
+ def test():
+ return unicode(flask.session.permanent)
+
+ client = app.test_client()
+ rv = client.get('/')
+ self.assert_('set-cookie' in rv.headers)
+ match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
+ expires = parse_date(match.group())
+ expected = datetime.utcnow() + app.permanent_session_lifetime
+ self.assert_equal(expires.year, expected.year)
+ self.assert_equal(expires.month, expected.month)
+ self.assert_equal(expires.day, expected.day)
+
+ rv = client.get('/test')
+ self.assert_equal(rv.data, 'True')
+
+ permanent = False
+ rv = app.test_client().get('/')
+ self.assert_('set-cookie' in rv.headers)
+ match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
+ self.assert_(match is None)
+
+ def test_flashes(self):
+ app = flask.Flask(__name__)
+ app.secret_key = 'testkey'
+
+ with app.test_request_context():
+ self.assert_(not flask.session.modified)
+ flask.flash('Zap')
+ flask.session.modified = False
+ flask.flash('Zip')
+ self.assert_(flask.session.modified)
+ self.assert_equal(list(flask.get_flashed_messages()), ['Zap', 'Zip'])
+
+ def test_extended_flashing(self):
+ app = flask.Flask(__name__)
+ app.secret_key = 'testkey'
+
+ @app.route('/')
+ def index():
+ flask.flash(u'Hello World')
+ flask.flash(u'Hello World', 'error')
+ flask.flash(flask.Markup(u'<em>Testing</em>'), 'warning')
+ return ''
+
+ @app.route('/test')
+ def test():
+ messages = flask.get_flashed_messages(with_categories=True)
+ self.assert_equal(len(messages), 3)
+ self.assert_equal(messages[0], ('message', u'Hello World'))
+ self.assert_equal(messages[1], ('error', u'Hello World'))
+ self.assert_equal(messages[2], ('warning', flask.Markup(u'<em>Testing</em>')))
+ return ''
+ messages = flask.get_flashed_messages()
+ self.assert_equal(len(messages), 3)
+ self.assert_equal(messages[0], u'Hello World')
+ self.assert_equal(messages[1], u'Hello World')
+ self.assert_equal(messages[2], flask.Markup(u'<em>Testing</em>'))
+
+ c = app.test_client()
+ c.get('/')
+ c.get('/test')
+
+ def test_request_processing(self):
+ app = flask.Flask(__name__)
+ evts = []
+ @app.before_request
+ def before_request():
+ evts.append('before')
+ @app.after_request
+ def after_request(response):
+ response.data += '|after'
+ evts.append('after')
+ return response
+ @app.route('/')
+ def index():
+ self.assert_('before' in evts)
+ self.assert_('after' not in evts)
+ return 'request'
+ self.assert_('after' not in evts)
+ rv = app.test_client().get('/').data
+ self.assert_('after' in evts)
+ self.assert_equal(rv, 'request|after')
+
+ def test_teardown_request_handler(self):
+ called = []
+ app = flask.Flask(__name__)
+ @app.teardown_request
+ def teardown_request(exc):
+ called.append(True)
+ return "Ignored"
+ @app.route('/')
+ def root():
+ return "Response"
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.status_code, 200)
+ self.assert_('Response' in rv.data)
+ self.assert_equal(len(called), 1)
+
+ def test_teardown_request_handler_debug_mode(self):
+ called = []
+ app = flask.Flask(__name__)
+ app.testing = True
+ @app.teardown_request
+ def teardown_request(exc):
+ called.append(True)
+ return "Ignored"
+ @app.route('/')
+ def root():
+ return "Response"
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.status_code, 200)
+ self.assert_('Response' in rv.data)
+ self.assert_equal(len(called), 1)
+
+ def test_teardown_request_handler_error(self):
+ called = []
+ app = flask.Flask(__name__)
+ @app.teardown_request
+ def teardown_request1(exc):
+ self.assert_equal(type(exc), ZeroDivisionError)
+ called.append(True)
+ # This raises a new error and blows away sys.exc_info(), so we can
+ # test that all teardown_requests get passed the same original
+ # exception.
+ try:
+ raise TypeError
+ except:
+ pass
+ @app.teardown_request
+ def teardown_request2(exc):
+ self.assert_equal(type(exc), ZeroDivisionError)
+ called.append(True)
+ # This raises a new error and blows away sys.exc_info(), so we can
+ # test that all teardown_requests get passed the same original
+ # exception.
+ try:
+ raise TypeError
+ except:
+ pass
+ @app.route('/')
+ def fails():
+ 1/0
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.status_code, 500)
+ self.assert_('Internal Server Error' in rv.data)
+ self.assert_equal(len(called), 2)
+
+ def test_before_after_request_order(self):
+ called = []
+ app = flask.Flask(__name__)
+ @app.before_request
+ def before1():
+ called.append(1)
+ @app.before_request
+ def before2():
+ called.append(2)
+ @app.after_request
+ def after1(response):
+ called.append(4)
+ return response
+ @app.after_request
+ def after2(response):
+ called.append(3)
+ return response
+ @app.teardown_request
+ def finish1(exc):
+ called.append(6)
+ @app.teardown_request
+ def finish2(exc):
+ called.append(5)
+ @app.route('/')
+ def index():
+ return '42'
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.data, '42')
+ self.assert_equal(called, [1, 2, 3, 4, 5, 6])
+
+ def test_error_handling(self):
+ app = flask.Flask(__name__)
+ @app.errorhandler(404)
+ def not_found(e):
+ return 'not found', 404
+ @app.errorhandler(500)
+ def internal_server_error(e):
+ return 'internal server error', 500
+ @app.route('/')
+ def index():
+ flask.abort(404)
+ @app.route('/error')
+ def error():
+ 1 // 0
+ c = app.test_client()
+ rv = c.get('/')
+ self.assert_equal(rv.status_code, 404)
+ self.assert_equal(rv.data, 'not found')
+ rv = c.get('/error')
+ self.assert_equal(rv.status_code, 500)
+ self.assert_equal('internal server error', rv.data)
+
+ def test_before_request_and_routing_errors(self):
+ app = flask.Flask(__name__)
+ @app.before_request
+ def attach_something():
+ flask.g.something = 'value'
+ @app.errorhandler(404)
+ def return_something(error):
+ return flask.g.something, 404
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.status_code, 404)
+ self.assert_equal(rv.data, 'value')
+
+ def test_user_error_handling(self):
+ class MyException(Exception):
+ pass
+
+ app = flask.Flask(__name__)
+ @app.errorhandler(MyException)
+ def handle_my_exception(e):
+ self.assert_(isinstance(e, MyException))
+ return '42'
+ @app.route('/')
+ def index():
+ raise MyException()
+
+ c = app.test_client()
+ self.assert_equal(c.get('/').data, '42')
+
+ def test_trapping_of_bad_request_key_errors(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ @app.route('/fail')
+ def fail():
+ flask.request.form['missing_key']
+ c = app.test_client()
+ self.assert_equal(c.get('/fail').status_code, 400)
+
+ app.config['TRAP_BAD_REQUEST_ERRORS'] = True
+ c = app.test_client()
+ try:
+ c.get('/fail')
+ except KeyError, e:
+ self.assert_(isinstance(e, BadRequest))
+ else:
+ self.fail('Expected exception')
+
+ def test_trapping_of_all_http_exceptions(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ app.config['TRAP_HTTP_EXCEPTIONS'] = True
+ @app.route('/fail')
+ def fail():
+ flask.abort(404)
+
+ c = app.test_client()
+ try:
+ c.get('/fail')
+ except NotFound, e:
+ pass
+ else:
+ self.fail('Expected exception')
+
+ def test_enctype_debug_helper(self):
+ from flask.debughelpers import DebugFilesKeyError
+ app = flask.Flask(__name__)
+ app.debug = True
+ @app.route('/fail', methods=['POST'])
+ def index():
+ return flask.request.files['foo'].filename
+
+ # with statement is important because we leave an exception on the
+ # stack otherwise and we want to ensure that this is not the case
+ # to not negatively affect other tests.
+ with app.test_client() as c:
+ try:
+ c.post('/fail', data={'foo': 'index.txt'})
+ except DebugFilesKeyError, e:
+ self.assert_('no file contents were transmitted' in str(e))
+ self.assert_('This was submitted: "index.txt"' in str(e))
+ else:
+ self.fail('Expected exception')
+
+ def test_teardown_on_pop(self):
+ buffer = []
+ app = flask.Flask(__name__)
+ @app.teardown_request
+ def end_of_request(exception):
+ buffer.append(exception)
+
+ ctx = app.test_request_context()
+ ctx.push()
+ self.assert_equal(buffer, [])
+ ctx.pop()
+ self.assert_equal(buffer, [None])
+
+ def test_response_creation(self):
+ app = flask.Flask(__name__)
+ @app.route('/unicode')
+ def from_unicode():
+ return u'Hällo Wörld'
+ @app.route('/string')
+ def from_string():
+ return u'Hällo Wörld'.encode('utf-8')
+ @app.route('/args')
+ def from_tuple():
+ return 'Meh', 400, {'X-Foo': 'Testing'}, 'text/plain'
+ c = app.test_client()
+ self.assert_equal(c.get('/unicode').data, u'Hällo Wörld'.encode('utf-8'))
+ self.assert_equal(c.get('/string').data, u'Hällo Wörld'.encode('utf-8'))
+ rv = c.get('/args')
+ self.assert_equal(rv.data, 'Meh')
+ self.assert_equal(rv.headers['X-Foo'], 'Testing')
+ self.assert_equal(rv.status_code, 400)
+ self.assert_equal(rv.mimetype, 'text/plain')
+
+ def test_make_response(self):
+ app = flask.Flask(__name__)
+ with app.test_request_context():
+ rv = flask.make_response()
+ self.assert_equal(rv.status_code, 200)
+ self.assert_equal(rv.data, '')
+ self.assert_equal(rv.mimetype, 'text/html')
+
+ rv = flask.make_response('Awesome')
+ self.assert_equal(rv.status_code, 200)
+ self.assert_equal(rv.data, 'Awesome')
+ self.assert_equal(rv.mimetype, 'text/html')
+
+ rv = flask.make_response('W00t', 404)
+ self.assert_equal(rv.status_code, 404)
+ self.assert_equal(rv.data, 'W00t')
+ self.assert_equal(rv.mimetype, 'text/html')
+
+ def test_url_generation(self):
+ app = flask.Flask(__name__)
+ @app.route('/hello/<name>', methods=['POST'])
+ def hello():
+ pass
+ with app.test_request_context():
+ self.assert_equal(flask.url_for('hello', name='test x'), '/hello/test%20x')
+ self.assert_equal(flask.url_for('hello', name='test x', _external=True),
+ 'http://localhost/hello/test%20x')
+
+ def test_custom_converters(self):
+ from werkzeug.routing import BaseConverter
+ class ListConverter(BaseConverter):
+ def to_python(self, value):
+ return value.split(',')
+ def to_url(self, value):
+ base_to_url = super(ListConverter, self).to_url
+ return ','.join(base_to_url(x) for x in value)
+ app = flask.Flask(__name__)
+ app.url_map.converters['list'] = ListConverter
+ @app.route('/<list:args>')
+ def index(args):
+ return '|'.join(args)
+ c = app.test_client()
+ self.assert_equal(c.get('/1,2,3').data, '1|2|3')
+
+ def test_static_files(self):
+ app = flask.Flask(__name__)
+ rv = app.test_client().get('/static/index.html')
+ self.assert_equal(rv.status_code, 200)
+ self.assert_equal(rv.data.strip(), '<h1>Hello World!</h1>')
+ with app.test_request_context():
+ self.assert_equal(flask.url_for('static', filename='index.html'),
+ '/static/index.html')
+
+ def test_none_response(self):
+ app = flask.Flask(__name__)
+ @app.route('/')
+ def test():
+ return None
+ try:
+ app.test_client().get('/')
+ except ValueError, e:
+ self.assert_equal(str(e), 'View function did not return a response')
+ pass
+ else:
+ self.assert_("Expected ValueError")
+
+ def test_request_locals(self):
+ self.assert_equal(repr(flask.g), '<LocalProxy unbound>')
+ self.assertFalse(flask.g)
+
+ def test_proper_test_request_context(self):
+ app = flask.Flask(__name__)
+ app.config.update(
+ SERVER_NAME='localhost.localdomain:5000'
+ )
+
+ @app.route('/')
+ def index():
+ return None
+
+ @app.route('/', subdomain='foo')
+ def sub():
+ return None
+
+ with app.test_request_context('/'):
+ self.assert_equal(flask.url_for('index', _external=True), 'http://localhost.localdomain:5000/')
+
+ with app.test_request_context('/'):
+ self.assert_equal(flask.url_for('sub', _external=True), 'http://foo.localhost.localdomain:5000/')
+
+ try:
+ with app.test_request_context('/', environ_overrides={'HTTP_HOST': 'localhost'}):
+ pass
+ except Exception, e:
+ self.assert_(isinstance(e, ValueError))
+ self.assert_equal(str(e), "the server name provided " +
+ "('localhost.localdomain:5000') does not match the " + \
+ "server name from the WSGI environment ('localhost')")
+
+ try:
+ app.config.update(SERVER_NAME='localhost')
+ with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost'}):
+ pass
+ except ValueError, e:
+ raise ValueError(
+ "No ValueError exception should have been raised \"%s\"" % e
+ )
+
+ try:
+ app.config.update(SERVER_NAME='localhost:80')
+ with app.test_request_context('/', environ_overrides={'SERVER_NAME': 'localhost:80'}):
+ pass
+ except ValueError, e:
+ raise ValueError(
+ "No ValueError exception should have been raised \"%s\"" % e
+ )
+
+ def test_test_app_proper_environ(self):
+ app = flask.Flask(__name__)
+ app.config.update(
+ SERVER_NAME='localhost.localdomain:5000'
+ )
+ @app.route('/')
+ def index():
+ return 'Foo'
+
+ @app.route('/', subdomain='foo')
+ def subdomain():
+ return 'Foo SubDomain'
+
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.data, 'Foo')
+
+ rv = app.test_client().get('/', 'http://localhost.localdomain:5000')
+ self.assert_equal(rv.data, 'Foo')
+
+ rv = app.test_client().get('/', 'https://localhost.localdomain:5000')
+ self.assert_equal(rv.data, 'Foo')
+
+ app.config.update(SERVER_NAME='localhost.localdomain')
+ rv = app.test_client().get('/', 'https://localhost.localdomain')
+ self.assert_equal(rv.data, 'Foo')
+
+ try:
+ app.config.update(SERVER_NAME='localhost.localdomain:443')
+ rv = app.test_client().get('/', 'https://localhost.localdomain')
+ # Werkzeug 0.8
+ self.assert_equal(rv.status_code, 404)
+ except ValueError, e:
+ # Werkzeug 0.7
+ self.assert_equal(str(e), "the server name provided " +
+ "('localhost.localdomain:443') does not match the " + \
+ "server name from the WSGI environment ('localhost.localdomain')")
+
+ try:
+ app.config.update(SERVER_NAME='localhost.localdomain')
+ rv = app.test_client().get('/', 'http://foo.localhost')
+ # Werkzeug 0.8
+ self.assert_equal(rv.status_code, 404)
+ except ValueError, e:
+ # Werkzeug 0.7
+ self.assert_equal(str(e), "the server name provided " + \
+ "('localhost.localdomain') does not match the " + \
+ "server name from the WSGI environment ('foo.localhost')")
+
+ rv = app.test_client().get('/', 'http://foo.localhost.localdomain')
+ self.assert_equal(rv.data, 'Foo SubDomain')
+
+ def test_exception_propagation(self):
+ def apprunner(configkey):
+ app = flask.Flask(__name__)
+ @app.route('/')
+ def index():
+ 1/0
+ c = app.test_client()
+ if config_key is not None:
+ app.config[config_key] = True
+ try:
+ resp = c.get('/')
+ except Exception:
+ pass
+ else:
+ self.fail('expected exception')
+ else:
+ self.assert_equal(c.get('/').status_code, 500)
+
+ # we have to run this test in an isolated thread because if the
+ # debug flag is set to true and an exception happens the context is
+ # not torn down. This causes other tests that run after this fail
+ # when they expect no exception on the stack.
+ for config_key in 'TESTING', 'PROPAGATE_EXCEPTIONS', 'DEBUG', None:
+ t = Thread(target=apprunner, args=(config_key,))
+ t.start()
+ t.join()
+
+ def test_max_content_length(self):
+ app = flask.Flask(__name__)
+ app.config['MAX_CONTENT_LENGTH'] = 64
+ @app.before_request
+ def always_first():
+ flask.request.form['myfile']
+ self.assert_(False)
+ @app.route('/accept', methods=['POST'])
+ def accept_file():
+ flask.request.form['myfile']
+ self.assert_(False)
+ @app.errorhandler(413)
+ def catcher(error):
+ return '42'
+
+ c = app.test_client()
+ rv = c.post('/accept', data={'myfile': 'foo' * 100})
+ self.assert_equal(rv.data, '42')
+
+ def test_url_processors(self):
+ app = flask.Flask(__name__)
+
+ @app.url_defaults
+ def add_language_code(endpoint, values):
+ if flask.g.lang_code is not None and \
+ app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
+ values.setdefault('lang_code', flask.g.lang_code)
+
+ @app.url_value_preprocessor
+ def pull_lang_code(endpoint, values):
+ flask.g.lang_code = values.pop('lang_code', None)
+
+ @app.route('/<lang_code>/')
+ def index():
+ return flask.url_for('about')
+
+ @app.route('/<lang_code>/about')
+ def about():
+ return flask.url_for('something_else')
+
+ @app.route('/foo')
+ def something_else():
+ return flask.url_for('about', lang_code='en')
+
+ c = app.test_client()
+
+ self.assert_equal(c.get('/de/').data, '/de/about')
+ self.assert_equal(c.get('/de/about').data, '/foo')
+ self.assert_equal(c.get('/foo').data, '/en/about')
+
+ def test_debug_mode_complains_after_first_request(self):
+ app = flask.Flask(__name__)
+ app.debug = True
+ @app.route('/')
+ def index():
+ return 'Awesome'
+ self.assert_(not app.got_first_request)
+ self.assert_equal(app.test_client().get('/').data, 'Awesome')
+ try:
+ @app.route('/foo')
+ def broken():
+ return 'Meh'
+ except AssertionError, e:
+ self.assert_('A setup function was called' in str(e))
+ else:
+ self.fail('Expected exception')
+
+ app.debug = False
+ @app.route('/foo')
+ def working():
+ return 'Meh'
+ self.assert_equal(app.test_client().get('/foo').data, 'Meh')
+ self.assert_(app.got_first_request)
+
+ def test_before_first_request_functions(self):
+ got = []
+ app = flask.Flask(__name__)
+ @app.before_first_request
+ def foo():
+ got.append(42)
+ c = app.test_client()
+ c.get('/')
+ self.assert_equal(got, [42])
+ c.get('/')
+ self.assert_equal(got, [42])
+ self.assert_(app.got_first_request)
+
+ def test_routing_redirect_debugging(self):
+ app = flask.Flask(__name__)
+ app.debug = True
+ @app.route('/foo/', methods=['GET', 'POST'])
+ def foo():
+ return 'success'
+ with app.test_client() as c:
+ try:
+ c.post('/foo', data={})
+ except AssertionError, e:
+ self.assert_('http://localhost/foo/' in str(e))
+ self.assert_('Make sure to directly send your POST-request '
+ 'to this URL' in str(e))
+ else:
+ self.fail('Expected exception')
+
+ rv = c.get('/foo', data={}, follow_redirects=True)
+ self.assert_equal(rv.data, 'success')
+
+ app.debug = False
+ with app.test_client() as c:
+ rv = c.post('/foo', data={}, follow_redirects=True)
+ self.assert_equal(rv.data, 'success')
+
+ def test_route_decorator_custom_endpoint(self):
+ app = flask.Flask(__name__)
+ app.debug = True
+
+ @app.route('/foo/')
+ def foo():
+ return flask.request.endpoint
+
+ @app.route('/bar/', endpoint='bar')
+ def for_bar():
+ return flask.request.endpoint
+
+ @app.route('/bar/123', endpoint='123')
+ def for_bar_foo():
+ return flask.request.endpoint
+
+ with app.test_request_context():
+ assert flask.url_for('foo') == '/foo/'
+ assert flask.url_for('bar') == '/bar/'
+ assert flask.url_for('123') == '/bar/123'
+
+ c = app.test_client()
+ self.assertEqual(c.get('/foo/').data, 'foo')
+ self.assertEqual(c.get('/bar/').data, 'bar')
+ self.assertEqual(c.get('/bar/123').data, '123')
+
+ def test_preserve_only_once(self):
+ app = flask.Flask(__name__)
+ app.debug = True
+
+ @app.route('/fail')
+ def fail_func():
+ 1/0
+
+ c = app.test_client()
+ for x in xrange(3):
+ with self.assert_raises(ZeroDivisionError):
+ c.get('/fail')
+
+ self.assert_(flask._request_ctx_stack.top is not None)
+ flask._request_ctx_stack.pop()
+ self.assert_(flask._request_ctx_stack.top is None)
+
+
+class ContextTestCase(FlaskTestCase):
+
+ def test_context_binding(self):
+ app = flask.Flask(__name__)
+ @app.route('/')
+ def index():
+ return 'Hello %s!' % flask.request.args['name']
+ @app.route('/meh')
+ def meh():
+ return flask.request.url
+
+ with app.test_request_context('/?name=World'):
+ self.assert_equal(index(), 'Hello World!')
+ with app.test_request_context('/meh'):
+ self.assert_equal(meh(), 'http://localhost/meh')
+ self.assert_(flask._request_ctx_stack.top is None)
+
+ def test_context_test(self):
+ app = flask.Flask(__name__)
+ self.assert_(not flask.request)
+ self.assert_(not flask.has_request_context())
+ ctx = app.test_request_context()
+ ctx.push()
+ try:
+ self.assert_(flask.request)
+ self.assert_(flask.has_request_context())
+ finally:
+ ctx.pop()
+
+ def test_manual_context_binding(self):
+ app = flask.Flask(__name__)
+ @app.route('/')
+ def index():
+ return 'Hello %s!' % flask.request.args['name']
+
+ ctx = app.test_request_context('/?name=World')
+ ctx.push()
+ self.assert_equal(index(), 'Hello World!')
+ ctx.pop()
+ try:
+ index()
+ except RuntimeError:
+ pass
+ else:
+ self.assert_(0, 'expected runtime error')
+
+
+class SubdomainTestCase(FlaskTestCase):
+
+ def test_basic_support(self):
+ app = flask.Flask(__name__)
+ app.config['SERVER_NAME'] = 'localhost'
+ @app.route('/')
+ def normal_index():
+ return 'normal index'
+ @app.route('/', subdomain='test')
+ def test_index():
+ return 'test index'
+
+ c = app.test_client()
+ rv = c.get('/', 'http://localhost/')
+ self.assert_equal(rv.data, 'normal index')
+
+ rv = c.get('/', 'http://test.localhost/')
+ self.assert_equal(rv.data, 'test index')
+
+ @emits_module_deprecation_warning
+ def test_module_static_path_subdomain(self):
+ app = flask.Flask(__name__)
+ app.config['SERVER_NAME'] = 'example.com'
+ from subdomaintestmodule import mod
+ app.register_module(mod)
+ c = app.test_client()
+ rv = c.get('/static/hello.txt', 'http://foo.example.com/')
+ self.assert_equal(rv.data.strip(), 'Hello Subdomain')
+
+ def test_subdomain_matching(self):
+ app = flask.Flask(__name__)
+ app.config['SERVER_NAME'] = 'localhost'
+ @app.route('/', subdomain='<user>')
+ def index(user):
+ return 'index for %s' % user
+
+ c = app.test_client()
+ rv = c.get('/', 'http://mitsuhiko.localhost/')
+ self.assert_equal(rv.data, 'index for mitsuhiko')
+
+ def test_subdomain_matching_with_ports(self):
+ app = flask.Flask(__name__)
+ app.config['SERVER_NAME'] = 'localhost:3000'
+ @app.route('/', subdomain='<user>')
+ def index(user):
+ return 'index for %s' % user
+
+ c = app.test_client()
+ rv = c.get('/', 'http://mitsuhiko.localhost:3000/')
+ self.assert_equal(rv.data, 'index for mitsuhiko')
+
+ @emits_module_deprecation_warning
+ def test_module_subdomain_support(self):
+ app = flask.Flask(__name__)
+ mod = flask.Module(__name__, 'test', subdomain='testing')
+ app.config['SERVER_NAME'] = 'localhost'
+
+ @mod.route('/test')
+ def test():
+ return 'Test'
+
+ @mod.route('/outside', subdomain='xtesting')
+ def bar():
+ return 'Outside'
+
+ app.register_module(mod)
+
+ c = app.test_client()
+ rv = c.get('/test', 'http://testing.localhost/')
+ self.assert_equal(rv.data, 'Test')
+ rv = c.get('/outside', 'http://xtesting.localhost/')
+ self.assert_equal(rv.data, 'Outside')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase))
+ suite.addTest(unittest.makeSuite(ContextTestCase))
+ suite.addTest(unittest.makeSuite(SubdomainTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/blueprints.py b/websdk/flask/testsuite/blueprints.py
new file mode 100644
index 0000000..3f65dd4
--- /dev/null
+++ b/websdk/flask/testsuite/blueprints.py
@@ -0,0 +1,512 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.blueprints
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Blueprints (and currently modules)
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import flask
+import unittest
+import warnings
+from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
+from werkzeug.exceptions import NotFound
+from jinja2 import TemplateNotFound
+
+
+# import moduleapp here because it uses deprecated features and we don't
+# want to see the warnings
+warnings.simplefilter('ignore', DeprecationWarning)
+from moduleapp import app as moduleapp
+warnings.simplefilter('default', DeprecationWarning)
+
+
+class ModuleTestCase(FlaskTestCase):
+
+ @emits_module_deprecation_warning
+ def test_basic_module(self):
+ app = flask.Flask(__name__)
+ admin = flask.Module(__name__, 'admin', url_prefix='/admin')
+ @admin.route('/')
+ def admin_index():
+ return 'admin index'
+ @admin.route('/login')
+ def admin_login():
+ return 'admin login'
+ @admin.route('/logout')
+ def admin_logout():
+ return 'admin logout'
+ @app.route('/')
+ def index():
+ return 'the index'
+ app.register_module(admin)
+ c = app.test_client()
+ self.assert_equal(c.get('/').data, 'the index')
+ self.assert_equal(c.get('/admin/').data, 'admin index')
+ self.assert_equal(c.get('/admin/login').data, 'admin login')
+ self.assert_equal(c.get('/admin/logout').data, 'admin logout')
+
+ @emits_module_deprecation_warning
+ def test_default_endpoint_name(self):
+ app = flask.Flask(__name__)
+ mod = flask.Module(__name__, 'frontend')
+ def index():
+ return 'Awesome'
+ mod.add_url_rule('/', view_func=index)
+ app.register_module(mod)
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.data, 'Awesome')
+ with app.test_request_context():
+ self.assert_equal(flask.url_for('frontend.index'), '/')
+
+ @emits_module_deprecation_warning
+ def test_request_processing(self):
+ catched = []
+ app = flask.Flask(__name__)
+ admin = flask.Module(__name__, 'admin', url_prefix='/admin')
+ @admin.before_request
+ def before_admin_request():
+ catched.append('before-admin')
+ @admin.after_request
+ def after_admin_request(response):
+ catched.append('after-admin')
+ return response
+ @admin.route('/')
+ def admin_index():
+ return 'the admin'
+ @app.before_request
+ def before_request():
+ catched.append('before-app')
+ @app.after_request
+ def after_request(response):
+ catched.append('after-app')
+ return response
+ @app.route('/')
+ def index():
+ return 'the index'
+ app.register_module(admin)
+ c = app.test_client()
+
+ self.assert_equal(c.get('/').data, 'the index')
+ self.assert_equal(catched, ['before-app', 'after-app'])
+ del catched[:]
+
+ self.assert_equal(c.get('/admin/').data, 'the admin')
+ self.assert_equal(catched, ['before-app', 'before-admin',
+ 'after-admin', 'after-app'])
+
+ @emits_module_deprecation_warning
+ def test_context_processors(self):
+ app = flask.Flask(__name__)
+ admin = flask.Module(__name__, 'admin', url_prefix='/admin')
+ @app.context_processor
+ def inject_all_regualr():
+ return {'a': 1}
+ @admin.context_processor
+ def inject_admin():
+ return {'b': 2}
+ @admin.app_context_processor
+ def inject_all_module():
+ return {'c': 3}
+ @app.route('/')
+ def index():
+ return flask.render_template_string('{{ a }}{{ b }}{{ c }}')
+ @admin.route('/')
+ def admin_index():
+ return flask.render_template_string('{{ a }}{{ b }}{{ c }}')
+ app.register_module(admin)
+ c = app.test_client()
+ self.assert_equal(c.get('/').data, '13')
+ self.assert_equal(c.get('/admin/').data, '123')
+
+ @emits_module_deprecation_warning
+ def test_late_binding(self):
+ app = flask.Flask(__name__)
+ admin = flask.Module(__name__, 'admin')
+ @admin.route('/')
+ def index():
+ return '42'
+ app.register_module(admin, url_prefix='/admin')
+ self.assert_equal(app.test_client().get('/admin/').data, '42')
+
+ @emits_module_deprecation_warning
+ def test_error_handling(self):
+ app = flask.Flask(__name__)
+ admin = flask.Module(__name__, 'admin')
+ @admin.app_errorhandler(404)
+ def not_found(e):
+ return 'not found', 404
+ @admin.app_errorhandler(500)
+ def internal_server_error(e):
+ return 'internal server error', 500
+ @admin.route('/')
+ def index():
+ flask.abort(404)
+ @admin.route('/error')
+ def error():
+ 1 // 0
+ app.register_module(admin)
+ c = app.test_client()
+ rv = c.get('/')
+ self.assert_equal(rv.status_code, 404)
+ self.assert_equal(rv.data, 'not found')
+ rv = c.get('/error')
+ self.assert_equal(rv.status_code, 500)
+ self.assert_equal('internal server error', rv.data)
+
+ def test_templates_and_static(self):
+ app = moduleapp
+ app.testing = True
+ c = app.test_client()
+
+ rv = c.get('/')
+ self.assert_equal(rv.data, 'Hello from the Frontend')
+ rv = c.get('/admin/')
+ self.assert_equal(rv.data, 'Hello from the Admin')
+ rv = c.get('/admin/index2')
+ self.assert_equal(rv.data, 'Hello from the Admin')
+ rv = c.get('/admin/static/test.txt')
+ self.assert_equal(rv.data.strip(), 'Admin File')
+ rv = c.get('/admin/static/css/test.css')
+ self.assert_equal(rv.data.strip(), '/* nested file */')
+
+ with app.test_request_context():
+ self.assert_equal(flask.url_for('admin.static', filename='test.txt'),
+ '/admin/static/test.txt')
+
+ with app.test_request_context():
+ try:
+ flask.render_template('missing.html')
+ except TemplateNotFound, e:
+ self.assert_equal(e.name, 'missing.html')
+ else:
+ self.assert_(0, 'expected exception')
+
+ with flask.Flask(__name__).test_request_context():
+ self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested')
+
+ def test_safe_access(self):
+ app = moduleapp
+
+ with app.test_request_context():
+ f = app.view_functions['admin.static']
+
+ try:
+ f('/etc/passwd')
+ except NotFound:
+ pass
+ else:
+ self.assert_(0, 'expected exception')
+ try:
+ f('../__init__.py')
+ except NotFound:
+ pass
+ else:
+ self.assert_(0, 'expected exception')
+
+ # testcase for a security issue that may exist on windows systems
+ import os
+ import ntpath
+ old_path = os.path
+ os.path = ntpath
+ try:
+ try:
+ f('..\\__init__.py')
+ except NotFound:
+ pass
+ else:
+ self.assert_(0, 'expected exception')
+ finally:
+ os.path = old_path
+
+ @emits_module_deprecation_warning
+ def test_endpoint_decorator(self):
+ from werkzeug.routing import Submount, Rule
+ from flask import Module
+
+ app = flask.Flask(__name__)
+ app.testing = True
+ app.url_map.add(Submount('/foo', [
+ Rule('/bar', endpoint='bar'),
+ Rule('/', endpoint='index')
+ ]))
+ module = Module(__name__, __name__)
+
+ @module.endpoint('bar')
+ def bar():
+ return 'bar'
+
+ @module.endpoint('index')
+ def index():
+ return 'index'
+
+ app.register_module(module)
+
+ c = app.test_client()
+ self.assert_equal(c.get('/foo/').data, 'index')
+ self.assert_equal(c.get('/foo/bar').data, 'bar')
+
+
+class BlueprintTestCase(FlaskTestCase):
+
+ def test_blueprint_specific_error_handling(self):
+ frontend = flask.Blueprint('frontend', __name__)
+ backend = flask.Blueprint('backend', __name__)
+ sideend = flask.Blueprint('sideend', __name__)
+
+ @frontend.errorhandler(403)
+ def frontend_forbidden(e):
+ return 'frontend says no', 403
+
+ @frontend.route('/frontend-no')
+ def frontend_no():
+ flask.abort(403)
+
+ @backend.errorhandler(403)
+ def backend_forbidden(e):
+ return 'backend says no', 403
+
+ @backend.route('/backend-no')
+ def backend_no():
+ flask.abort(403)
+
+ @sideend.route('/what-is-a-sideend')
+ def sideend_no():
+ flask.abort(403)
+
+ app = flask.Flask(__name__)
+ app.register_blueprint(frontend)
+ app.register_blueprint(backend)
+ app.register_blueprint(sideend)
+
+ @app.errorhandler(403)
+ def app_forbidden(e):
+ return 'application itself says no', 403
+
+ c = app.test_client()
+
+ self.assert_equal(c.get('/frontend-no').data, 'frontend says no')
+ self.assert_equal(c.get('/backend-no').data, 'backend says no')
+ self.assert_equal(c.get('/what-is-a-sideend').data, 'application itself says no')
+
+ def test_blueprint_url_definitions(self):
+ bp = flask.Blueprint('test', __name__)
+
+ @bp.route('/foo', defaults={'baz': 42})
+ def foo(bar, baz):
+ return '%s/%d' % (bar, baz)
+
+ @bp.route('/bar')
+ def bar(bar):
+ return unicode(bar)
+
+ app = flask.Flask(__name__)
+ app.register_blueprint(bp, url_prefix='/1', url_defaults={'bar': 23})
+ app.register_blueprint(bp, url_prefix='/2', url_defaults={'bar': 19})
+
+ c = app.test_client()
+ self.assert_equal(c.get('/1/foo').data, u'23/42')
+ self.assert_equal(c.get('/2/foo').data, u'19/42')
+ self.assert_equal(c.get('/1/bar').data, u'23')
+ self.assert_equal(c.get('/2/bar').data, u'19')
+
+ def test_blueprint_url_processors(self):
+ bp = flask.Blueprint('frontend', __name__, url_prefix='/<lang_code>')
+
+ @bp.url_defaults
+ def add_language_code(endpoint, values):
+ values.setdefault('lang_code', flask.g.lang_code)
+
+ @bp.url_value_preprocessor
+ def pull_lang_code(endpoint, values):
+ flask.g.lang_code = values.pop('lang_code')
+
+ @bp.route('/')
+ def index():
+ return flask.url_for('.about')
+
+ @bp.route('/about')
+ def about():
+ return flask.url_for('.index')
+
+ app = flask.Flask(__name__)
+ app.register_blueprint(bp)
+
+ c = app.test_client()
+
+ self.assert_equal(c.get('/de/').data, '/de/about')
+ self.assert_equal(c.get('/de/about').data, '/de/')
+
+ def test_templates_and_static(self):
+ from blueprintapp import app
+ c = app.test_client()
+
+ rv = c.get('/')
+ self.assert_equal(rv.data, 'Hello from the Frontend')
+ rv = c.get('/admin/')
+ self.assert_equal(rv.data, 'Hello from the Admin')
+ rv = c.get('/admin/index2')
+ self.assert_equal(rv.data, 'Hello from the Admin')
+ rv = c.get('/admin/static/test.txt')
+ self.assert_equal(rv.data.strip(), 'Admin File')
+ rv = c.get('/admin/static/css/test.css')
+ self.assert_equal(rv.data.strip(), '/* nested file */')
+
+ with app.test_request_context():
+ self.assert_equal(flask.url_for('admin.static', filename='test.txt'),
+ '/admin/static/test.txt')
+
+ with app.test_request_context():
+ try:
+ flask.render_template('missing.html')
+ except TemplateNotFound, e:
+ self.assert_equal(e.name, 'missing.html')
+ else:
+ self.assert_(0, 'expected exception')
+
+ with flask.Flask(__name__).test_request_context():
+ self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested')
+
+ def test_templates_list(self):
+ from blueprintapp import app
+ templates = sorted(app.jinja_env.list_templates())
+ self.assert_equal(templates, ['admin/index.html',
+ 'frontend/index.html'])
+
+ def test_dotted_names(self):
+ frontend = flask.Blueprint('myapp.frontend', __name__)
+ backend = flask.Blueprint('myapp.backend', __name__)
+
+ @frontend.route('/fe')
+ def frontend_index():
+ return flask.url_for('myapp.backend.backend_index')
+
+ @frontend.route('/fe2')
+ def frontend_page2():
+ return flask.url_for('.frontend_index')
+
+ @backend.route('/be')
+ def backend_index():
+ return flask.url_for('myapp.frontend.frontend_index')
+
+ app = flask.Flask(__name__)
+ app.register_blueprint(frontend)
+ app.register_blueprint(backend)
+
+ c = app.test_client()
+ self.assert_equal(c.get('/fe').data.strip(), '/be')
+ self.assert_equal(c.get('/fe2').data.strip(), '/fe')
+ self.assert_equal(c.get('/be').data.strip(), '/fe')
+
+ def test_empty_url_defaults(self):
+ bp = flask.Blueprint('bp', __name__)
+
+ @bp.route('/', defaults={'page': 1})
+ @bp.route('/page/<int:page>')
+ def something(page):
+ return str(page)
+
+ app = flask.Flask(__name__)
+ app.register_blueprint(bp)
+
+ c = app.test_client()
+ self.assert_equal(c.get('/').data, '1')
+ self.assert_equal(c.get('/page/2').data, '2')
+
+ def test_route_decorator_custom_endpoint(self):
+
+ bp = flask.Blueprint('bp', __name__)
+
+ @bp.route('/foo')
+ def foo():
+ return flask.request.endpoint
+
+ @bp.route('/bar', endpoint='bar')
+ def foo_bar():
+ return flask.request.endpoint
+
+ @bp.route('/bar/123', endpoint='123')
+ def foo_bar_foo():
+ return flask.request.endpoint
+
+ @bp.route('/bar/foo')
+ def bar_foo():
+ return flask.request.endpoint
+
+ app = flask.Flask(__name__)
+ app.register_blueprint(bp, url_prefix='/py')
+
+ @app.route('/')
+ def index():
+ return flask.request.endpoint
+
+ c = app.test_client()
+ self.assertEqual(c.get('/').data, 'index')
+ self.assertEqual(c.get('/py/foo').data, 'bp.foo')
+ self.assertEqual(c.get('/py/bar').data, 'bp.bar')
+ self.assertEqual(c.get('/py/bar/123').data, 'bp.123')
+ self.assertEqual(c.get('/py/bar/foo').data, 'bp.bar_foo')
+
+ def test_route_decorator_custom_endpoint_with_dots(self):
+ bp = flask.Blueprint('bp', __name__)
+
+ @bp.route('/foo')
+ def foo():
+ return flask.request.endpoint
+
+ try:
+ @bp.route('/bar', endpoint='bar.bar')
+ def foo_bar():
+ return flask.request.endpoint
+ except AssertionError:
+ pass
+ else:
+ raise AssertionError('expected AssertionError not raised')
+
+ try:
+ @bp.route('/bar/123', endpoint='bar.123')
+ def foo_bar_foo():
+ return flask.request.endpoint
+ except AssertionError:
+ pass
+ else:
+ raise AssertionError('expected AssertionError not raised')
+
+ def foo_foo_foo():
+ pass
+
+ self.assertRaises(
+ AssertionError,
+ lambda: bp.add_url_rule(
+ '/bar/123', endpoint='bar.123', view_func=foo_foo_foo
+ )
+ )
+
+ self.assertRaises(
+ AssertionError,
+ bp.route('/bar/123', endpoint='bar.123'),
+ lambda: None
+ )
+
+ app = flask.Flask(__name__)
+ app.register_blueprint(bp, url_prefix='/py')
+
+ c = app.test_client()
+ self.assertEqual(c.get('/py/foo').data, 'bp.foo')
+ # The rule's din't actually made it through
+ rv = c.get('/py/bar')
+ assert rv.status_code == 404
+ rv = c.get('/py/bar/123')
+ assert rv.status_code == 404
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(BlueprintTestCase))
+ suite.addTest(unittest.makeSuite(ModuleTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/config.py b/websdk/flask/testsuite/config.py
new file mode 100644
index 0000000..ad1721f
--- /dev/null
+++ b/websdk/flask/testsuite/config.py
@@ -0,0 +1,182 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.config
+ ~~~~~~~~~~~~~~~~~~~~~~
+
+ Configuration and instances.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import os
+import sys
+import flask
+import unittest
+from flask.testsuite import FlaskTestCase
+
+
+# config keys used for the ConfigTestCase
+TEST_KEY = 'foo'
+SECRET_KEY = 'devkey'
+
+
+class ConfigTestCase(FlaskTestCase):
+
+ def common_object_test(self, app):
+ self.assert_equal(app.secret_key, 'devkey')
+ self.assert_equal(app.config['TEST_KEY'], 'foo')
+ self.assert_('ConfigTestCase' not in app.config)
+
+ def test_config_from_file(self):
+ app = flask.Flask(__name__)
+ app.config.from_pyfile(__file__.rsplit('.', 1)[0] + '.py')
+ self.common_object_test(app)
+
+ def test_config_from_object(self):
+ app = flask.Flask(__name__)
+ app.config.from_object(__name__)
+ self.common_object_test(app)
+
+ def test_config_from_class(self):
+ class Base(object):
+ TEST_KEY = 'foo'
+ class Test(Base):
+ SECRET_KEY = 'devkey'
+ app = flask.Flask(__name__)
+ app.config.from_object(Test)
+ self.common_object_test(app)
+
+ def test_config_from_envvar(self):
+ env = os.environ
+ try:
+ os.environ = {}
+ app = flask.Flask(__name__)
+ try:
+ app.config.from_envvar('FOO_SETTINGS')
+ except RuntimeError, e:
+ self.assert_("'FOO_SETTINGS' is not set" in str(e))
+ else:
+ self.assert_(0, 'expected exception')
+ self.assert_(not app.config.from_envvar('FOO_SETTINGS', silent=True))
+
+ os.environ = {'FOO_SETTINGS': __file__.rsplit('.', 1)[0] + '.py'}
+ self.assert_(app.config.from_envvar('FOO_SETTINGS'))
+ self.common_object_test(app)
+ finally:
+ os.environ = env
+
+ def test_config_missing(self):
+ app = flask.Flask(__name__)
+ try:
+ app.config.from_pyfile('missing.cfg')
+ except IOError, e:
+ msg = str(e)
+ self.assert_(msg.startswith('[Errno 2] Unable to load configuration '
+ 'file (No such file or directory):'))
+ self.assert_(msg.endswith("missing.cfg'"))
+ else:
+ self.assert_(0, 'expected config')
+ self.assert_(not app.config.from_pyfile('missing.cfg', silent=True))
+
+ def test_session_lifetime(self):
+ app = flask.Flask(__name__)
+ app.config['PERMANENT_SESSION_LIFETIME'] = 42
+ self.assert_equal(app.permanent_session_lifetime.seconds, 42)
+
+
+class InstanceTestCase(FlaskTestCase):
+
+ def test_explicit_instance_paths(self):
+ here = os.path.abspath(os.path.dirname(__file__))
+ try:
+ flask.Flask(__name__, instance_path='instance')
+ except ValueError, e:
+ self.assert_('must be absolute' in str(e))
+ else:
+ self.fail('Expected value error')
+
+ app = flask.Flask(__name__, instance_path=here)
+ self.assert_equal(app.instance_path, here)
+
+ def test_uninstalled_module_paths(self):
+ from config_module_app import app
+ here = os.path.abspath(os.path.dirname(__file__))
+ self.assert_equal(app.instance_path, os.path.join(here, 'test_apps', 'instance'))
+
+ def test_uninstalled_package_paths(self):
+ from config_package_app import app
+ here = os.path.abspath(os.path.dirname(__file__))
+ self.assert_equal(app.instance_path, os.path.join(here, 'test_apps', 'instance'))
+
+ def test_installed_module_paths(self):
+ import types
+ expected_prefix = os.path.abspath('foo')
+ mod = types.ModuleType('myapp')
+ mod.__file__ = os.path.join(expected_prefix, 'lib', 'python2.5',
+ 'site-packages', 'myapp.py')
+ sys.modules['myapp'] = mod
+ try:
+ mod.app = flask.Flask(mod.__name__)
+ self.assert_equal(mod.app.instance_path,
+ os.path.join(expected_prefix, 'var',
+ 'myapp-instance'))
+ finally:
+ sys.modules['myapp'] = None
+
+ def test_installed_package_paths(self):
+ import types
+ expected_prefix = os.path.abspath('foo')
+ package_path = os.path.join(expected_prefix, 'lib', 'python2.5',
+ 'site-packages', 'myapp')
+ mod = types.ModuleType('myapp')
+ mod.__path__ = [package_path]
+ mod.__file__ = os.path.join(package_path, '__init__.py')
+ sys.modules['myapp'] = mod
+ try:
+ mod.app = flask.Flask(mod.__name__)
+ self.assert_equal(mod.app.instance_path,
+ os.path.join(expected_prefix, 'var',
+ 'myapp-instance'))
+ finally:
+ sys.modules['myapp'] = None
+
+ def test_prefix_installed_paths(self):
+ import types
+ expected_prefix = os.path.abspath(sys.prefix)
+ package_path = os.path.join(expected_prefix, 'lib', 'python2.5',
+ 'site-packages', 'myapp')
+ mod = types.ModuleType('myapp')
+ mod.__path__ = [package_path]
+ mod.__file__ = os.path.join(package_path, '__init__.py')
+ sys.modules['myapp'] = mod
+ try:
+ mod.app = flask.Flask(mod.__name__)
+ self.assert_equal(mod.app.instance_path,
+ os.path.join(expected_prefix, 'var',
+ 'myapp-instance'))
+ finally:
+ sys.modules['myapp'] = None
+
+ def test_egg_installed_paths(self):
+ import types
+ expected_prefix = os.path.abspath(sys.prefix)
+ package_path = os.path.join(expected_prefix, 'lib', 'python2.5',
+ 'site-packages', 'MyApp.egg', 'myapp')
+ mod = types.ModuleType('myapp')
+ mod.__path__ = [package_path]
+ mod.__file__ = os.path.join(package_path, '__init__.py')
+ sys.modules['myapp'] = mod
+ try:
+ mod.app = flask.Flask(mod.__name__)
+ self.assert_equal(mod.app.instance_path,
+ os.path.join(expected_prefix, 'var',
+ 'myapp-instance'))
+ finally:
+ sys.modules['myapp'] = None
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ConfigTestCase))
+ suite.addTest(unittest.makeSuite(InstanceTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/deprecations.py b/websdk/flask/testsuite/deprecations.py
new file mode 100644
index 0000000..795a5d3
--- /dev/null
+++ b/websdk/flask/testsuite/deprecations.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.deprecations
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Tests deprecation support.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import flask
+import unittest
+from flask.testsuite import FlaskTestCase, catch_warnings
+
+
+class DeprecationsTestCase(FlaskTestCase):
+
+ def test_init_jinja_globals(self):
+ class MyFlask(flask.Flask):
+ def init_jinja_globals(self):
+ self.jinja_env.globals['foo'] = '42'
+
+ with catch_warnings() as log:
+ app = MyFlask(__name__)
+ @app.route('/')
+ def foo():
+ return app.jinja_env.globals['foo']
+
+ c = app.test_client()
+ self.assert_equal(c.get('/').data, '42')
+ self.assert_equal(len(log), 1)
+ self.assert_('init_jinja_globals' in str(log[0]['message']))
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(DeprecationsTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/examples.py b/websdk/flask/testsuite/examples.py
new file mode 100644
index 0000000..2d30958
--- /dev/null
+++ b/websdk/flask/testsuite/examples.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.examples
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Tests the examples.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import os
+import unittest
+from flask.testsuite import add_to_path
+
+
+def setup_path():
+ example_path = os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir, 'examples')
+ add_to_path(os.path.join(example_path, 'flaskr'))
+ add_to_path(os.path.join(example_path, 'minitwit'))
+
+
+def suite():
+ setup_path()
+ suite = unittest.TestSuite()
+ try:
+ from minitwit_tests import MiniTwitTestCase
+ except ImportError:
+ pass
+ else:
+ suite.addTest(unittest.makeSuite(MiniTwitTestCase))
+ try:
+ from flaskr_tests import FlaskrTestCase
+ except ImportError:
+ pass
+ else:
+ suite.addTest(unittest.makeSuite(FlaskrTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/ext.py b/websdk/flask/testsuite/ext.py
new file mode 100644
index 0000000..034ab5b
--- /dev/null
+++ b/websdk/flask/testsuite/ext.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.ext
+ ~~~~~~~~~~~~~~~~~~~
+
+ Tests the extension import thing.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from __future__ import with_statement
+
+import sys
+import unittest
+from flask.testsuite import FlaskTestCase
+
+
+class ExtImportHookTestCase(FlaskTestCase):
+
+ def setup(self):
+ # we clear this out for various reasons. The most important one is
+ # that a real flaskext could be in there which would disable our
+ # fake package. Secondly we want to make sure that the flaskext
+ # import hook does not break on reloading.
+ for entry, value in sys.modules.items():
+ if (entry.startswith('flask.ext.') or
+ entry.startswith('flask_') or
+ entry.startswith('flaskext.') or
+ entry == 'flaskext') and value is not None:
+ sys.modules.pop(entry, None)
+ from flask import ext
+ reload(ext)
+
+ # reloading must not add more hooks
+ import_hooks = 0
+ for item in sys.meta_path:
+ cls = type(item)
+ if cls.__module__ == 'flask.exthook' and \
+ cls.__name__ == 'ExtensionImporter':
+ import_hooks += 1
+ self.assert_equal(import_hooks, 1)
+
+ def teardown(self):
+ from flask import ext
+ for key in ext.__dict__:
+ self.assert_('.' not in key)
+
+ def test_flaskext_new_simple_import_normal(self):
+ from flask.ext.newext_simple import ext_id
+ self.assert_equal(ext_id, 'newext_simple')
+
+ def test_flaskext_new_simple_import_module(self):
+ from flask.ext import newext_simple
+ self.assert_equal(newext_simple.ext_id, 'newext_simple')
+ self.assert_equal(newext_simple.__name__, 'flask_newext_simple')
+
+ def test_flaskext_new_package_import_normal(self):
+ from flask.ext.newext_package import ext_id
+ self.assert_equal(ext_id, 'newext_package')
+
+ def test_flaskext_new_package_import_module(self):
+ from flask.ext import newext_package
+ self.assert_equal(newext_package.ext_id, 'newext_package')
+ self.assert_equal(newext_package.__name__, 'flask_newext_package')
+
+ def test_flaskext_new_package_import_submodule_function(self):
+ from flask.ext.newext_package.submodule import test_function
+ self.assert_equal(test_function(), 42)
+
+ def test_flaskext_new_package_import_submodule(self):
+ from flask.ext.newext_package import submodule
+ self.assert_equal(submodule.__name__, 'flask_newext_package.submodule')
+ self.assert_equal(submodule.test_function(), 42)
+
+ def test_flaskext_old_simple_import_normal(self):
+ from flask.ext.oldext_simple import ext_id
+ self.assert_equal(ext_id, 'oldext_simple')
+
+ def test_flaskext_old_simple_import_module(self):
+ from flask.ext import oldext_simple
+ self.assert_equal(oldext_simple.ext_id, 'oldext_simple')
+ self.assert_equal(oldext_simple.__name__, 'flaskext.oldext_simple')
+
+ def test_flaskext_old_package_import_normal(self):
+ from flask.ext.oldext_package import ext_id
+ self.assert_equal(ext_id, 'oldext_package')
+
+ def test_flaskext_old_package_import_module(self):
+ from flask.ext import oldext_package
+ self.assert_equal(oldext_package.ext_id, 'oldext_package')
+ self.assert_equal(oldext_package.__name__, 'flaskext.oldext_package')
+
+ def test_flaskext_old_package_import_submodule(self):
+ from flask.ext.oldext_package import submodule
+ self.assert_equal(submodule.__name__, 'flaskext.oldext_package.submodule')
+ self.assert_equal(submodule.test_function(), 42)
+
+ def test_flaskext_old_package_import_submodule_function(self):
+ from flask.ext.oldext_package.submodule import test_function
+ self.assert_equal(test_function(), 42)
+
+ def test_flaskext_broken_package_no_module_caching(self):
+ for x in xrange(2):
+ with self.assert_raises(ImportError):
+ import flask.ext.broken
+
+ def test_no_error_swallowing(self):
+ try:
+ import flask.ext.broken
+ except ImportError:
+ exc_type, exc_value, tb = sys.exc_info()
+ self.assert_(exc_type is ImportError)
+ self.assert_equal(str(exc_value), 'No module named missing_module')
+ self.assert_(tb.tb_frame.f_globals is globals())
+
+ next = tb.tb_next
+ self.assert_('flask_broken/__init__.py' in next.tb_frame.f_code.co_filename)
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ExtImportHookTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/helpers.py b/websdk/flask/testsuite/helpers.py
new file mode 100644
index 0000000..052d36e
--- /dev/null
+++ b/websdk/flask/testsuite/helpers.py
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.helpers
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Various helpers.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import os
+import flask
+import unittest
+from logging import StreamHandler
+from StringIO import StringIO
+from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr
+from werkzeug.http import parse_options_header
+
+
+def has_encoding(name):
+ try:
+ import codecs
+ codecs.lookup(name)
+ return True
+ except LookupError:
+ return False
+
+
+class JSONTestCase(FlaskTestCase):
+
+ def test_json_bad_requests(self):
+ app = flask.Flask(__name__)
+ @app.route('/json', methods=['POST'])
+ def return_json():
+ return unicode(flask.request.json)
+ c = app.test_client()
+ rv = c.post('/json', data='malformed', content_type='application/json')
+ self.assert_equal(rv.status_code, 400)
+
+ def test_json_body_encoding(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ @app.route('/')
+ def index():
+ return flask.request.json
+
+ c = app.test_client()
+ resp = c.get('/', data=u'"Hällo Wörld"'.encode('iso-8859-15'),
+ content_type='application/json; charset=iso-8859-15')
+ self.assert_equal(resp.data, u'Hällo Wörld'.encode('utf-8'))
+
+ def test_jsonify(self):
+ d = dict(a=23, b=42, c=[1, 2, 3])
+ app = flask.Flask(__name__)
+ @app.route('/kw')
+ def return_kwargs():
+ return flask.jsonify(**d)
+ @app.route('/dict')
+ def return_dict():
+ return flask.jsonify(d)
+ c = app.test_client()
+ for url in '/kw', '/dict':
+ rv = c.get(url)
+ self.assert_equal(rv.mimetype, 'application/json')
+ self.assert_equal(flask.json.loads(rv.data), d)
+
+ def test_json_attr(self):
+ app = flask.Flask(__name__)
+ @app.route('/add', methods=['POST'])
+ def add():
+ return unicode(flask.request.json['a'] + flask.request.json['b'])
+ c = app.test_client()
+ rv = c.post('/add', data=flask.json.dumps({'a': 1, 'b': 2}),
+ content_type='application/json')
+ self.assert_equal(rv.data, '3')
+
+ def test_template_escaping(self):
+ app = flask.Flask(__name__)
+ render = flask.render_template_string
+ with app.test_request_context():
+ rv = render('{{ "</script>"|tojson|safe }}')
+ self.assert_equal(rv, '"<\\/script>"')
+ rv = render('{{ "<\0/script>"|tojson|safe }}')
+ self.assert_equal(rv, '"<\\u0000\\/script>"')
+
+ def test_modified_url_encoding(self):
+ class ModifiedRequest(flask.Request):
+ url_charset = 'euc-kr'
+ app = flask.Flask(__name__)
+ app.request_class = ModifiedRequest
+ app.url_map.charset = 'euc-kr'
+
+ @app.route('/')
+ def index():
+ return flask.request.args['foo']
+
+ rv = app.test_client().get(u'/?foo=정상처리'.encode('euc-kr'))
+ self.assert_equal(rv.status_code, 200)
+ self.assert_equal(rv.data, u'정상처리'.encode('utf-8'))
+
+ if not has_encoding('euc-kr'):
+ test_modified_url_encoding = None
+
+
+class SendfileTestCase(FlaskTestCase):
+
+ def test_send_file_regular(self):
+ app = flask.Flask(__name__)
+ with app.test_request_context():
+ rv = flask.send_file('static/index.html')
+ self.assert_(rv.direct_passthrough)
+ self.assert_equal(rv.mimetype, 'text/html')
+ with app.open_resource('static/index.html') as f:
+ self.assert_equal(rv.data, f.read())
+
+ def test_send_file_xsendfile(self):
+ app = flask.Flask(__name__)
+ app.use_x_sendfile = True
+ with app.test_request_context():
+ rv = flask.send_file('static/index.html')
+ self.assert_(rv.direct_passthrough)
+ self.assert_('x-sendfile' in rv.headers)
+ self.assert_equal(rv.headers['x-sendfile'],
+ os.path.join(app.root_path, 'static/index.html'))
+ self.assert_equal(rv.mimetype, 'text/html')
+
+ def test_send_file_object(self):
+ app = flask.Flask(__name__)
+ with catch_warnings() as captured:
+ with app.test_request_context():
+ f = open(os.path.join(app.root_path, 'static/index.html'))
+ rv = flask.send_file(f)
+ with app.open_resource('static/index.html') as f:
+ self.assert_equal(rv.data, f.read())
+ self.assert_equal(rv.mimetype, 'text/html')
+ # mimetypes + etag
+ self.assert_equal(len(captured), 2)
+
+ app.use_x_sendfile = True
+ with catch_warnings() as captured:
+ with app.test_request_context():
+ f = open(os.path.join(app.root_path, 'static/index.html'))
+ rv = flask.send_file(f)
+ self.assert_equal(rv.mimetype, 'text/html')
+ self.assert_('x-sendfile' in rv.headers)
+ self.assert_equal(rv.headers['x-sendfile'],
+ os.path.join(app.root_path, 'static/index.html'))
+ # mimetypes + etag
+ self.assert_equal(len(captured), 2)
+
+ app.use_x_sendfile = False
+ with app.test_request_context():
+ with catch_warnings() as captured:
+ f = StringIO('Test')
+ rv = flask.send_file(f)
+ self.assert_equal(rv.data, 'Test')
+ self.assert_equal(rv.mimetype, 'application/octet-stream')
+ # etags
+ self.assert_equal(len(captured), 1)
+ with catch_warnings() as captured:
+ f = StringIO('Test')
+ rv = flask.send_file(f, mimetype='text/plain')
+ self.assert_equal(rv.data, 'Test')
+ self.assert_equal(rv.mimetype, 'text/plain')
+ # etags
+ self.assert_equal(len(captured), 1)
+
+ app.use_x_sendfile = True
+ with catch_warnings() as captured:
+ with app.test_request_context():
+ f = StringIO('Test')
+ rv = flask.send_file(f)
+ self.assert_('x-sendfile' not in rv.headers)
+ # etags
+ self.assert_equal(len(captured), 1)
+
+ def test_attachment(self):
+ app = flask.Flask(__name__)
+ with catch_warnings() as captured:
+ with app.test_request_context():
+ f = open(os.path.join(app.root_path, 'static/index.html'))
+ rv = flask.send_file(f, as_attachment=True)
+ value, options = parse_options_header(rv.headers['Content-Disposition'])
+ self.assert_equal(value, 'attachment')
+ # mimetypes + etag
+ self.assert_equal(len(captured), 2)
+
+ with app.test_request_context():
+ self.assert_equal(options['filename'], 'index.html')
+ rv = flask.send_file('static/index.html', as_attachment=True)
+ value, options = parse_options_header(rv.headers['Content-Disposition'])
+ self.assert_equal(value, 'attachment')
+ self.assert_equal(options['filename'], 'index.html')
+
+ with app.test_request_context():
+ rv = flask.send_file(StringIO('Test'), as_attachment=True,
+ attachment_filename='index.txt',
+ add_etags=False)
+ self.assert_equal(rv.mimetype, 'text/plain')
+ value, options = parse_options_header(rv.headers['Content-Disposition'])
+ self.assert_equal(value, 'attachment')
+ self.assert_equal(options['filename'], 'index.txt')
+
+
+class LoggingTestCase(FlaskTestCase):
+
+ def test_logger_cache(self):
+ app = flask.Flask(__name__)
+ logger1 = app.logger
+ self.assert_(app.logger is logger1)
+ self.assert_equal(logger1.name, __name__)
+ app.logger_name = __name__ + '/test_logger_cache'
+ self.assert_(app.logger is not logger1)
+
+ def test_debug_log(self):
+ app = flask.Flask(__name__)
+ app.debug = True
+
+ @app.route('/')
+ def index():
+ app.logger.warning('the standard library is dead')
+ app.logger.debug('this is a debug statement')
+ return ''
+
+ @app.route('/exc')
+ def exc():
+ 1/0
+
+ with app.test_client() as c:
+ with catch_stderr() as err:
+ c.get('/')
+ out = err.getvalue()
+ self.assert_('WARNING in helpers [' in out)
+ self.assert_(os.path.basename(__file__.rsplit('.', 1)[0] + '.py') in out)
+ self.assert_('the standard library is dead' in out)
+ self.assert_('this is a debug statement' in out)
+
+ with catch_stderr() as err:
+ try:
+ c.get('/exc')
+ except ZeroDivisionError:
+ pass
+ else:
+ self.assert_(False, 'debug log ate the exception')
+
+ def test_exception_logging(self):
+ out = StringIO()
+ app = flask.Flask(__name__)
+ app.logger_name = 'flask_tests/test_exception_logging'
+ app.logger.addHandler(StreamHandler(out))
+
+ @app.route('/')
+ def index():
+ 1/0
+
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.status_code, 500)
+ self.assert_('Internal Server Error' in rv.data)
+
+ err = out.getvalue()
+ self.assert_('Exception on / [GET]' in err)
+ self.assert_('Traceback (most recent call last):' in err)
+ self.assert_('1/0' in err)
+ self.assert_('ZeroDivisionError:' in err)
+
+ def test_processor_exceptions(self):
+ app = flask.Flask(__name__)
+ @app.before_request
+ def before_request():
+ if trigger == 'before':
+ 1/0
+ @app.after_request
+ def after_request(response):
+ if trigger == 'after':
+ 1/0
+ return response
+ @app.route('/')
+ def index():
+ return 'Foo'
+ @app.errorhandler(500)
+ def internal_server_error(e):
+ return 'Hello Server Error', 500
+ for trigger in 'before', 'after':
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.status_code, 500)
+ self.assert_equal(rv.data, 'Hello Server Error')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ if flask.json_available:
+ suite.addTest(unittest.makeSuite(JSONTestCase))
+ suite.addTest(unittest.makeSuite(SendfileTestCase))
+ suite.addTest(unittest.makeSuite(LoggingTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/signals.py b/websdk/flask/testsuite/signals.py
new file mode 100644
index 0000000..da1a68c
--- /dev/null
+++ b/websdk/flask/testsuite/signals.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.signals
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Signalling.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import flask
+import unittest
+from flask.testsuite import FlaskTestCase
+
+
+class SignalsTestCase(FlaskTestCase):
+
+ def test_template_rendered(self):
+ app = flask.Flask(__name__)
+
+ @app.route('/')
+ def index():
+ return flask.render_template('simple_template.html', whiskey=42)
+
+ recorded = []
+ def record(sender, template, context):
+ recorded.append((template, context))
+
+ flask.template_rendered.connect(record, app)
+ try:
+ app.test_client().get('/')
+ self.assert_equal(len(recorded), 1)
+ template, context = recorded[0]
+ self.assert_equal(template.name, 'simple_template.html')
+ self.assert_equal(context['whiskey'], 42)
+ finally:
+ flask.template_rendered.disconnect(record, app)
+
+ def test_request_signals(self):
+ app = flask.Flask(__name__)
+ calls = []
+
+ def before_request_signal(sender):
+ calls.append('before-signal')
+
+ def after_request_signal(sender, response):
+ self.assert_equal(response.data, 'stuff')
+ calls.append('after-signal')
+
+ @app.before_request
+ def before_request_handler():
+ calls.append('before-handler')
+
+ @app.after_request
+ def after_request_handler(response):
+ calls.append('after-handler')
+ response.data = 'stuff'
+ return response
+
+ @app.route('/')
+ def index():
+ calls.append('handler')
+ return 'ignored anyway'
+
+ flask.request_started.connect(before_request_signal, app)
+ flask.request_finished.connect(after_request_signal, app)
+
+ try:
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.data, 'stuff')
+
+ self.assert_equal(calls, ['before-signal', 'before-handler',
+ 'handler', 'after-handler',
+ 'after-signal'])
+ finally:
+ flask.request_started.disconnect(before_request_signal, app)
+ flask.request_finished.disconnect(after_request_signal, app)
+
+ def test_request_exception_signal(self):
+ app = flask.Flask(__name__)
+ recorded = []
+
+ @app.route('/')
+ def index():
+ 1/0
+
+ def record(sender, exception):
+ recorded.append(exception)
+
+ flask.got_request_exception.connect(record, app)
+ try:
+ self.assert_equal(app.test_client().get('/').status_code, 500)
+ self.assert_equal(len(recorded), 1)
+ self.assert_(isinstance(recorded[0], ZeroDivisionError))
+ finally:
+ flask.got_request_exception.disconnect(record, app)
+
+
+def suite():
+ suite = unittest.TestSuite()
+ if flask.signals_available:
+ suite.addTest(unittest.makeSuite(SignalsTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/static/index.html b/websdk/flask/testsuite/static/index.html
new file mode 100644
index 0000000..de8b69b
--- /dev/null
+++ b/websdk/flask/testsuite/static/index.html
@@ -0,0 +1 @@
+<h1>Hello World!</h1>
diff --git a/websdk/flask/testsuite/subclassing.py b/websdk/flask/testsuite/subclassing.py
new file mode 100644
index 0000000..e56ad56
--- /dev/null
+++ b/websdk/flask/testsuite/subclassing.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.subclassing
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Test that certain behavior of flask can be customized by
+ subclasses.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import flask
+import unittest
+from StringIO import StringIO
+from logging import StreamHandler
+from flask.testsuite import FlaskTestCase
+
+
+class FlaskSubclassingTestCase(FlaskTestCase):
+
+ def test_supressed_exception_logging(self):
+ class SupressedFlask(flask.Flask):
+ def log_exception(self, exc_info):
+ pass
+
+ out = StringIO()
+ app = SupressedFlask(__name__)
+ app.logger_name = 'flask_tests/test_supressed_exception_logging'
+ app.logger.addHandler(StreamHandler(out))
+
+ @app.route('/')
+ def index():
+ 1/0
+
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.status_code, 500)
+ self.assert_('Internal Server Error' in rv.data)
+
+ err = out.getvalue()
+ self.assert_equal(err, '')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(FlaskSubclassingTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/templates/_macro.html b/websdk/flask/testsuite/templates/_macro.html
new file mode 100644
index 0000000..3460ae2
--- /dev/null
+++ b/websdk/flask/testsuite/templates/_macro.html
@@ -0,0 +1 @@
+{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
diff --git a/websdk/flask/testsuite/templates/context_template.html b/websdk/flask/testsuite/templates/context_template.html
new file mode 100644
index 0000000..fadf3e5
--- /dev/null
+++ b/websdk/flask/testsuite/templates/context_template.html
@@ -0,0 +1 @@
+<p>{{ value }}|{{ injected_value }}
diff --git a/websdk/flask/testsuite/templates/escaping_template.html b/websdk/flask/testsuite/templates/escaping_template.html
new file mode 100644
index 0000000..dc47644
--- /dev/null
+++ b/websdk/flask/testsuite/templates/escaping_template.html
@@ -0,0 +1,6 @@
+{{ text }}
+{{ html }}
+{% autoescape false %}{{ text }}
+{{ html }}{% endautoescape %}
+{% autoescape true %}{{ text }}
+{{ html }}{% endautoescape %}
diff --git a/websdk/flask/testsuite/templates/mail.txt b/websdk/flask/testsuite/templates/mail.txt
new file mode 100644
index 0000000..d6cb92e
--- /dev/null
+++ b/websdk/flask/testsuite/templates/mail.txt
@@ -0,0 +1 @@
+{{ foo}} Mail
diff --git a/websdk/flask/testsuite/templates/nested/nested.txt b/websdk/flask/testsuite/templates/nested/nested.txt
new file mode 100644
index 0000000..2c8634f
--- /dev/null
+++ b/websdk/flask/testsuite/templates/nested/nested.txt
@@ -0,0 +1 @@
+I'm nested
diff --git a/websdk/flask/testsuite/templates/simple_template.html b/websdk/flask/testsuite/templates/simple_template.html
new file mode 100644
index 0000000..c24612c
--- /dev/null
+++ b/websdk/flask/testsuite/templates/simple_template.html
@@ -0,0 +1 @@
+<h1>{{ whiskey }}</h1>
diff --git a/websdk/flask/testsuite/templates/template_filter.html b/websdk/flask/testsuite/templates/template_filter.html
new file mode 100644
index 0000000..af46cd9
--- /dev/null
+++ b/websdk/flask/testsuite/templates/template_filter.html
@@ -0,0 +1 @@
+{{ value|super_reverse }} \ No newline at end of file
diff --git a/websdk/flask/testsuite/templating.py b/websdk/flask/testsuite/templating.py
new file mode 100644
index 0000000..453bfb6
--- /dev/null
+++ b/websdk/flask/testsuite/templating.py
@@ -0,0 +1,144 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.templating
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Template functionality
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import flask
+import unittest
+from flask.testsuite import FlaskTestCase
+
+
+class TemplatingTestCase(FlaskTestCase):
+
+ def test_context_processing(self):
+ app = flask.Flask(__name__)
+ @app.context_processor
+ def context_processor():
+ return {'injected_value': 42}
+ @app.route('/')
+ def index():
+ return flask.render_template('context_template.html', value=23)
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.data, '<p>23|42')
+
+ def test_original_win(self):
+ app = flask.Flask(__name__)
+ @app.route('/')
+ def index():
+ return flask.render_template_string('{{ config }}', config=42)
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.data, '42')
+
+ def test_standard_context(self):
+ app = flask.Flask(__name__)
+ app.secret_key = 'development key'
+ @app.route('/')
+ def index():
+ flask.g.foo = 23
+ flask.session['test'] = 'aha'
+ return flask.render_template_string('''
+ {{ request.args.foo }}
+ {{ g.foo }}
+ {{ config.DEBUG }}
+ {{ session.test }}
+ ''')
+ rv = app.test_client().get('/?foo=42')
+ self.assert_equal(rv.data.split(), ['42', '23', 'False', 'aha'])
+
+ def test_escaping(self):
+ text = '<p>Hello World!'
+ app = flask.Flask(__name__)
+ @app.route('/')
+ def index():
+ return flask.render_template('escaping_template.html', text=text,
+ html=flask.Markup(text))
+ lines = app.test_client().get('/').data.splitlines()
+ self.assert_equal(lines, [
+ '&lt;p&gt;Hello World!',
+ '<p>Hello World!',
+ '<p>Hello World!',
+ '<p>Hello World!',
+ '&lt;p&gt;Hello World!',
+ '<p>Hello World!'
+ ])
+
+ def test_no_escaping(self):
+ app = flask.Flask(__name__)
+ with app.test_request_context():
+ self.assert_equal(flask.render_template_string('{{ foo }}',
+ foo='<test>'), '<test>')
+ self.assert_equal(flask.render_template('mail.txt', foo='<test>'),
+ '<test> Mail')
+
+ def test_macros(self):
+ app = flask.Flask(__name__)
+ with app.test_request_context():
+ macro = flask.get_template_attribute('_macro.html', 'hello')
+ self.assert_equal(macro('World'), 'Hello World!')
+
+ def test_template_filter(self):
+ app = flask.Flask(__name__)
+ @app.template_filter()
+ def my_reverse(s):
+ return s[::-1]
+ self.assert_('my_reverse' in app.jinja_env.filters.keys())
+ self.assert_equal(app.jinja_env.filters['my_reverse'], my_reverse)
+ self.assert_equal(app.jinja_env.filters['my_reverse']('abcd'), 'dcba')
+
+ def test_template_filter_with_name(self):
+ app = flask.Flask(__name__)
+ @app.template_filter('strrev')
+ def my_reverse(s):
+ return s[::-1]
+ self.assert_('strrev' in app.jinja_env.filters.keys())
+ self.assert_equal(app.jinja_env.filters['strrev'], my_reverse)
+ self.assert_equal(app.jinja_env.filters['strrev']('abcd'), 'dcba')
+
+ def test_template_filter_with_template(self):
+ app = flask.Flask(__name__)
+ @app.template_filter()
+ def super_reverse(s):
+ return s[::-1]
+ @app.route('/')
+ def index():
+ return flask.render_template('template_filter.html', value='abcd')
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.data, 'dcba')
+
+ def test_template_filter_with_name_and_template(self):
+ app = flask.Flask(__name__)
+ @app.template_filter('super_reverse')
+ def my_reverse(s):
+ return s[::-1]
+ @app.route('/')
+ def index():
+ return flask.render_template('template_filter.html', value='abcd')
+ rv = app.test_client().get('/')
+ self.assert_equal(rv.data, 'dcba')
+
+ def test_custom_template_loader(self):
+ class MyFlask(flask.Flask):
+ def create_global_jinja_loader(self):
+ from jinja2 import DictLoader
+ return DictLoader({'index.html': 'Hello Custom World!'})
+ app = MyFlask(__name__)
+ @app.route('/')
+ def index():
+ return flask.render_template('index.html')
+ c = app.test_client()
+ rv = c.get('/')
+ self.assert_equal(rv.data, 'Hello Custom World!')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TemplatingTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/test_apps/blueprintapp/__init__.py b/websdk/flask/testsuite/test_apps/blueprintapp/__init__.py
new file mode 100644
index 0000000..2b8ef75
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/blueprintapp/__init__.py
@@ -0,0 +1,7 @@
+from flask import Flask
+
+app = Flask(__name__)
+from blueprintapp.apps.admin import admin
+from blueprintapp.apps.frontend import frontend
+app.register_blueprint(admin)
+app.register_blueprint(frontend)
diff --git a/websdk/flask/testsuite/test_apps/blueprintapp/apps/__init__.py b/websdk/flask/testsuite/test_apps/blueprintapp/apps/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/blueprintapp/apps/__init__.py
diff --git a/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py b/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py
new file mode 100644
index 0000000..3f714d9
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/__init__.py
@@ -0,0 +1,15 @@
+from flask import Blueprint, render_template
+
+admin = Blueprint('admin', __name__, url_prefix='/admin',
+ template_folder='templates',
+ static_folder='static')
+
+
+@admin.route('/')
+def index():
+ return render_template('admin/index.html')
+
+
+@admin.route('/index2')
+def index2():
+ return render_template('./admin/index.html')
diff --git a/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css b/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css
new file mode 100644
index 0000000..b9f564d
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/static/css/test.css
@@ -0,0 +1 @@
+/* nested file */
diff --git a/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt b/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt
new file mode 100644
index 0000000..f220d22
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/static/test.txt
@@ -0,0 +1 @@
+Admin File
diff --git a/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html b/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html
new file mode 100644
index 0000000..eeec199
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/blueprintapp/apps/admin/templates/admin/index.html
@@ -0,0 +1 @@
+Hello from the Admin
diff --git a/websdk/flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py b/websdk/flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py
new file mode 100644
index 0000000..69c8666
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/blueprintapp/apps/frontend/__init__.py
@@ -0,0 +1,8 @@
+from flask import Blueprint, render_template
+
+frontend = Blueprint('frontend', __name__, template_folder='templates')
+
+
+@frontend.route('/')
+def index():
+ return render_template('frontend/index.html')
diff --git a/websdk/flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html b/websdk/flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html
new file mode 100644
index 0000000..a062d71
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html
@@ -0,0 +1 @@
+Hello from the Frontend
diff --git a/websdk/flask/testsuite/test_apps/config_module_app.py b/websdk/flask/testsuite/test_apps/config_module_app.py
new file mode 100644
index 0000000..380d46b
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/config_module_app.py
@@ -0,0 +1,4 @@
+import os
+import flask
+here = os.path.abspath(os.path.dirname(__file__))
+app = flask.Flask(__name__)
diff --git a/websdk/flask/testsuite/test_apps/config_package_app/__init__.py b/websdk/flask/testsuite/test_apps/config_package_app/__init__.py
new file mode 100644
index 0000000..380d46b
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/config_package_app/__init__.py
@@ -0,0 +1,4 @@
+import os
+import flask
+here = os.path.abspath(os.path.dirname(__file__))
+app = flask.Flask(__name__)
diff --git a/websdk/flask/testsuite/test_apps/flask_broken/__init__.py b/websdk/flask/testsuite/test_apps/flask_broken/__init__.py
new file mode 100644
index 0000000..c194c04
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/flask_broken/__init__.py
@@ -0,0 +1,2 @@
+import flask.ext.broken.b
+import missing_module
diff --git a/websdk/flask/testsuite/test_apps/flask_broken/b.py b/websdk/flask/testsuite/test_apps/flask_broken/b.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/flask_broken/b.py
diff --git a/websdk/flask/testsuite/test_apps/flask_newext_package/__init__.py b/websdk/flask/testsuite/test_apps/flask_newext_package/__init__.py
new file mode 100644
index 0000000..3fd13e1
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/flask_newext_package/__init__.py
@@ -0,0 +1 @@
+ext_id = 'newext_package'
diff --git a/websdk/flask/testsuite/test_apps/flask_newext_package/submodule.py b/websdk/flask/testsuite/test_apps/flask_newext_package/submodule.py
new file mode 100644
index 0000000..26ad56b
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/flask_newext_package/submodule.py
@@ -0,0 +1,2 @@
+def test_function():
+ return 42
diff --git a/websdk/flask/testsuite/test_apps/flask_newext_simple.py b/websdk/flask/testsuite/test_apps/flask_newext_simple.py
new file mode 100644
index 0000000..dc4a362
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/flask_newext_simple.py
@@ -0,0 +1 @@
+ext_id = 'newext_simple'
diff --git a/websdk/flask/testsuite/test_apps/flaskext/__init__.py b/websdk/flask/testsuite/test_apps/flaskext/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/flaskext/__init__.py
diff --git a/websdk/flask/testsuite/test_apps/flaskext/oldext_package/__init__.py b/websdk/flask/testsuite/test_apps/flaskext/oldext_package/__init__.py
new file mode 100644
index 0000000..7c46206
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/flaskext/oldext_package/__init__.py
@@ -0,0 +1 @@
+ext_id = 'oldext_package'
diff --git a/websdk/flask/testsuite/test_apps/flaskext/oldext_package/submodule.py b/websdk/flask/testsuite/test_apps/flaskext/oldext_package/submodule.py
new file mode 100644
index 0000000..26ad56b
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/flaskext/oldext_package/submodule.py
@@ -0,0 +1,2 @@
+def test_function():
+ return 42
diff --git a/websdk/flask/testsuite/test_apps/flaskext/oldext_simple.py b/websdk/flask/testsuite/test_apps/flaskext/oldext_simple.py
new file mode 100644
index 0000000..c6664a7
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/flaskext/oldext_simple.py
@@ -0,0 +1 @@
+ext_id = 'oldext_simple'
diff --git a/websdk/flask/testsuite/test_apps/moduleapp/__init__.py b/websdk/flask/testsuite/test_apps/moduleapp/__init__.py
new file mode 100644
index 0000000..35e82d4
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/moduleapp/__init__.py
@@ -0,0 +1,7 @@
+from flask import Flask
+
+app = Flask(__name__)
+from moduleapp.apps.admin import admin
+from moduleapp.apps.frontend import frontend
+app.register_module(admin)
+app.register_module(frontend)
diff --git a/websdk/flask/testsuite/test_apps/moduleapp/apps/__init__.py b/websdk/flask/testsuite/test_apps/moduleapp/apps/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/moduleapp/apps/__init__.py
diff --git a/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py b/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py
new file mode 100644
index 0000000..b85b802
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/__init__.py
@@ -0,0 +1,14 @@
+from flask import Module, render_template
+
+
+admin = Module(__name__, url_prefix='/admin')
+
+
+@admin.route('/')
+def index():
+ return render_template('admin/index.html')
+
+
+@admin.route('/index2')
+def index2():
+ return render_template('./admin/index.html')
diff --git a/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css b/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css
new file mode 100644
index 0000000..b9f564d
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/static/css/test.css
@@ -0,0 +1 @@
+/* nested file */
diff --git a/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt b/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt
new file mode 100644
index 0000000..f220d22
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/static/test.txt
@@ -0,0 +1 @@
+Admin File
diff --git a/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html b/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html
new file mode 100644
index 0000000..eeec199
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/moduleapp/apps/admin/templates/index.html
@@ -0,0 +1 @@
+Hello from the Admin
diff --git a/websdk/flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py b/websdk/flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py
new file mode 100644
index 0000000..f83581e
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/moduleapp/apps/frontend/__init__.py
@@ -0,0 +1,9 @@
+from flask import Module, render_template
+
+
+frontend = Module(__name__)
+
+
+@frontend.route('/')
+def index():
+ return render_template('frontend/index.html')
diff --git a/websdk/flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html b/websdk/flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html
new file mode 100644
index 0000000..a062d71
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/moduleapp/apps/frontend/templates/index.html
@@ -0,0 +1 @@
+Hello from the Frontend
diff --git a/websdk/flask/testsuite/test_apps/subdomaintestmodule/__init__.py b/websdk/flask/testsuite/test_apps/subdomaintestmodule/__init__.py
new file mode 100644
index 0000000..3c5e358
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/subdomaintestmodule/__init__.py
@@ -0,0 +1,4 @@
+from flask import Module
+
+
+mod = Module(__name__, 'foo', subdomain='foo')
diff --git a/websdk/flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt b/websdk/flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt
new file mode 100644
index 0000000..12e23c1
--- /dev/null
+++ b/websdk/flask/testsuite/test_apps/subdomaintestmodule/static/hello.txt
@@ -0,0 +1 @@
+Hello Subdomain
diff --git a/websdk/flask/testsuite/testing.py b/websdk/flask/testsuite/testing.py
new file mode 100644
index 0000000..6574e77
--- /dev/null
+++ b/websdk/flask/testsuite/testing.py
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.testing
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+ Test client and more.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from __future__ import with_statement
+
+import flask
+import unittest
+from flask.testsuite import FlaskTestCase
+
+
+class TestToolsTestCase(FlaskTestCase):
+
+ def test_environ_defaults_from_config(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ app.config['SERVER_NAME'] = 'example.com:1234'
+ app.config['APPLICATION_ROOT'] = '/foo'
+ @app.route('/')
+ def index():
+ return flask.request.url
+
+ ctx = app.test_request_context()
+ self.assert_equal(ctx.request.url, 'http://example.com:1234/foo/')
+ with app.test_client() as c:
+ rv = c.get('/')
+ self.assert_equal(rv.data, 'http://example.com:1234/foo/')
+
+ def test_environ_defaults(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ @app.route('/')
+ def index():
+ return flask.request.url
+
+ ctx = app.test_request_context()
+ self.assert_equal(ctx.request.url, 'http://localhost/')
+ with app.test_client() as c:
+ rv = c.get('/')
+ self.assert_equal(rv.data, 'http://localhost/')
+
+ def test_session_transactions(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ app.secret_key = 'testing'
+
+ @app.route('/')
+ def index():
+ return unicode(flask.session['foo'])
+
+ with app.test_client() as c:
+ with c.session_transaction() as sess:
+ self.assert_equal(len(sess), 0)
+ sess['foo'] = [42]
+ self.assert_equal(len(sess), 1)
+ rv = c.get('/')
+ self.assert_equal(rv.data, '[42]')
+ with c.session_transaction() as sess:
+ self.assert_equal(len(sess), 1)
+ self.assert_equal(sess['foo'], [42])
+
+ def test_session_transactions_no_null_sessions(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+
+ with app.test_client() as c:
+ try:
+ with c.session_transaction() as sess:
+ pass
+ except RuntimeError, e:
+ self.assert_('Session backend did not open a session' in str(e))
+ else:
+ self.fail('Expected runtime error')
+
+ def test_session_transactions_keep_context(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ app.secret_key = 'testing'
+
+ with app.test_client() as c:
+ rv = c.get('/')
+ req = flask.request._get_current_object()
+ with c.session_transaction():
+ self.assert_(req is flask.request._get_current_object())
+
+ def test_session_transaction_needs_cookies(self):
+ app = flask.Flask(__name__)
+ app.testing = True
+ c = app.test_client(use_cookies=False)
+ try:
+ with c.session_transaction() as s:
+ pass
+ except RuntimeError, e:
+ self.assert_('cookies' in str(e))
+ else:
+ self.fail('Expected runtime error')
+
+ def test_test_client_context_binding(self):
+ app = flask.Flask(__name__)
+ @app.route('/')
+ def index():
+ flask.g.value = 42
+ return 'Hello World!'
+
+ @app.route('/other')
+ def other():
+ 1/0
+
+ with app.test_client() as c:
+ resp = c.get('/')
+ self.assert_equal(flask.g.value, 42)
+ self.assert_equal(resp.data, 'Hello World!')
+ self.assert_equal(resp.status_code, 200)
+
+ resp = c.get('/other')
+ self.assert_(not hasattr(flask.g, 'value'))
+ self.assert_('Internal Server Error' in resp.data)
+ self.assert_equal(resp.status_code, 500)
+ flask.g.value = 23
+
+ try:
+ flask.g.value
+ except (AttributeError, RuntimeError):
+ pass
+ else:
+ raise AssertionError('some kind of exception expected')
+
+ def test_reuse_client(self):
+ app = flask.Flask(__name__)
+ c = app.test_client()
+
+ with c:
+ self.assert_equal(c.get('/').status_code, 404)
+
+ with c:
+ self.assert_equal(c.get('/').status_code, 404)
+
+ def test_test_client_calls_teardown_handlers(self):
+ app = flask.Flask(__name__)
+ called = []
+ @app.teardown_request
+ def remember(error):
+ called.append(error)
+
+ with app.test_client() as c:
+ self.assert_equal(called, [])
+ c.get('/')
+ self.assert_equal(called, [])
+ self.assert_equal(called, [None])
+
+ del called[:]
+ with app.test_client() as c:
+ self.assert_equal(called, [])
+ c.get('/')
+ self.assert_equal(called, [])
+ c.get('/')
+ self.assert_equal(called, [None])
+ self.assert_equal(called, [None, None])
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestToolsTestCase))
+ return suite
diff --git a/websdk/flask/testsuite/views.py b/websdk/flask/testsuite/views.py
new file mode 100644
index 0000000..c7cb0a8
--- /dev/null
+++ b/websdk/flask/testsuite/views.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.testsuite.views
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ Pluggable views.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+import flask
+import flask.views
+import unittest
+from flask.testsuite import FlaskTestCase
+from werkzeug.http import parse_set_header
+
+
+class ViewTestCase(FlaskTestCase):
+
+ def common_test(self, app):
+ c = app.test_client()
+
+ self.assert_equal(c.get('/').data, 'GET')
+ self.assert_equal(c.post('/').data, 'POST')
+ self.assert_equal(c.put('/').status_code, 405)
+ meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow'])
+ self.assert_equal(sorted(meths), ['GET', 'HEAD', 'OPTIONS', 'POST'])
+
+ def test_basic_view(self):
+ app = flask.Flask(__name__)
+
+ class Index(flask.views.View):
+ methods = ['GET', 'POST']
+ def dispatch_request(self):
+ return flask.request.method
+
+ app.add_url_rule('/', view_func=Index.as_view('index'))
+ self.common_test(app)
+
+ def test_method_based_view(self):
+ app = flask.Flask(__name__)
+
+ class Index(flask.views.MethodView):
+ def get(self):
+ return 'GET'
+ def post(self):
+ return 'POST'
+
+ app.add_url_rule('/', view_func=Index.as_view('index'))
+
+ self.common_test(app)
+
+ def test_view_patching(self):
+ app = flask.Flask(__name__)
+
+ class Index(flask.views.MethodView):
+ def get(self):
+ 1/0
+ def post(self):
+ 1/0
+
+ class Other(Index):
+ def get(self):
+ return 'GET'
+ def post(self):
+ return 'POST'
+
+ view = Index.as_view('index')
+ view.view_class = Other
+ app.add_url_rule('/', view_func=view)
+ self.common_test(app)
+
+ def test_view_inheritance(self):
+ app = flask.Flask(__name__)
+
+ class Index(flask.views.MethodView):
+ def get(self):
+ return 'GET'
+ def post(self):
+ return 'POST'
+
+ class BetterIndex(Index):
+ def delete(self):
+ return 'DELETE'
+
+ app.add_url_rule('/', view_func=BetterIndex.as_view('index'))
+ c = app.test_client()
+
+ meths = parse_set_header(c.open('/', method='OPTIONS').headers['Allow'])
+ self.assert_equal(sorted(meths), ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST'])
+
+ def test_view_decorators(self):
+ app = flask.Flask(__name__)
+
+ def add_x_parachute(f):
+ def new_function(*args, **kwargs):
+ resp = flask.make_response(f(*args, **kwargs))
+ resp.headers['X-Parachute'] = 'awesome'
+ return resp
+ return new_function
+
+ class Index(flask.views.View):
+ decorators = [add_x_parachute]
+ def dispatch_request(self):
+ return 'Awesome'
+
+ app.add_url_rule('/', view_func=Index.as_view('index'))
+ c = app.test_client()
+ rv = c.get('/')
+ self.assert_equal(rv.headers['X-Parachute'], 'awesome')
+ self.assert_equal(rv.data, 'Awesome')
+
+ def test_implicit_head(self):
+ app = flask.Flask(__name__)
+
+ class Index(flask.views.MethodView):
+ def get(self):
+ return flask.Response('Blub', headers={
+ 'X-Method': flask.request.method
+ })
+
+ app.add_url_rule('/', view_func=Index.as_view('index'))
+ c = app.test_client()
+ rv = c.get('/')
+ self.assert_equal(rv.data, 'Blub')
+ self.assert_equal(rv.headers['X-Method'], 'GET')
+ rv = c.head('/')
+ self.assert_equal(rv.data, '')
+ self.assert_equal(rv.headers['X-Method'], 'HEAD')
+
+ def test_explicit_head(self):
+ app = flask.Flask(__name__)
+
+ class Index(flask.views.MethodView):
+ def get(self):
+ return 'GET'
+ def head(self):
+ return flask.Response('', headers={'X-Method': 'HEAD'})
+
+ app.add_url_rule('/', view_func=Index.as_view('index'))
+ c = app.test_client()
+ rv = c.get('/')
+ self.assert_equal(rv.data, 'GET')
+ rv = c.head('/')
+ self.assert_equal(rv.data, '')
+ self.assert_equal(rv.headers['X-Method'], 'HEAD')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ViewTestCase))
+ return suite
diff --git a/websdk/flask/views.py b/websdk/flask/views.py
new file mode 100644
index 0000000..be718cc
--- /dev/null
+++ b/websdk/flask/views.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.views
+ ~~~~~~~~~~~
+
+ This module provides class based views inspired by the ones in Django.
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+from .globals import request
+
+
+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
+ the URL routing system. If :attr:`methods` is provided the methods
+ do not have to be passed to the :meth:`~flask.Flask.add_url_rule`
+ method explicitly::
+
+ class MyView(View):
+ methods = ['GET']
+
+ def dispatch_request(self, name):
+ 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 function code. This method is called with all
+ the arguments from the URL rule.
+ """
+ raise NotImplementedError()
+
+ @classmethod
+ def as_view(cls, name, *class_args, **class_kwargs):
+ """Converts the class into an actual view function that can be
+ used with the routing system. What it does internally is generating
+ a function on the fly that will instanciate the :class:`View`
+ on each request and call the :meth:`dispatch_request` method on it.
+
+ The arguments passed to :meth:`as_view` are forwarded to the
+ constructor of the class.
+ """
+ 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
+ # the view class so you can actually replace it with something else
+ # for testing purposes and debugging.
+ view.view_class = cls
+ view.__name__ = name
+ view.__doc__ = cls.__doc__
+ view.__module__ = cls.__module__
+ view.methods = cls.methods
+ return view
+
+
+class MethodViewType(type):
+
+ def __new__(cls, name, bases, d):
+ rv = type.__new__(cls, name, bases, d)
+ if 'methods' not in d:
+ methods = set(rv.methods or [])
+ for key, value in d.iteritems():
+ if key in http_method_funcs:
+ methods.add(key.upper())
+ # if we have no method at all in there we don't want to
+ # add a method list. (This is for instance the case for
+ # the baseclass or another subclass of a base method view
+ # that does not introduce new methods).
+ if methods:
+ rv.methods = sorted(methods)
+ return rv
+
+
+class MethodView(View):
+ """Like a regular class based view but that dispatches requests to
+ particular methods. For instance if you implement a method called
+ :meth:`get` it means you will response to ``'GET'`` requests and
+ the :meth:`dispatch_request` implementation will automatically
+ forward your request to that. Also :attr:`options` is set for you
+ automatically::
+
+ class CounterAPI(MethodView):
+
+ def get(self):
+ return session.get('counter', 0)
+
+ def post(self):
+ session['counter'] = session.get('counter', 0) + 1
+ return 'OK'
+
+ app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
+ """
+ __metaclass__ = MethodViewType
+
+ def dispatch_request(self, *args, **kwargs):
+ meth = getattr(self, request.method.lower(), None)
+ # 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)
diff --git a/websdk/flask/wrappers.py b/websdk/flask/wrappers.py
new file mode 100644
index 0000000..f6ec278
--- /dev/null
+++ b/websdk/flask/wrappers.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+"""
+ flask.wrappers
+ ~~~~~~~~~~~~~~
+
+ Implements the WSGI wrappers (request and response).
+
+ :copyright: (c) 2011 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
+from werkzeug.exceptions import BadRequest
+from werkzeug.utils import cached_property
+
+from .debughelpers import attach_enctype_error_multidict
+from .helpers import json, _assert_have_json
+from .globals import _request_ctx_stack
+
+
+class Request(RequestBase):
+ """The request object used by default in Flask. Remembers the
+ matched endpoint and view arguments.
+
+ It is what ends up as :class:`~flask.request`. If you want to replace
+ the request object used you can subclass this and set
+ :attr:`~flask.Flask.request_class` to your subclass.
+
+ The request object is a :class:`~werkzeug.wrappers.Request` subclass and
+ provides all of the attributes Werkzeug defines plus a few Flask
+ specific ones.
+ """
+
+ #: the internal URL rule that matched the request. This can be
+ #: useful to inspect which methods are allowed for the URL from
+ #: a before/after handler (``request.url_rule.methods``) etc.
+ #:
+ #: .. versionadded:: 0.6
+ url_rule = None
+
+ #: a dict of view arguments that matched the request. If an exception
+ #: happened when matching, this will be `None`.
+ view_args = None
+
+ #: if matching the URL failed, this is the exception that will be
+ #: raised / was raised as part of the request handling. This is
+ #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
+ #: something similar.
+ routing_exception = None
+
+ # switched by the request context until 1.0 to opt in deprecated
+ # module functionality
+ _is_old_module = False
+
+ @property
+ def max_content_length(self):
+ """Read-only view of the `MAX_CONTENT_LENGTH` config key."""
+ ctx = _request_ctx_stack.top
+ if ctx is not None:
+ return ctx.app.config['MAX_CONTENT_LENGTH']
+
+ @property
+ def endpoint(self):
+ """The endpoint that matched the request. This in combination with
+ :attr:`view_args` can be used to reconstruct the same or a
+ modified URL. If an exception happened when matching, this will
+ be `None`.
+ """
+ if self.url_rule is not None:
+ return self.url_rule.endpoint
+
+ @property
+ def module(self):
+ """The name of the current module if the request was dispatched
+ to an actual module. This is deprecated functionality, use blueprints
+ instead.
+ """
+ from warnings import warn
+ warn(DeprecationWarning('modules were deprecated in favor of '
+ 'blueprints. Use request.blueprint '
+ 'instead.'), stacklevel=2)
+ if self._is_old_module:
+ return self.blueprint
+
+ @property
+ def blueprint(self):
+ """The name of the current blueprint"""
+ if self.url_rule and '.' in self.url_rule.endpoint:
+ return self.url_rule.endpoint.rsplit('.', 1)[0]
+
+ @cached_property
+ def json(self):
+ """If the mimetype is `application/json` this will contain the
+ parsed JSON data. Otherwise this will be `None`.
+
+ This requires Python 2.6 or an installed version of simplejson.
+ """
+ if __debug__:
+ _assert_have_json()
+ if self.mimetype == 'application/json':
+ request_charset = self.mimetype_params.get('charset')
+ try:
+ if request_charset is not None:
+ return json.loads(self.data, encoding=request_charset)
+ return json.loads(self.data)
+ except ValueError, e:
+ return self.on_json_loading_failed(e)
+
+ def on_json_loading_failed(self, e):
+ """Called if decoding of the JSON data failed. The return value of
+ this method is used by :attr:`json` when an error ocurred. The
+ default implementation raises a :class:`~werkzeug.exceptions.BadRequest`.
+
+ .. versionadded:: 0.8
+ """
+ raise BadRequest()
+
+ def _load_form_data(self):
+ RequestBase._load_form_data(self)
+
+ # in debug mode we're replacing the files multidict with an ad-hoc
+ # subclass that raises a different error for key errors.
+ ctx = _request_ctx_stack.top
+ if ctx is not None and ctx.app.debug and \
+ self.mimetype != 'multipart/form-data' and not self.files:
+ attach_enctype_error_multidict(self)
+
+
+class Response(ResponseBase):
+ """The response object that is used by default in Flask. Works like the
+ response object from Werkzeug but is set to have an HTML mimetype by
+ default. Quite often you don't have to create this object yourself because
+ :meth:`~flask.Flask.make_response` will take care of that for you.
+
+ If you want to replace the response object used you can subclass this and
+ set :attr:`~flask.Flask.response_class` to your subclass.
+ """
+ default_mimetype = 'text/html'