Merge remote-tracking branch 'mitsuhiko/master'
This commit is contained in:
commit
a9e09ec50e
66 changed files with 3260 additions and 2430 deletions
11
CHANGES
11
CHANGES
|
|
@ -32,6 +32,17 @@ Relase date to be decided, codename to be chosen.
|
|||
conceptionally only instance depending and outside version control so it's
|
||||
the perfect place to put configuration files etc. For more information
|
||||
see :ref:`instance-folders`.
|
||||
- Added the ``APPLICATION_ROOT`` configuration variable.
|
||||
- Implemented :meth:`~flask.testing.TestClient.session_transaction` to
|
||||
easily modify sessions from the test environment.
|
||||
- Refactored test client internally. The ``APPLICATION_ROOT`` configuration
|
||||
variable as well as ``SERVER_NAME`` are now properly used by the test client
|
||||
as defaults.
|
||||
- Added :attr:`flask.views.View.decorators` to support simpler decorating of
|
||||
pluggable (class based) views.
|
||||
- Fixed an issue where the test client if used with the with statement did not
|
||||
trigger the execution of the teardown handlers.
|
||||
- Added finer control over the session cookie parameters.
|
||||
|
||||
Version 0.7.3
|
||||
-------------
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
include Makefile CHANGES LICENSE AUTHORS
|
||||
include Makefile CHANGES LICENSE AUTHORS run-tests.py
|
||||
recursive-include artwork *
|
||||
recursive-include tests *
|
||||
recursive-include examples *
|
||||
|
|
@ -9,5 +9,8 @@ recursive-exclude tests *.pyc
|
|||
recursive-exclude tests *.pyo
|
||||
recursive-exclude examples *.pyc
|
||||
recursive-exclude examples *.pyo
|
||||
recursive-include flask/testsuite/static *
|
||||
recursive-include flask/testsuite/templates *
|
||||
recursive-include flask/testsuite/test_apps *
|
||||
prune docs/_build
|
||||
prune docs/_themes/.git
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -3,7 +3,7 @@
|
|||
all: clean-pyc test
|
||||
|
||||
test:
|
||||
python setup.py test
|
||||
python run-tests.py
|
||||
|
||||
audit:
|
||||
python setup.py audit
|
||||
|
|
|
|||
33
README
33
README
|
|
@ -11,24 +11,41 @@
|
|||
|
||||
~ Is it ready?
|
||||
|
||||
A preview release is out now, and I'm hoping for some
|
||||
input about what you want from a microframework and
|
||||
how it should look like. Consider the API to slightly
|
||||
improve over time.
|
||||
It's still not 1.0 but it's shaping up nicely and is
|
||||
already widely used. Consider the API to slightly
|
||||
improve over time but we don't plan on breaking it.
|
||||
|
||||
~ What do I need?
|
||||
|
||||
Jinja 2.4 and Werkzeug 0.6.1. `easy_install` will
|
||||
install them for you if you do `easy_install Flask==dev`.
|
||||
Jinja 2.4 and Werkzeug 0.6.1. `pip` or `easy_install` will
|
||||
install them for you if you do `easy_install Flask`.
|
||||
I encourage you to use a virtualenv. Check the docs for
|
||||
complete installation and usage instructions.
|
||||
|
||||
~ Where are the docs?
|
||||
|
||||
Go to http://flask.pocoo.org/ for a prebuilt version of
|
||||
the current documentation. Otherwise build them yourself
|
||||
Go to http://flask.pocoo.org/docs/ for a prebuilt version
|
||||
of the current documentation. Otherwise build them yourself
|
||||
from the sphinx sources in the docs folder.
|
||||
|
||||
~ Where are the tests?
|
||||
|
||||
Good that you're asking. The tests are in the
|
||||
flask/testsuite package. To run the tests use the
|
||||
`run-tests.py` file:
|
||||
|
||||
$ python run-tests.py
|
||||
|
||||
If it's not enough output for you, you can use the
|
||||
`--verbose` flag:
|
||||
|
||||
$ python run-tests.py --verbose
|
||||
|
||||
If you just want one particular testcase to run you can
|
||||
provide it on the command line:
|
||||
|
||||
$ python run-tests.py test_to_run
|
||||
|
||||
~ Where can I get help?
|
||||
|
||||
Either use the #pocoo IRC channel on irc.freenode.net or
|
||||
|
|
|
|||
|
|
@ -218,6 +218,15 @@ implementation that Flask is using.
|
|||
:members:
|
||||
|
||||
|
||||
Test Client
|
||||
-----------
|
||||
|
||||
.. currentmodule:: flask.testing
|
||||
|
||||
.. autoclass:: FlaskClient
|
||||
:members:
|
||||
|
||||
|
||||
Application Globals
|
||||
-------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,20 @@ The following configuration values are used internally by Flask:
|
|||
very risky).
|
||||
``SECRET_KEY`` the secret key
|
||||
``SESSION_COOKIE_NAME`` the name of the session cookie
|
||||
``SESSION_COOKIE_DOMAIN`` the domain for the session cookie. If
|
||||
this is not set, the cookie will be
|
||||
valid for all subdomains of
|
||||
``SERVER_NAME``.
|
||||
``SESSION_COOKIE_PATH`` the path for the session cookie. If
|
||||
this is not set the cookie will be valid
|
||||
for all of ``APPLICATION_ROOT`` or if
|
||||
that is not set for ``'/'``.
|
||||
``SESSION_COOKIE_HTTPONLY`` controls if the cookie should be set
|
||||
with the httponly flag. Defaults to
|
||||
`True`.
|
||||
``SESSION_COOKIE_SECURE`` controls if the cookie should be set
|
||||
with the secure flag. Defaults to
|
||||
`False`.
|
||||
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
|
||||
:class:`datetime.timedelta` object.
|
||||
``USE_X_SENDFILE`` enable/disable x-sendfile
|
||||
|
|
@ -77,6 +91,13 @@ The following configuration values are used internally by Flask:
|
|||
``SERVER_NAME`` the name and port number of the server.
|
||||
Required for subdomain support (e.g.:
|
||||
``'localhost:5000'``)
|
||||
``APPLICATION_ROOT`` If the application does not occupy
|
||||
a whole domain or subdomain this can
|
||||
be set to the path where the application
|
||||
is configured to live. This is for
|
||||
session cookie as path value. If
|
||||
domains are used, this should be
|
||||
``None``.
|
||||
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
||||
reject incoming requests with a
|
||||
content length greater than this by
|
||||
|
|
@ -134,7 +155,10 @@ The following configuration values are used internally by Flask:
|
|||
``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION``
|
||||
|
||||
.. versionadded:: 0.8
|
||||
``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``
|
||||
``TRAP_BAD_REQUEST_ERRORS``, ``TRAP_HTTP_EXCEPTIONS``,
|
||||
``APPLICATION_ROOT``, ``SESSION_COOKIE_DOMAIN``,
|
||||
``SESSION_COOKIE_PATH``, ``SESSION_COOKIE_HTTPONLY``,
|
||||
``SESSION_COOKIE_SECURE``
|
||||
|
||||
Configuring from Files
|
||||
----------------------
|
||||
|
|
@ -291,25 +315,37 @@ With Flask 0.8 a new attribute was introduced:
|
|||
version control and be deployment specific. It's the perfect place to
|
||||
drop things that either change at runtime or configuration files.
|
||||
|
||||
To make it easier to put this folder into an ignore list for your version
|
||||
control system it's called ``instance`` and placed directly next to your
|
||||
package or module by default. This path can be overridden by specifying
|
||||
the `instance_path` parameter to your application::
|
||||
You can either explicitly provide the path of the instance folder when
|
||||
creating the Flask application or you can let Flask autodetect the
|
||||
instance folder. For explicit configuration use the `instance_path`
|
||||
parameter::
|
||||
|
||||
app = Flask(__name__, instance_path='/path/to/instance/folder')
|
||||
|
||||
Default locations::
|
||||
Please keep in mind that this path *must* be absolute when provided.
|
||||
|
||||
If the `instance_path` parameter is not provided the following default
|
||||
locations are used:
|
||||
|
||||
- Uninstalled module::
|
||||
|
||||
Module situation:
|
||||
/myapp.py
|
||||
/instance
|
||||
|
||||
Package situation:
|
||||
- Uninstalled package::
|
||||
|
||||
/myapp
|
||||
/__init__.py
|
||||
/instance
|
||||
|
||||
Please keep in mind that this path *must* be absolute when provided.
|
||||
- Installed module or package::
|
||||
|
||||
$PREFIX/lib/python2.X/site-packages/myapp
|
||||
$PREFIX/var/myapp-instance
|
||||
|
||||
``$PREFIX`` is the prefix of your Python installation. This can be
|
||||
``/usr`` or the path to your virtualenv. You can print the value of
|
||||
``sys.prefix`` to see what the prefix is set to.
|
||||
|
||||
Since the config object provided loading of configuration files from
|
||||
relative filenames we made it possible to change the loading via filenames
|
||||
|
|
|
|||
|
|
@ -79,6 +79,22 @@ Furthermore this design makes it possible to use a factory function to
|
|||
create the application which is very helpful for unittesting and similar
|
||||
things (:ref:`app-factories`).
|
||||
|
||||
The Routing System
|
||||
------------------
|
||||
|
||||
Flask uses the Werkzeug routing system which has was designed to
|
||||
automatically order routes by complexity. This means that you can declare
|
||||
routes in arbitrary order and they will still work as expected. This is a
|
||||
requirement if you want to properly implement decorator based routing
|
||||
since decorators could be fired in undefined order when the application is
|
||||
split into multiple modules.
|
||||
|
||||
Another design decision with the Werkzeug routing system is that routes
|
||||
in Werkzeug try to ensure that there is that URLs are unique. Werkzeug
|
||||
will go quite far with that in that it will automatically redirect to a
|
||||
canonical URL if a route is ambiguous.
|
||||
|
||||
|
||||
One Template Engine
|
||||
-------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -9,27 +9,30 @@ What does "micro" mean?
|
|||
-----------------------
|
||||
|
||||
To me, the "micro" in microframework refers not only to the simplicity and
|
||||
small size of the framework, but also to the typically limited complexity
|
||||
and size of applications that are written with the framework. Also the
|
||||
fact that you can have an entire application in a single Python file. To
|
||||
be approachable and concise, a microframework sacrifices a few features
|
||||
that may be necessary in larger or more complex applications.
|
||||
small size of the framework, but also the fact that it does not make much
|
||||
decisions for you. While Flask does pick a templating engine for you, we
|
||||
won't make such decisions for your datastore or other parts.
|
||||
|
||||
For example, Flask uses thread-local objects internally so that you don't
|
||||
have to pass objects around from function to function within a request in
|
||||
order to stay threadsafe. While this is a really easy approach and saves
|
||||
you a lot of time, it might also cause some troubles for very large
|
||||
applications because changes on these thread-local objects can happen
|
||||
anywhere in the same thread.
|
||||
For us however the term “micro” does not mean that the whole implementation
|
||||
has to fit into a single Python file.
|
||||
|
||||
Flask provides some tools to deal with the downsides of this approach but
|
||||
it might be an issue for larger applications because in theory
|
||||
modifications on these objects might happen anywhere in the same thread.
|
||||
One of the design decisions with Flask was that simple tasks should be
|
||||
simple and not take up a lot of code and yet not limit yourself. Because
|
||||
of that we took a few design choices that some people might find
|
||||
surprising or unorthodox. For example, Flask uses thread-local objects
|
||||
internally so that you don't have to pass objects around from function to
|
||||
function within a request in order to stay threadsafe. While this is a
|
||||
really easy approach and saves you a lot of time, it might also cause some
|
||||
troubles for very large applications because changes on these thread-local
|
||||
objects can happen anywhere in the same thread. In order to solve these
|
||||
problems we don't hide the thread locals for you but instead embrace them
|
||||
and provide you with a lot of tools to make it as pleasant as possible to
|
||||
work with them.
|
||||
|
||||
Flask is also based on convention over configuration, which means that
|
||||
many things are preconfigured. For example, by convention, templates and
|
||||
static files are in subdirectories within the Python source tree of the
|
||||
application.
|
||||
application. While this can be changed you usually don't have to.
|
||||
|
||||
The main reason however why Flask is called a "microframework" is the idea
|
||||
to keep the core simple but extensible. There is no database abstraction
|
||||
|
|
@ -40,22 +43,15 @@ was implemented in Flask itself. There are currently extensions for
|
|||
object relational mappers, form validation, upload handling, various open
|
||||
authentication technologies and more.
|
||||
|
||||
However Flask is not much code and it is built on a very solid foundation
|
||||
and with that it is very easy to adapt for large applications. If you are
|
||||
interested in that, check out the :ref:`becomingbig` chapter.
|
||||
Since Flask is based on a very solid foundation there is not a lot of code
|
||||
in Flask itself. As such it's easy to adapt even for lage applications
|
||||
and we are making sure that you can either configure it as much as
|
||||
possible by subclassing things or by forking the entire codebase. If you
|
||||
are interested in that, check out the :ref:`becomingbig` chapter.
|
||||
|
||||
If you are curious about the Flask design principles, head over to the
|
||||
section about :ref:`design`.
|
||||
|
||||
A Framework and an Example
|
||||
--------------------------
|
||||
|
||||
Flask is not only a microframework; it is also an example. Based on
|
||||
Flask, there will be a series of blog posts that explain how to create a
|
||||
framework. Flask itself is just one way to implement a framework on top
|
||||
of existing libraries. Unlike many other microframeworks, Flask does not
|
||||
try to implement everything on its own; it reuses existing code.
|
||||
|
||||
Web Development is Dangerous
|
||||
----------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,15 @@ So here is a simple example of how you can use SQLite 3 with Flask::
|
|||
|
||||
@app.teardown_request
|
||||
def teardown_request(exception):
|
||||
g.db.close()
|
||||
if hasattr(g, 'db'):
|
||||
g.db.close()
|
||||
|
||||
.. note::
|
||||
|
||||
Please keep in mind that the teardown request functions are always
|
||||
executed, even if a before-request handler failed or was never
|
||||
executed. Because of this we have to make sure here that the database
|
||||
is there before we close it.
|
||||
|
||||
Connect on Demand
|
||||
-----------------
|
||||
|
|
|
|||
|
|
@ -131,7 +131,9 @@ understand what is actually happening. The new behavior is quite simple:
|
|||
|
||||
4. At the end of the request the :meth:`~flask.Flask.teardown_request`
|
||||
functions are executed. This always happens, even in case of an
|
||||
unhandled exception down the road.
|
||||
unhandled exception down the road or if a before-request handler was
|
||||
not executed yet or at all (for example in test environments sometimes
|
||||
you might want to not execute before-request callbacks).
|
||||
|
||||
Now what happens on errors? In production mode if an exception is not
|
||||
caught, the 500 internal server handler is called. In development mode
|
||||
|
|
@ -183,6 +185,12 @@ It's easy to see the behavior from the command line:
|
|||
this runs after request
|
||||
>>>
|
||||
|
||||
Keep in mind that teardown callbacks are always executed, even if
|
||||
before-request callbacks were not executed yet but an exception happened.
|
||||
Certain parts of the test system might also temporarily create a request
|
||||
context without calling the before-request handlers. Make sure to write
|
||||
your teardown-request handlers in a way that they will never fail.
|
||||
|
||||
.. _notes-on-proxies:
|
||||
|
||||
Notes On Proxies
|
||||
|
|
|
|||
|
|
@ -273,3 +273,35 @@ is no longer available (because you are trying to use it outside of the actual r
|
|||
However, keep in mind that any :meth:`~flask.Flask.after_request` functions
|
||||
are already called at this point so your database connection and
|
||||
everything involved is probably already closed down.
|
||||
|
||||
|
||||
Accessing and Modifying Sessions
|
||||
--------------------------------
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
Sometimes it can be very helpful to access or modify the sessions from the
|
||||
test client. Generally there are two ways for this. If you just want to
|
||||
ensure that a session has certain keys set to certain values you can just
|
||||
keep the context around and access :data:`flask.session`::
|
||||
|
||||
with app.test_client() as c:
|
||||
rv = c.get('/')
|
||||
assert flask.session['foo'] == 42
|
||||
|
||||
This however does not make it possible to also modify the session or to
|
||||
access the session before a request was fired. Starting with Flask 0.8 we
|
||||
provide a so called “session transaction” which simulates the appropriate
|
||||
calls to open a session in the context of the test client and to modify
|
||||
it. At the end of the transaction the session is stored. This works
|
||||
independently of the session backend used::
|
||||
|
||||
with app.test_client() as c:
|
||||
with c.session_transaction() as sess:
|
||||
sess['a_key'] = 'a value'
|
||||
|
||||
# once this is reached the session was stored
|
||||
|
||||
Note that in this case you have to use the ``sess`` object instead of the
|
||||
:data:`flask.session` proxy. The object however itself will provide the
|
||||
same interface.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ longer have to handle that error to avoid an internal server error showing
|
|||
up for the user. If you were catching this down explicitly in the past
|
||||
as `ValueError` you will need to change this.
|
||||
|
||||
Due to a bug in the test client Flask 0.7 did not trigger teardown
|
||||
handlers when the test client was used in a with statement. This was
|
||||
since fixed but might require some changes in your testsuites if you
|
||||
relied on this behavior.
|
||||
|
||||
Version 0.7
|
||||
-----------
|
||||
|
||||
|
|
@ -142,7 +147,8 @@ You are now encouraged to use this instead::
|
|||
|
||||
@app.teardown_request
|
||||
def after_request(exception):
|
||||
g.db.close()
|
||||
if hasattr(g, 'db'):
|
||||
g.db.close()
|
||||
|
||||
On the upside this change greatly improves the internal code flow and
|
||||
makes it easier to customize the dispatching and error handling. This
|
||||
|
|
|
|||
|
|
@ -135,3 +135,24 @@ easily do that. Each HTTP method maps to a function with the same name
|
|||
That way you also don't have to provide the
|
||||
:attr:`~flask.views.View.methods` attribute. It's automatically set based
|
||||
on the methods defined in the class.
|
||||
|
||||
Decorating Views
|
||||
----------------
|
||||
|
||||
Since the view class itself is not the view function that is added to the
|
||||
routing system it does not make much sense to decorate the class itself.
|
||||
Instead you either have to decorate the return value of
|
||||
:meth:`~flask.views.View.as_view` by hand::
|
||||
|
||||
view = rate_limited(UserAPI.as_view('users'))
|
||||
app.add_url_rule('/users/', view_func=view)
|
||||
|
||||
Starting with Flask 0.8 there is also an alternative way where you can
|
||||
specify a list of decorators to apply in the class declaration::
|
||||
|
||||
class UserAPI(MethodView):
|
||||
decorators = [rate_limited]
|
||||
|
||||
Due to the implicit self from the caller's perspective you cannot use
|
||||
regular view decorators on the individual methods of the view however,
|
||||
keep this in mind.
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ def before_request():
|
|||
@app.teardown_request
|
||||
def teardown_request(exception):
|
||||
"""Closes the database again at the end of the request."""
|
||||
g.db.close()
|
||||
if hasattr(g, 'db'):
|
||||
g.db.close()
|
||||
|
||||
|
||||
@app.route('/')
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@ def before_request():
|
|||
@app.teardown_request
|
||||
def teardown_request(exception):
|
||||
"""Closes the database again at the end of the request."""
|
||||
g.db.close()
|
||||
if hasattr(g, 'db'):
|
||||
g.db.close()
|
||||
|
||||
|
||||
@app.route('/')
|
||||
|
|
|
|||
73
flask/app.py
73
flask/app.py
|
|
@ -231,11 +231,16 @@ class Flask(_PackageBoundObject):
|
|||
'PROPAGATE_EXCEPTIONS': None,
|
||||
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
|
||||
'SECRET_KEY': None,
|
||||
'SESSION_COOKIE_NAME': 'session',
|
||||
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
|
||||
'USE_X_SENDFILE': False,
|
||||
'LOGGER_NAME': None,
|
||||
'SERVER_NAME': None,
|
||||
'APPLICATION_ROOT': None,
|
||||
'SESSION_COOKIE_NAME': 'session',
|
||||
'SESSION_COOKIE_DOMAIN': None,
|
||||
'SESSION_COOKIE_PATH': None,
|
||||
'SESSION_COOKIE_HTTPONLY': True,
|
||||
'SESSION_COOKIE_SECURE': False,
|
||||
'MAX_CONTENT_LENGTH': None,
|
||||
'TRAP_BAD_REQUEST_ERRORS': False,
|
||||
'TRAP_HTTP_EXCEPTIONS': False
|
||||
|
|
@ -705,6 +710,8 @@ class Flask(_PackageBoundObject):
|
|||
rv = c.get('/?vodka=42')
|
||||
assert request.args['vodka'] == '42'
|
||||
|
||||
See :class:`~flask.testing.FlaskClient` for more information.
|
||||
|
||||
.. versionchanged:: 0.4
|
||||
added support for `with` block usage for the client.
|
||||
|
||||
|
|
@ -1217,14 +1224,24 @@ class Flask(_PackageBoundObject):
|
|||
else:
|
||||
raise e
|
||||
|
||||
self.logger.exception('Exception on %s [%s]' % (
|
||||
request.path,
|
||||
request.method
|
||||
))
|
||||
self.log_exception((exc_type, exc_value, tb))
|
||||
if handler is None:
|
||||
return InternalServerError()
|
||||
return handler(e)
|
||||
|
||||
def log_exception(self, exc_info):
|
||||
"""Logs an exception. This is called by :meth:`handle_exception`
|
||||
if debugging is disabled and right before the handler is called.
|
||||
The default implementation logs the exception as error on the
|
||||
:attr:`logger`.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
self.logger.error('Exception on %s [%s]' % (
|
||||
request.path,
|
||||
request.method
|
||||
), exc_info=exc_info)
|
||||
|
||||
def raise_routing_exception(self, request):
|
||||
"""Exceptions that are recording during routing are reraised with
|
||||
this method. During debug we are not reraising redirect requests
|
||||
|
|
@ -1306,17 +1323,18 @@ class Flask(_PackageBoundObject):
|
|||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
# This would be nicer in Werkzeug 0.7, which however currently
|
||||
# is not released. Werkzeug 0.7 provides a method called
|
||||
# allowed_methods() that returns all methods that are valid for
|
||||
# a given path.
|
||||
methods = []
|
||||
try:
|
||||
_request_ctx_stack.top.url_adapter.match(method='--')
|
||||
except MethodNotAllowed, e:
|
||||
methods = e.valid_methods
|
||||
except HTTPException, e:
|
||||
pass
|
||||
adapter = _request_ctx_stack.top.url_adapter
|
||||
if hasattr(adapter, 'allowed_methods'):
|
||||
methods = adapter.allowed_methods()
|
||||
else:
|
||||
# fallback for Werkzeug < 0.7
|
||||
methods = []
|
||||
try:
|
||||
adapter.match(method='--')
|
||||
except MethodNotAllowed, e:
|
||||
methods = e.valid_methods
|
||||
except HTTPException, e:
|
||||
pass
|
||||
rv = self.response_class()
|
||||
rv.allow.update(methods)
|
||||
return rv
|
||||
|
|
@ -1387,7 +1405,7 @@ class Flask(_PackageBoundObject):
|
|||
This also triggers the :meth:`url_value_processor` functions before
|
||||
the actualy :meth:`before_request` functions are called.
|
||||
"""
|
||||
bp = request.blueprint
|
||||
bp = _request_ctx_stack.top.request.blueprint
|
||||
|
||||
funcs = self.url_value_preprocessors.get(None, ())
|
||||
if bp is not None and bp in self.url_value_preprocessors:
|
||||
|
|
@ -1437,7 +1455,7 @@ class Flask(_PackageBoundObject):
|
|||
tighter control over certain resources under testing environments.
|
||||
"""
|
||||
funcs = reversed(self.teardown_request_funcs.get(None, ()))
|
||||
bp = request.blueprint
|
||||
bp = _request_ctx_stack.top.request.blueprint
|
||||
if bp is not None and bp in self.teardown_request_funcs:
|
||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
||||
exc = sys.exc_info()[1]
|
||||
|
|
@ -1482,19 +1500,12 @@ class Flask(_PackageBoundObject):
|
|||
:func:`werkzeug.test.EnvironBuilder` for more information, this
|
||||
function accepts the same arguments).
|
||||
"""
|
||||
from werkzeug.test import create_environ
|
||||
environ_overrides = kwargs.setdefault('environ_overrides', {})
|
||||
if self.config.get('SERVER_NAME'):
|
||||
server_name = self.config.get('SERVER_NAME')
|
||||
if ':' not in server_name:
|
||||
http_host, http_port = server_name, '80'
|
||||
else:
|
||||
http_host, http_port = server_name.split(':', 1)
|
||||
|
||||
environ_overrides.setdefault('SERVER_NAME', server_name)
|
||||
environ_overrides.setdefault('HTTP_HOST', server_name)
|
||||
environ_overrides.setdefault('SERVER_PORT', http_port)
|
||||
return self.request_context(create_environ(*args, **kwargs))
|
||||
from flask.testing import make_test_environ_builder
|
||||
builder = make_test_environ_builder(self, *args, **kwargs)
|
||||
try:
|
||||
return self.request_context(builder.get_environ())
|
||||
finally:
|
||||
builder.close()
|
||||
|
||||
def wsgi_app(self, environ, start_response):
|
||||
"""The actual WSGI application. This is not implemented in
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class RequestContext(object):
|
|||
|
||||
self.match_request()
|
||||
|
||||
# Support for deprecated functionality. This is doing away with
|
||||
# XXX: Support for deprecated functionality. This is doing away with
|
||||
# Flask 1.0
|
||||
blueprint = self.request.blueprint
|
||||
if blueprint is not None:
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ class FormDataRoutingRedirect(AssertionError):
|
|||
|
||||
buf.append(' Make sure to directly send your %s-request to this URL '
|
||||
'since we can\'t make browsers or HTTP clients redirect '
|
||||
'with form data.' % request.method)
|
||||
'with form data reliably or without user interaction.' %
|
||||
request.method)
|
||||
buf.append('\n\nNote: this exception is only raised in debug mode')
|
||||
AssertionError.__init__(self, ''.join(buf).encode('utf-8'))
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,13 @@ def make_response(*args):
|
|||
|
||||
response = make_response(render_template('not_found.html'), 404)
|
||||
|
||||
The other use case of this function is to force the return value of a
|
||||
view function into a response which is helpful with view
|
||||
decorators::
|
||||
|
||||
response = make_response(view_function())
|
||||
response.headers['X-Parachutes'] = 'parachutes are cool'
|
||||
|
||||
Internally this function does the following things:
|
||||
|
||||
- if no arguments are passed, it creates a new response argument
|
||||
|
|
@ -477,6 +484,8 @@ def get_root_path(import_name):
|
|||
directory = os.path.dirname(sys.modules[import_name].__file__)
|
||||
return os.path.abspath(directory)
|
||||
except AttributeError:
|
||||
# this is necessary in case we are running from the interactive
|
||||
# python shell. It will never be used for production code however
|
||||
return os.getcwd()
|
||||
|
||||
|
||||
|
|
@ -492,6 +501,7 @@ def find_package(import_name):
|
|||
root_mod = sys.modules[import_name.split('.')[0]]
|
||||
package_path = getattr(root_mod, '__file__', None)
|
||||
if package_path is None:
|
||||
# support for the interactive python shell
|
||||
package_path = os.getcwd()
|
||||
else:
|
||||
package_path = os.path.abspath(os.path.dirname(package_path))
|
||||
|
|
|
|||
|
|
@ -123,10 +123,34 @@ class SessionInterface(object):
|
|||
"""Helpful helper method that returns the cookie domain that should
|
||||
be used for the session cookie if session cookies are used.
|
||||
"""
|
||||
if app.config['SESSION_COOKIE_DOMAIN'] is not None:
|
||||
return app.config['SESSION_COOKIE_DOMAIN']
|
||||
if app.config['SERVER_NAME'] is not None:
|
||||
# chop of the port which is usually not supported by browsers
|
||||
return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0]
|
||||
|
||||
def get_cookie_path(self, app):
|
||||
"""Returns the path for which the cookie should be valid. The
|
||||
default implementation uses the value from the SESSION_COOKIE_PATH``
|
||||
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
||||
uses ``/`` if it's `None`.
|
||||
"""
|
||||
return app.config['SESSION_COOKIE_PATH'] or \
|
||||
app.config['APPLICATION_ROOT'] or '/'
|
||||
|
||||
def get_cookie_httponly(self, app):
|
||||
"""Returns True if the session cookie should be httponly. This
|
||||
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
|
||||
config var.
|
||||
"""
|
||||
return app.config['SESSION_COOKIE_HTTPONLY']
|
||||
|
||||
def get_cookie_secure(self, app):
|
||||
"""Returns True if the cookie should be secure. This currently
|
||||
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
|
||||
"""
|
||||
return app.config['SESSION_COOKIE_SECURE']
|
||||
|
||||
def get_expiration_time(self, app, session):
|
||||
"""A helper method that returns an expiration date for the session
|
||||
or `None` if the session is linked to the browser session. The
|
||||
|
|
@ -169,9 +193,13 @@ class SecureCookieSessionInterface(SessionInterface):
|
|||
def save_session(self, app, session, response):
|
||||
expires = self.get_expiration_time(app, session)
|
||||
domain = self.get_cookie_domain(app)
|
||||
path = self.get_cookie_path(app)
|
||||
httponly = self.get_cookie_httponly(app)
|
||||
secure = self.get_cookie_secure(app)
|
||||
if session.modified and not session:
|
||||
response.delete_cookie(app.session_cookie_name,
|
||||
response.delete_cookie(app.session_cookie_name, path=path,
|
||||
domain=domain)
|
||||
else:
|
||||
session.save_cookie(response, app.session_cookie_name,
|
||||
expires=expires, httponly=True, domain=domain)
|
||||
session.save_cookie(response, app.session_cookie_name, path=path,
|
||||
expires=expires, httponly=httponly,
|
||||
secure=secure, domain=domain)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ except ImportError:
|
|||
'not installed.')
|
||||
send = lambda *a, **kw: None
|
||||
connect = disconnect = has_receivers_for = receivers_for = \
|
||||
temporarily_connected_to = _fail
|
||||
temporarily_connected_to = connected_to = _fail
|
||||
del _fail
|
||||
|
||||
# the namespace for code signals. If you are not flask code, do
|
||||
|
|
|
|||
100
flask/testing.py
100
flask/testing.py
|
|
@ -10,44 +10,91 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
from werkzeug.test import Client, EnvironBuilder
|
||||
from flask import _request_ctx_stack
|
||||
|
||||
|
||||
def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
|
||||
"""Creates a new test builder with some application defaults thrown in."""
|
||||
http_host = app.config.get('SERVER_NAME')
|
||||
app_root = app.config.get('APPLICATION_ROOT')
|
||||
if base_url is None:
|
||||
base_url = 'http://%s/' % (http_host or 'localhost')
|
||||
if app_root:
|
||||
base_url += app_root.lstrip('/')
|
||||
return EnvironBuilder(path, base_url, *args, **kwargs)
|
||||
|
||||
|
||||
class FlaskClient(Client):
|
||||
"""Works like a regular Werkzeug test client but has some
|
||||
knowledge about how Flask works to defer the cleanup of the
|
||||
request context stack to the end of a with body when used
|
||||
in a with statement.
|
||||
"""Works like a regular Werkzeug test client but has some knowledge about
|
||||
how Flask works to defer the cleanup of the request context stack to the
|
||||
end of a with body when used in a with statement. For general information
|
||||
about how to use this class refer to :class:`werkzeug.test.Client`.
|
||||
|
||||
Basic usage is outlined in the :ref:`testing` chapter.
|
||||
"""
|
||||
|
||||
preserve_context = context_preserved = False
|
||||
|
||||
@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.pop('environ_overrides', {})
|
||||
self.cookie_jar.inject_wsgi(environ_overrides)
|
||||
outer_reqctx = _request_ctx_stack.top
|
||||
with app.test_request_context(*args, **kwargs) as c:
|
||||
sess = app.open_session(c.request)
|
||||
if sess is None:
|
||||
raise RuntimeError('Session backend did not open a session. '
|
||||
'Check the configuration')
|
||||
|
||||
# Since we have to open a new request context for the session
|
||||
# handling we want to make sure that we hide out own context
|
||||
# from the caller. By pushing the original request context
|
||||
# (or None) on top of this and popping it we get exactly that
|
||||
# behavior. It's important to not use the push and pop
|
||||
# methods of the actual request context object since that would
|
||||
# mean that cleanup handlers are called
|
||||
_request_ctx_stack.push(outer_reqctx)
|
||||
try:
|
||||
yield sess
|
||||
finally:
|
||||
_request_ctx_stack.pop()
|
||||
|
||||
resp = app.response_class()
|
||||
if not app.session_interface.is_null_session(sess):
|
||||
app.save_session(sess, resp)
|
||||
headers = resp.get_wsgi_headers(c.request.environ)
|
||||
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
if self.context_preserved:
|
||||
_request_ctx_stack.pop()
|
||||
self.context_preserved = False
|
||||
self._pop_reqctx_if_necessary()
|
||||
kwargs.setdefault('environ_overrides', {}) \
|
||||
['flask._preserve_context'] = self.preserve_context
|
||||
|
||||
as_tuple = kwargs.pop('as_tuple', False)
|
||||
buffered = kwargs.pop('buffered', False)
|
||||
follow_redirects = kwargs.pop('follow_redirects', False)
|
||||
builder = make_test_environ_builder(self.application, *args, **kwargs)
|
||||
|
||||
builder = EnvironBuilder(*args, **kwargs)
|
||||
|
||||
if self.application.config.get('SERVER_NAME'):
|
||||
server_name = self.application.config.get('SERVER_NAME')
|
||||
if ':' not in server_name:
|
||||
http_host, http_port = server_name, None
|
||||
else:
|
||||
http_host, http_port = server_name.split(':', 1)
|
||||
if builder.base_url == 'http://localhost/':
|
||||
# Default Generated Base URL
|
||||
if http_port != None:
|
||||
builder.host = http_host + ':' + http_port
|
||||
else:
|
||||
builder.host = http_host
|
||||
old = _request_ctx_stack.top
|
||||
try:
|
||||
return Client.open(self, builder,
|
||||
|
|
@ -58,10 +105,19 @@ class FlaskClient(Client):
|
|||
self.context_preserved = _request_ctx_stack.top is not old
|
||||
|
||||
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
|
||||
self._pop_reqctx_if_necessary()
|
||||
|
||||
def _pop_reqctx_if_necessary(self):
|
||||
if self.context_preserved:
|
||||
_request_ctx_stack.pop()
|
||||
# we have to use _request_ctx_stack.top.pop instead of
|
||||
# _request_ctx_stack.pop since we want teardown handlers
|
||||
# to be executed.
|
||||
_request_ctx_stack.top.pop()
|
||||
self.context_preserved = False
|
||||
|
|
|
|||
194
flask/testsuite/__init__.py
Normal file
194
flask/testsuite/__init__.py
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
# -*- 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.
|
||||
"""
|
||||
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_info if it's not already there."""
|
||||
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
|
||||
for entry in sys.path:
|
||||
try:
|
||||
if os.path.samefile(path, entry):
|
||||
return
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
sys.path.append(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)
|
||||
|
||||
|
||||
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
|
||||
1051
flask/testsuite/basic.py
Normal file
1051
flask/testsuite/basic.py
Normal file
File diff suppressed because it is too large
Load diff
509
flask/testsuite/blueprints.py
Normal file
509
flask/testsuite/blueprints.py
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.testsuite.blueprints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Blueprints (and currently modules)
|
||||
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
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
|
||||
177
flask/testsuite/config.py
Normal file
177
flask/testsuite/config.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
# -*- 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))
|
||||
|
||||
|
||||
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
|
||||
38
flask/testsuite/deprecations.py
Normal file
38
flask/testsuite/deprecations.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.testsuite.deprecations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tests deprecation support.
|
||||
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
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
|
||||
38
flask/testsuite/examples.py
Normal file
38
flask/testsuite/examples.py
Normal file
|
|
@ -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
|
||||
295
flask/testsuite/helpers.py
Normal file
295
flask/testsuite/helpers.py
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.testsuite.helpers
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Various helpers.
|
||||
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
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
|
||||
103
flask/testsuite/signals.py
Normal file
103
flask/testsuite/signals.py
Normal file
|
|
@ -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
|
||||
141
flask/testsuite/templating.py
Normal file
141
flask/testsuite/templating.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.testsuite.templating
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Template functionality
|
||||
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
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, [
|
||||
'<p>Hello World!',
|
||||
'<p>Hello World!',
|
||||
'<p>Hello World!',
|
||||
'<p>Hello World!',
|
||||
'<p>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
|
||||
4
flask/testsuite/test_apps/config_module_app.py
Normal file
4
flask/testsuite/test_apps/config_module_app.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import os
|
||||
import flask
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
app = flask.Flask(__name__)
|
||||
4
flask/testsuite/test_apps/config_package_app/__init__.py
Normal file
4
flask/testsuite/test_apps/config_package_app/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import os
|
||||
import flask
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
app = flask.Flask(__name__)
|
||||
165
flask/testsuite/testing.py
Normal file
165
flask/testsuite/testing.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.testsuite.testing
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Test client and more.
|
||||
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
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]')
|
||||
|
||||
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
|
||||
117
flask/testsuite/views.py
Normal file
117
flask/testsuite/views.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# -*- 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 suite():
|
||||
suite = unittest.TestSuite()
|
||||
suite.addTest(unittest.makeSuite(ViewTestCase))
|
||||
return suite
|
||||
|
|
@ -30,10 +30,38 @@ class View(object):
|
|||
return 'Hello %s!' % name
|
||||
|
||||
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
|
||||
|
||||
When you want to decorate a pluggable view you will have to either do that
|
||||
when the view function is created (by wrapping the return value of
|
||||
:meth:`as_view`) or you can use the :attr:`decorators` attribute::
|
||||
|
||||
class SecretView(View):
|
||||
methods = ['GET']
|
||||
decorators = [superuser_required]
|
||||
|
||||
def dispatch_request(self):
|
||||
...
|
||||
|
||||
The decorators stored in the decorators list are applied one after another
|
||||
when the view function is created. Note that you can *not* use the class
|
||||
based decorators since those would decorate the view class and not the
|
||||
generated view function!
|
||||
"""
|
||||
|
||||
#: A for which methods this pluggable view can handle.
|
||||
methods = None
|
||||
|
||||
#: The canonical way to decorate class based views is to decorate the
|
||||
#: return value of as_view(). However since this moves parts of the
|
||||
#: logic from the class declaration to the place where it's hooked
|
||||
#: into the routing system.
|
||||
#:
|
||||
#: You can place one or more decorators in this list and whenever the
|
||||
#: view function is created the result is automatically decorated.
|
||||
#:
|
||||
#: .. versionadded:: 0.8
|
||||
decorators = []
|
||||
|
||||
def dispatch_request(self):
|
||||
"""Subclasses have to override this method to implement the
|
||||
actual view functionc ode. This method is called with all
|
||||
|
|
@ -54,6 +82,13 @@ class View(object):
|
|||
def view(*args, **kwargs):
|
||||
self = view.view_class(*class_args, **class_kwargs)
|
||||
return self.dispatch_request(*args, **kwargs)
|
||||
|
||||
if cls.decorators:
|
||||
view.__name__ = name
|
||||
view.__module__ = cls.__module__
|
||||
for decorator in cls.decorators:
|
||||
view = decorator(view)
|
||||
|
||||
# we attach the view class to the view function for two reasons:
|
||||
# first of all it allows us to easily figure out what class based
|
||||
# view this thing came from, secondly it's also used for instanciating
|
||||
|
|
|
|||
3
run-tests.py
Normal file
3
run-tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env python
|
||||
from flask.testsuite import main
|
||||
main()
|
||||
13
setup.py
13
setup.py
|
|
@ -77,12 +77,6 @@ class run_audit(Command):
|
|||
else:
|
||||
print ("No problems found in sourcecode.")
|
||||
|
||||
def run_tests():
|
||||
import os, sys
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'tests'))
|
||||
from flask_tests import suite
|
||||
return suite()
|
||||
|
||||
|
||||
setup(
|
||||
name='Flask',
|
||||
|
|
@ -94,7 +88,10 @@ setup(
|
|||
description='A microframework based on Werkzeug, Jinja2 '
|
||||
'and good intentions',
|
||||
long_description=__doc__,
|
||||
packages=['flask'],
|
||||
packages=['flask', 'flask.testsuite'],
|
||||
package_data={
|
||||
'flask.testsuite': ['test_apps/*', 'static/*', 'templates/*']
|
||||
},
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
install_requires=[
|
||||
|
|
@ -112,5 +109,5 @@ setup(
|
|||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
],
|
||||
cmdclass={'audit': run_audit},
|
||||
test_suite='__main__.run_tests'
|
||||
test_suite='flask.testsuite.suite'
|
||||
)
|
||||
|
|
|
|||
2312
tests/flask_tests.py
2312
tests/flask_tests.py
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue