forked from orbit-oss/flask
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
|
conceptionally only instance depending and outside version control so it's
|
||||||
the perfect place to put configuration files etc. For more information
|
the perfect place to put configuration files etc. For more information
|
||||||
see :ref:`instance-folders`.
|
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
|
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 artwork *
|
||||||
recursive-include tests *
|
recursive-include tests *
|
||||||
recursive-include examples *
|
recursive-include examples *
|
||||||
|
|
@ -9,5 +9,8 @@ recursive-exclude tests *.pyc
|
||||||
recursive-exclude tests *.pyo
|
recursive-exclude tests *.pyo
|
||||||
recursive-exclude examples *.pyc
|
recursive-exclude examples *.pyc
|
||||||
recursive-exclude examples *.pyo
|
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/_build
|
||||||
prune docs/_themes/.git
|
prune docs/_themes/.git
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -3,7 +3,7 @@
|
||||||
all: clean-pyc test
|
all: clean-pyc test
|
||||||
|
|
||||||
test:
|
test:
|
||||||
python setup.py test
|
python run-tests.py
|
||||||
|
|
||||||
audit:
|
audit:
|
||||||
python setup.py audit
|
python setup.py audit
|
||||||
|
|
|
||||||
33
README
33
README
|
|
@ -11,24 +11,41 @@
|
||||||
|
|
||||||
~ Is it ready?
|
~ Is it ready?
|
||||||
|
|
||||||
A preview release is out now, and I'm hoping for some
|
It's still not 1.0 but it's shaping up nicely and is
|
||||||
input about what you want from a microframework and
|
already widely used. Consider the API to slightly
|
||||||
how it should look like. Consider the API to slightly
|
improve over time but we don't plan on breaking it.
|
||||||
improve over time.
|
|
||||||
|
|
||||||
~ What do I need?
|
~ What do I need?
|
||||||
|
|
||||||
Jinja 2.4 and Werkzeug 0.6.1. `easy_install` will
|
Jinja 2.4 and Werkzeug 0.6.1. `pip` or `easy_install` will
|
||||||
install them for you if you do `easy_install Flask==dev`.
|
install them for you if you do `easy_install Flask`.
|
||||||
I encourage you to use a virtualenv. Check the docs for
|
I encourage you to use a virtualenv. Check the docs for
|
||||||
complete installation and usage instructions.
|
complete installation and usage instructions.
|
||||||
|
|
||||||
~ Where are the docs?
|
~ Where are the docs?
|
||||||
|
|
||||||
Go to http://flask.pocoo.org/ for a prebuilt version of
|
Go to http://flask.pocoo.org/docs/ for a prebuilt version
|
||||||
the current documentation. Otherwise build them yourself
|
of the current documentation. Otherwise build them yourself
|
||||||
from the sphinx sources in the docs folder.
|
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?
|
~ Where can I get help?
|
||||||
|
|
||||||
Either use the #pocoo IRC channel on irc.freenode.net or
|
Either use the #pocoo IRC channel on irc.freenode.net or
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,15 @@ implementation that Flask is using.
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
Test Client
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. currentmodule:: flask.testing
|
||||||
|
|
||||||
|
.. autoclass:: FlaskClient
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Application Globals
|
Application Globals
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,20 @@ The following configuration values are used internally by Flask:
|
||||||
very risky).
|
very risky).
|
||||||
``SECRET_KEY`` the secret key
|
``SECRET_KEY`` the secret key
|
||||||
``SESSION_COOKIE_NAME`` the name of the session cookie
|
``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
|
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
|
||||||
:class:`datetime.timedelta` object.
|
:class:`datetime.timedelta` object.
|
||||||
``USE_X_SENDFILE`` enable/disable x-sendfile
|
``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.
|
``SERVER_NAME`` the name and port number of the server.
|
||||||
Required for subdomain support (e.g.:
|
Required for subdomain support (e.g.:
|
||||||
``'localhost:5000'``)
|
``'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
|
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
||||||
reject incoming requests with a
|
reject incoming requests with a
|
||||||
content length greater than this by
|
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``
|
``PROPAGATE_EXCEPTIONS``, ``PRESERVE_CONTEXT_ON_EXCEPTION``
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
.. 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
|
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
|
version control and be deployment specific. It's the perfect place to
|
||||||
drop things that either change at runtime or configuration files.
|
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
|
You can either explicitly provide the path of the instance folder when
|
||||||
control system it's called ``instance`` and placed directly next to your
|
creating the Flask application or you can let Flask autodetect the
|
||||||
package or module by default. This path can be overridden by specifying
|
instance folder. For explicit configuration use the `instance_path`
|
||||||
the `instance_path` parameter to your application::
|
parameter::
|
||||||
|
|
||||||
app = Flask(__name__, instance_path='/path/to/instance/folder')
|
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
|
/myapp.py
|
||||||
/instance
|
/instance
|
||||||
|
|
||||||
Package situation:
|
- Uninstalled package::
|
||||||
|
|
||||||
/myapp
|
/myapp
|
||||||
/__init__.py
|
/__init__.py
|
||||||
/instance
|
/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
|
Since the config object provided loading of configuration files from
|
||||||
relative filenames we made it possible to change the loading via filenames
|
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
|
create the application which is very helpful for unittesting and similar
|
||||||
things (:ref:`app-factories`).
|
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
|
One Template Engine
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,27 +9,30 @@ What does "micro" mean?
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
To me, the "micro" in microframework refers not only to the simplicity and
|
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
|
small size of the framework, but also the fact that it does not make much
|
||||||
and size of applications that are written with the framework. Also the
|
decisions for you. While Flask does pick a templating engine for you, we
|
||||||
fact that you can have an entire application in a single Python file. To
|
won't make such decisions for your datastore or other parts.
|
||||||
be approachable and concise, a microframework sacrifices a few features
|
|
||||||
that may be necessary in larger or more complex applications.
|
|
||||||
|
|
||||||
For example, Flask uses thread-local objects internally so that you don't
|
For us however the term “micro” does not mean that the whole implementation
|
||||||
have to pass objects around from function to function within a request in
|
has to fit into a single Python file.
|
||||||
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.
|
|
||||||
|
|
||||||
Flask provides some tools to deal with the downsides of this approach but
|
One of the design decisions with Flask was that simple tasks should be
|
||||||
it might be an issue for larger applications because in theory
|
simple and not take up a lot of code and yet not limit yourself. Because
|
||||||
modifications on these objects might happen anywhere in the same thread.
|
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
|
Flask is also based on convention over configuration, which means that
|
||||||
many things are preconfigured. For example, by convention, templates and
|
many things are preconfigured. For example, by convention, templates and
|
||||||
static files are in subdirectories within the Python source tree of the
|
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
|
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
|
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
|
object relational mappers, form validation, upload handling, various open
|
||||||
authentication technologies and more.
|
authentication technologies and more.
|
||||||
|
|
||||||
However Flask is not much code and it is built on a very solid foundation
|
Since Flask is based on a very solid foundation there is not a lot of code
|
||||||
and with that it is very easy to adapt for large applications. If you are
|
in Flask itself. As such it's easy to adapt even for lage applications
|
||||||
interested in that, check out the :ref:`becomingbig` chapter.
|
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
|
If you are curious about the Flask design principles, head over to the
|
||||||
section about :ref:`design`.
|
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
|
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
|
@app.teardown_request
|
||||||
def teardown_request(exception):
|
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
|
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`
|
4. At the end of the request the :meth:`~flask.Flask.teardown_request`
|
||||||
functions are executed. This always happens, even in case of an
|
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
|
Now what happens on errors? In production mode if an exception is not
|
||||||
caught, the 500 internal server handler is called. In development mode
|
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
|
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:
|
||||||
|
|
||||||
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
|
However, keep in mind that any :meth:`~flask.Flask.after_request` functions
|
||||||
are already called at this point so your database connection and
|
are already called at this point so your database connection and
|
||||||
everything involved is probably already closed down.
|
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
|
up for the user. If you were catching this down explicitly in the past
|
||||||
as `ValueError` you will need to change this.
|
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
|
Version 0.7
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
@ -142,7 +147,8 @@ You are now encouraged to use this instead::
|
||||||
|
|
||||||
@app.teardown_request
|
@app.teardown_request
|
||||||
def after_request(exception):
|
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
|
On the upside this change greatly improves the internal code flow and
|
||||||
makes it easier to customize the dispatching and error handling. This
|
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
|
That way you also don't have to provide the
|
||||||
:attr:`~flask.views.View.methods` attribute. It's automatically set based
|
:attr:`~flask.views.View.methods` attribute. It's automatically set based
|
||||||
on the methods defined in the class.
|
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
|
@app.teardown_request
|
||||||
def teardown_request(exception):
|
def teardown_request(exception):
|
||||||
"""Closes the database again at the end of the request."""
|
"""Closes the database again at the end of the request."""
|
||||||
g.db.close()
|
if hasattr(g, 'db'):
|
||||||
|
g.db.close()
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,8 @@ def before_request():
|
||||||
@app.teardown_request
|
@app.teardown_request
|
||||||
def teardown_request(exception):
|
def teardown_request(exception):
|
||||||
"""Closes the database again at the end of the request."""
|
"""Closes the database again at the end of the request."""
|
||||||
g.db.close()
|
if hasattr(g, 'db'):
|
||||||
|
g.db.close()
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
|
|
||||||
73
flask/app.py
73
flask/app.py
|
|
@ -231,11 +231,16 @@ class Flask(_PackageBoundObject):
|
||||||
'PROPAGATE_EXCEPTIONS': None,
|
'PROPAGATE_EXCEPTIONS': None,
|
||||||
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
|
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
|
||||||
'SECRET_KEY': None,
|
'SECRET_KEY': None,
|
||||||
'SESSION_COOKIE_NAME': 'session',
|
|
||||||
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
|
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
|
||||||
'USE_X_SENDFILE': False,
|
'USE_X_SENDFILE': False,
|
||||||
'LOGGER_NAME': None,
|
'LOGGER_NAME': None,
|
||||||
'SERVER_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,
|
'MAX_CONTENT_LENGTH': None,
|
||||||
'TRAP_BAD_REQUEST_ERRORS': False,
|
'TRAP_BAD_REQUEST_ERRORS': False,
|
||||||
'TRAP_HTTP_EXCEPTIONS': False
|
'TRAP_HTTP_EXCEPTIONS': False
|
||||||
|
|
@ -705,6 +710,8 @@ class Flask(_PackageBoundObject):
|
||||||
rv = c.get('/?vodka=42')
|
rv = c.get('/?vodka=42')
|
||||||
assert request.args['vodka'] == '42'
|
assert request.args['vodka'] == '42'
|
||||||
|
|
||||||
|
See :class:`~flask.testing.FlaskClient` for more information.
|
||||||
|
|
||||||
.. versionchanged:: 0.4
|
.. versionchanged:: 0.4
|
||||||
added support for `with` block usage for the client.
|
added support for `with` block usage for the client.
|
||||||
|
|
||||||
|
|
@ -1217,14 +1224,24 @@ class Flask(_PackageBoundObject):
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
self.logger.exception('Exception on %s [%s]' % (
|
self.log_exception((exc_type, exc_value, tb))
|
||||||
request.path,
|
|
||||||
request.method
|
|
||||||
))
|
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return InternalServerError()
|
return InternalServerError()
|
||||||
return handler(e)
|
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):
|
def raise_routing_exception(self, request):
|
||||||
"""Exceptions that are recording during routing are reraised with
|
"""Exceptions that are recording during routing are reraised with
|
||||||
this method. During debug we are not reraising redirect requests
|
this method. During debug we are not reraising redirect requests
|
||||||
|
|
@ -1306,17 +1323,18 @@ class Flask(_PackageBoundObject):
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
.. versionadded:: 0.7
|
||||||
"""
|
"""
|
||||||
# This would be nicer in Werkzeug 0.7, which however currently
|
adapter = _request_ctx_stack.top.url_adapter
|
||||||
# is not released. Werkzeug 0.7 provides a method called
|
if hasattr(adapter, 'allowed_methods'):
|
||||||
# allowed_methods() that returns all methods that are valid for
|
methods = adapter.allowed_methods()
|
||||||
# a given path.
|
else:
|
||||||
methods = []
|
# fallback for Werkzeug < 0.7
|
||||||
try:
|
methods = []
|
||||||
_request_ctx_stack.top.url_adapter.match(method='--')
|
try:
|
||||||
except MethodNotAllowed, e:
|
adapter.match(method='--')
|
||||||
methods = e.valid_methods
|
except MethodNotAllowed, e:
|
||||||
except HTTPException, e:
|
methods = e.valid_methods
|
||||||
pass
|
except HTTPException, e:
|
||||||
|
pass
|
||||||
rv = self.response_class()
|
rv = self.response_class()
|
||||||
rv.allow.update(methods)
|
rv.allow.update(methods)
|
||||||
return rv
|
return rv
|
||||||
|
|
@ -1387,7 +1405,7 @@ class Flask(_PackageBoundObject):
|
||||||
This also triggers the :meth:`url_value_processor` functions before
|
This also triggers the :meth:`url_value_processor` functions before
|
||||||
the actualy :meth:`before_request` functions are called.
|
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, ())
|
funcs = self.url_value_preprocessors.get(None, ())
|
||||||
if bp is not None and bp in self.url_value_preprocessors:
|
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.
|
tighter control over certain resources under testing environments.
|
||||||
"""
|
"""
|
||||||
funcs = reversed(self.teardown_request_funcs.get(None, ()))
|
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:
|
if bp is not None and bp in self.teardown_request_funcs:
|
||||||
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
funcs = chain(funcs, reversed(self.teardown_request_funcs[bp]))
|
||||||
exc = sys.exc_info()[1]
|
exc = sys.exc_info()[1]
|
||||||
|
|
@ -1482,19 +1500,12 @@ class Flask(_PackageBoundObject):
|
||||||
:func:`werkzeug.test.EnvironBuilder` for more information, this
|
:func:`werkzeug.test.EnvironBuilder` for more information, this
|
||||||
function accepts the same arguments).
|
function accepts the same arguments).
|
||||||
"""
|
"""
|
||||||
from werkzeug.test import create_environ
|
from flask.testing import make_test_environ_builder
|
||||||
environ_overrides = kwargs.setdefault('environ_overrides', {})
|
builder = make_test_environ_builder(self, *args, **kwargs)
|
||||||
if self.config.get('SERVER_NAME'):
|
try:
|
||||||
server_name = self.config.get('SERVER_NAME')
|
return self.request_context(builder.get_environ())
|
||||||
if ':' not in server_name:
|
finally:
|
||||||
http_host, http_port = server_name, '80'
|
builder.close()
|
||||||
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))
|
|
||||||
|
|
||||||
def wsgi_app(self, environ, start_response):
|
def wsgi_app(self, environ, start_response):
|
||||||
"""The actual WSGI application. This is not implemented in
|
"""The actual WSGI application. This is not implemented in
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ class RequestContext(object):
|
||||||
|
|
||||||
self.match_request()
|
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
|
# Flask 1.0
|
||||||
blueprint = self.request.blueprint
|
blueprint = self.request.blueprint
|
||||||
if blueprint is not None:
|
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 '
|
buf.append(' Make sure to directly send your %s-request to this URL '
|
||||||
'since we can\'t make browsers or HTTP clients redirect '
|
'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')
|
buf.append('\n\nNote: this exception is only raised in debug mode')
|
||||||
AssertionError.__init__(self, ''.join(buf).encode('utf-8'))
|
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)
|
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:
|
Internally this function does the following things:
|
||||||
|
|
||||||
- if no arguments are passed, it creates a new response argument
|
- 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__)
|
directory = os.path.dirname(sys.modules[import_name].__file__)
|
||||||
return os.path.abspath(directory)
|
return os.path.abspath(directory)
|
||||||
except AttributeError:
|
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()
|
return os.getcwd()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -492,6 +501,7 @@ def find_package(import_name):
|
||||||
root_mod = sys.modules[import_name.split('.')[0]]
|
root_mod = sys.modules[import_name.split('.')[0]]
|
||||||
package_path = getattr(root_mod, '__file__', None)
|
package_path = getattr(root_mod, '__file__', None)
|
||||||
if package_path is None:
|
if package_path is None:
|
||||||
|
# support for the interactive python shell
|
||||||
package_path = os.getcwd()
|
package_path = os.getcwd()
|
||||||
else:
|
else:
|
||||||
package_path = os.path.abspath(os.path.dirname(package_path))
|
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
|
"""Helpful helper method that returns the cookie domain that should
|
||||||
be used for the session cookie if session cookies are used.
|
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:
|
if app.config['SERVER_NAME'] is not None:
|
||||||
# chop of the port which is usually not supported by browsers
|
# chop of the port which is usually not supported by browsers
|
||||||
return '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0]
|
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):
|
def get_expiration_time(self, app, session):
|
||||||
"""A helper method that returns an expiration date for the session
|
"""A helper method that returns an expiration date for the session
|
||||||
or `None` if the session is linked to the browser session. The
|
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):
|
def save_session(self, app, session, response):
|
||||||
expires = self.get_expiration_time(app, session)
|
expires = self.get_expiration_time(app, session)
|
||||||
domain = self.get_cookie_domain(app)
|
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:
|
if session.modified and not session:
|
||||||
response.delete_cookie(app.session_cookie_name,
|
response.delete_cookie(app.session_cookie_name, path=path,
|
||||||
domain=domain)
|
domain=domain)
|
||||||
else:
|
else:
|
||||||
session.save_cookie(response, app.session_cookie_name,
|
session.save_cookie(response, app.session_cookie_name, path=path,
|
||||||
expires=expires, httponly=True, domain=domain)
|
expires=expires, httponly=httponly,
|
||||||
|
secure=secure, domain=domain)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ except ImportError:
|
||||||
'not installed.')
|
'not installed.')
|
||||||
send = lambda *a, **kw: None
|
send = lambda *a, **kw: None
|
||||||
connect = disconnect = has_receivers_for = receivers_for = \
|
connect = disconnect = has_receivers_for = receivers_for = \
|
||||||
temporarily_connected_to = _fail
|
temporarily_connected_to = connected_to = _fail
|
||||||
del _fail
|
del _fail
|
||||||
|
|
||||||
# the namespace for code signals. If you are not flask code, do
|
# 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.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
from werkzeug.test import Client, EnvironBuilder
|
from werkzeug.test import Client, EnvironBuilder
|
||||||
from flask import _request_ctx_stack
|
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):
|
class FlaskClient(Client):
|
||||||
"""Works like a regular Werkzeug test client but has some
|
"""Works like a regular Werkzeug test client but has some knowledge about
|
||||||
knowledge about how Flask works to defer the cleanup of the
|
how Flask works to defer the cleanup of the request context stack to the
|
||||||
request context stack to the end of a with body when used
|
end of a with body when used in a with statement. For general information
|
||||||
in a with statement.
|
about how to use this class refer to :class:`werkzeug.test.Client`.
|
||||||
|
|
||||||
|
Basic usage is outlined in the :ref:`testing` chapter.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
preserve_context = context_preserved = False
|
preserve_context = 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):
|
def open(self, *args, **kwargs):
|
||||||
if self.context_preserved:
|
self._pop_reqctx_if_necessary()
|
||||||
_request_ctx_stack.pop()
|
|
||||||
self.context_preserved = False
|
|
||||||
kwargs.setdefault('environ_overrides', {}) \
|
kwargs.setdefault('environ_overrides', {}) \
|
||||||
['flask._preserve_context'] = self.preserve_context
|
['flask._preserve_context'] = self.preserve_context
|
||||||
|
|
||||||
as_tuple = kwargs.pop('as_tuple', False)
|
as_tuple = kwargs.pop('as_tuple', False)
|
||||||
buffered = kwargs.pop('buffered', False)
|
buffered = kwargs.pop('buffered', False)
|
||||||
follow_redirects = kwargs.pop('follow_redirects', 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
|
old = _request_ctx_stack.top
|
||||||
try:
|
try:
|
||||||
return Client.open(self, builder,
|
return Client.open(self, builder,
|
||||||
|
|
@ -58,10 +105,19 @@ class FlaskClient(Client):
|
||||||
self.context_preserved = _request_ctx_stack.top is not old
|
self.context_preserved = _request_ctx_stack.top is not old
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
if self.preserve_context:
|
||||||
|
raise RuntimeError('Cannot nest client invocations')
|
||||||
self.preserve_context = True
|
self.preserve_context = True
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
self.preserve_context = False
|
self.preserve_context = False
|
||||||
|
self._pop_reqctx_if_necessary()
|
||||||
|
|
||||||
|
def _pop_reqctx_if_necessary(self):
|
||||||
if self.context_preserved:
|
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
|
return 'Hello %s!' % name
|
||||||
|
|
||||||
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
|
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
|
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):
|
def dispatch_request(self):
|
||||||
"""Subclasses have to override this method to implement the
|
"""Subclasses have to override this method to implement the
|
||||||
actual view functionc ode. This method is called with all
|
actual view functionc ode. This method is called with all
|
||||||
|
|
@ -54,6 +82,13 @@ class View(object):
|
||||||
def view(*args, **kwargs):
|
def view(*args, **kwargs):
|
||||||
self = view.view_class(*class_args, **class_kwargs)
|
self = view.view_class(*class_args, **class_kwargs)
|
||||||
return self.dispatch_request(*args, **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:
|
# 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
|
# 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
|
# 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:
|
else:
|
||||||
print ("No problems found in sourcecode.")
|
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(
|
setup(
|
||||||
name='Flask',
|
name='Flask',
|
||||||
|
|
@ -94,7 +88,10 @@ setup(
|
||||||
description='A microframework based on Werkzeug, Jinja2 '
|
description='A microframework based on Werkzeug, Jinja2 '
|
||||||
'and good intentions',
|
'and good intentions',
|
||||||
long_description=__doc__,
|
long_description=__doc__,
|
||||||
packages=['flask'],
|
packages=['flask', 'flask.testsuite'],
|
||||||
|
package_data={
|
||||||
|
'flask.testsuite': ['test_apps/*', 'static/*', 'templates/*']
|
||||||
|
},
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
platforms='any',
|
platforms='any',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
|
|
@ -112,5 +109,5 @@ setup(
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||||
],
|
],
|
||||||
cmdclass={'audit': run_audit},
|
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