Merge remote branch 'mitsuhiko/master'
This commit is contained in:
commit
38262ccac5
84 changed files with 5584 additions and 2003 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,6 +2,9 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
env
|
env
|
||||||
|
env*
|
||||||
dist
|
dist
|
||||||
|
*.egg
|
||||||
*.egg-info
|
*.egg-info
|
||||||
_mailinglist
|
_mailinglist
|
||||||
|
.tox
|
||||||
|
|
|
||||||
5
AUTHORS
5
AUTHORS
|
|
@ -9,16 +9,21 @@ Development Lead
|
||||||
Patches and Suggestions
|
Patches and Suggestions
|
||||||
```````````````````````
|
```````````````````````
|
||||||
|
|
||||||
|
- Adam Zapletal
|
||||||
|
- Ali Afshar
|
||||||
- Chris Edgemon
|
- Chris Edgemon
|
||||||
- Chris Grindstaff
|
- Chris Grindstaff
|
||||||
|
- Christopher Grebs
|
||||||
- Florent Xicluna
|
- Florent Xicluna
|
||||||
- Georg Brandl
|
- Georg Brandl
|
||||||
- Justin Quick
|
- Justin Quick
|
||||||
- Kenneth Reitz
|
- Kenneth Reitz
|
||||||
- Marian Sigler
|
- Marian Sigler
|
||||||
|
- Matt Campell
|
||||||
- Matthew Frazier
|
- Matthew Frazier
|
||||||
- Ron DuPlain
|
- Ron DuPlain
|
||||||
- Sebastien Estienne
|
- Sebastien Estienne
|
||||||
- Simon Sapin
|
- Simon Sapin
|
||||||
- Stephane Wirtel
|
- Stephane Wirtel
|
||||||
|
- Thomas Schranz
|
||||||
- Zhao Xiaohong
|
- Zhao Xiaohong
|
||||||
|
|
|
||||||
104
CHANGES
104
CHANGES
|
|
@ -3,10 +3,110 @@ Flask Changelog
|
||||||
|
|
||||||
Here you can see the full list of changes between each Flask release.
|
Here you can see the full list of changes between each Flask release.
|
||||||
|
|
||||||
|
Version 0.7
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Release date to be announced, codename to be selected
|
||||||
|
|
||||||
|
- Added :meth:`~flask.Flask.make_default_options_response`
|
||||||
|
which can be used by subclasses to alter the default
|
||||||
|
behaviour for `OPTIONS` responses.
|
||||||
|
- Unbound locals now raise a proper :exc:`RuntimeError` instead
|
||||||
|
of an :exc:`AttributeError`.
|
||||||
|
- Mimetype guessing and etag support based on file objects is now
|
||||||
|
deprecated for :func:`flask.send_file` because it was unreliable.
|
||||||
|
Pass filenames instead or attach your own etags and provide a
|
||||||
|
proper mimetype by hand.
|
||||||
|
|
||||||
|
Version 0.6.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix release, release date to be announced.
|
||||||
|
|
||||||
|
- Fixed an issue where the default `OPTIONS` response was
|
||||||
|
not exposing all valid methods in the `Allow` header.
|
||||||
|
- Jinja2 template loading syntax now allows "./" in front of
|
||||||
|
a template load path. Previously this caused issues with
|
||||||
|
module setups.
|
||||||
|
- Fixed an issue where the subdomain setting for modules was
|
||||||
|
ignored for the static folder.
|
||||||
|
|
||||||
|
Version 0.6
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Released on July 27th 2010, codename Whisky
|
||||||
|
|
||||||
|
- after request functions are now called in reverse order of
|
||||||
|
registration.
|
||||||
|
- OPTIONS is now automatically implemented by Flask unless the
|
||||||
|
application explictly adds 'OPTIONS' as method to the URL rule.
|
||||||
|
In this case no automatic OPTIONS handling kicks in.
|
||||||
|
- static rules are now even in place if there is no static folder
|
||||||
|
for the module. This was implemented to aid GAE which will
|
||||||
|
remove the static folder if it's part of a mapping in the .yml
|
||||||
|
file.
|
||||||
|
- the :attr:`~flask.Flask.config` is now available in the templates
|
||||||
|
as `config`.
|
||||||
|
- context processors will no longer override values passed directly
|
||||||
|
to the render function.
|
||||||
|
- added the ability to limit the incoming request data with the
|
||||||
|
new ``MAX_CONTENT_LENGTH`` configuration value.
|
||||||
|
- the endpoint for the :meth:`flask.Module.add_url_rule` method
|
||||||
|
is now optional to be consistent with the function of the
|
||||||
|
same name on the application object.
|
||||||
|
- added a :func:`flask.make_response` function that simplifies
|
||||||
|
creating response object instances in views.
|
||||||
|
- added signalling support based on blinker. This feature is currently
|
||||||
|
optional and supposed to be used by extensions and applications. If
|
||||||
|
you want to use it, make sure to have `blinker`_ installed.
|
||||||
|
- refactored the way url adapters are created. This process is now
|
||||||
|
fully customizable with the :meth:`~flask.Flask.create_url_adapter`
|
||||||
|
method.
|
||||||
|
- modules can now register for a subdomain instead of just an URL
|
||||||
|
prefix. This makes it possible to bind a whole module to a
|
||||||
|
configurable subdomain.
|
||||||
|
|
||||||
|
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||||
|
|
||||||
|
Version 0.5.2
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix Release, released on July 15th 2010
|
||||||
|
|
||||||
|
- fixed another issue with loading templates from directories when
|
||||||
|
modules were used.
|
||||||
|
|
||||||
|
Version 0.5.1
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Bugfix Release, released on July 6th 2010
|
||||||
|
|
||||||
|
- fixes an issue with template loading from directories when modules
|
||||||
|
where used.
|
||||||
|
|
||||||
Version 0.5
|
Version 0.5
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
Codename to be decided, release date to be announced.
|
Released on July 6th 2010, codename Calvados
|
||||||
|
|
||||||
|
- fixed a bug with subdomains that was caused by the inability to
|
||||||
|
specify the server name. The server name can now be set with
|
||||||
|
the `SERVER_NAME` config key. This key is now also used to set
|
||||||
|
the session cookie cross-subdomain wide.
|
||||||
|
- autoescaping is no longer active for all templates. Instead it
|
||||||
|
is only active for ``.html``, ``.htm``, ``.xml`` and ``.xhtml``.
|
||||||
|
Inside templates this behaviour can be changed with the
|
||||||
|
``autoescape`` tag.
|
||||||
|
- refactored Flask internally. It now consists of more than a
|
||||||
|
single file.
|
||||||
|
- :func:`flask.send_file` now emits etags and has the ability to
|
||||||
|
do conditional responses builtin.
|
||||||
|
- (temporarily) dropped support for zipped applications. This was a
|
||||||
|
rarely used feature and led to some confusing behaviour.
|
||||||
|
- added support for per-package template and static-file directories.
|
||||||
|
- removed support for `create_jinja_loader` which is no longer used
|
||||||
|
in 0.5 due to the improved module support.
|
||||||
|
- added a helper function to expose files from any directory.
|
||||||
|
|
||||||
Version 0.4
|
Version 0.4
|
||||||
-----------
|
-----------
|
||||||
|
|
@ -29,7 +129,7 @@ Released on June 18th 2010, codename Rakia
|
||||||
Version 0.3.1
|
Version 0.3.1
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Bugfix release, released May 28th 2010
|
Bugfix release, released on May 28th 2010
|
||||||
|
|
||||||
- fixed a error reporting bug with :meth:`flask.Config.from_envvar`
|
- fixed a error reporting bug with :meth:`flask.Config.from_envvar`
|
||||||
- removed some unused code from flask
|
- removed some unused code from flask
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
include Makefile CHANGES LICENSE AUTHORS
|
include Makefile CHANGES LICENSE AUTHORS
|
||||||
|
recursive-include artwork *
|
||||||
recursive-include tests *
|
recursive-include tests *
|
||||||
recursive-include examples *
|
recursive-include examples *
|
||||||
recursive-include docs *
|
recursive-include docs *
|
||||||
|
|
|
||||||
14
Makefile
14
Makefile
|
|
@ -1,10 +1,19 @@
|
||||||
.PHONY: clean-pyc test upload-docs
|
.PHONY: clean-pyc ext-test test upload-docs docs audit
|
||||||
|
|
||||||
all: clean-pyc test
|
all: clean-pyc test
|
||||||
|
|
||||||
test:
|
test:
|
||||||
python setup.py test
|
python setup.py test
|
||||||
|
|
||||||
|
audit:
|
||||||
|
python setup.py audit
|
||||||
|
|
||||||
|
tox-test:
|
||||||
|
PYTHONDONTWRITEBYTECODE= tox
|
||||||
|
|
||||||
|
ext-test:
|
||||||
|
python tests/flaskext_test.py --browse
|
||||||
|
|
||||||
release:
|
release:
|
||||||
python setup.py release sdist upload
|
python setup.py release sdist upload
|
||||||
|
|
||||||
|
|
@ -20,3 +29,6 @@ upload-docs:
|
||||||
scp -r docs/_build/dirhtml/* pocoo.org:/var/www/flask.pocoo.org/docs/
|
scp -r docs/_build/dirhtml/* pocoo.org:/var/www/flask.pocoo.org/docs/
|
||||||
scp -r docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf
|
scp -r docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf
|
||||||
scp -r docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/
|
scp -r docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/
|
||||||
|
|
||||||
|
docs:
|
||||||
|
$(MAKE) -C docs html
|
||||||
|
|
|
||||||
28
docs/LICENSE
28
docs/LICENSE
|
|
@ -1,28 +0,0 @@
|
||||||
Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS
|
|
||||||
for more details.
|
|
||||||
|
|
||||||
Some rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source (reStructuredText) and 'compiled' forms (HTML,
|
|
||||||
PDF, PostScript, RTF and so forth) with or without modification, are permitted
|
|
||||||
provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code (reStructuredText) must retain the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer as the
|
|
||||||
first lines of this file unmodified.
|
|
||||||
|
|
||||||
* Redistributions in compiled form (converted to HTML, PDF, PostScript, RTF
|
|
||||||
and so forth) must reproduce the above copyright notice, this list of
|
|
||||||
conditions and the following disclaimer in the documentation and/or other
|
|
||||||
materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
||||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
1
docs/_themes
Submodule
1
docs/_themes
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 3d964b660442e23faedf801caed6e3c7bd42d5c9
|
||||||
122
docs/api.rst
122
docs/api.rst
|
|
@ -38,6 +38,8 @@ Incoming Request Data
|
||||||
sure that you always get the correct data for the active thread if you
|
sure that you always get the correct data for the active thread if you
|
||||||
are in a multithreaded environment.
|
are in a multithreaded environment.
|
||||||
|
|
||||||
|
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||||
|
|
||||||
The request object is an instance of a :class:`~werkzeug.Request`
|
The request object is an instance of a :class:`~werkzeug.Request`
|
||||||
subclass and provides all of the attributes Werkzeug defines. This
|
subclass and provides all of the attributes Werkzeug defines. This
|
||||||
just shows a quick overview of the most important ones.
|
just shows a quick overview of the most important ones.
|
||||||
|
|
@ -164,6 +166,8 @@ To access the current session you can use the :class:`session` object:
|
||||||
The session object works pretty much like an ordinary dict, with the
|
The session object works pretty much like an ordinary dict, with the
|
||||||
difference that it keeps track on modifications.
|
difference that it keeps track on modifications.
|
||||||
|
|
||||||
|
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||||
|
|
||||||
The following attributes are interesting:
|
The following attributes are interesting:
|
||||||
|
|
||||||
.. attribute:: new
|
.. attribute:: new
|
||||||
|
|
@ -206,6 +210,8 @@ thing, like it does for :class:`request` and :class:`session`.
|
||||||
Just store on this whatever you want. For example a database
|
Just store on this whatever you want. For example a database
|
||||||
connection or the user that is currently logged in.
|
connection or the user that is currently logged in.
|
||||||
|
|
||||||
|
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||||
|
|
||||||
|
|
||||||
Useful Functions and Classes
|
Useful Functions and Classes
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
@ -216,6 +222,8 @@ Useful Functions and Classes
|
||||||
extensions that want to support multiple applications running side
|
extensions that want to support multiple applications running side
|
||||||
by side.
|
by side.
|
||||||
|
|
||||||
|
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||||
|
|
||||||
.. autofunction:: url_for
|
.. autofunction:: url_for
|
||||||
|
|
||||||
.. function:: abort(code)
|
.. function:: abort(code)
|
||||||
|
|
@ -228,8 +236,12 @@ Useful Functions and Classes
|
||||||
|
|
||||||
.. autofunction:: redirect
|
.. autofunction:: redirect
|
||||||
|
|
||||||
|
.. autofunction:: make_response
|
||||||
|
|
||||||
.. autofunction:: send_file
|
.. autofunction:: send_file
|
||||||
|
|
||||||
|
.. autofunction:: send_from_directory
|
||||||
|
|
||||||
.. autofunction:: escape
|
.. autofunction:: escape
|
||||||
|
|
||||||
.. autoclass:: Markup
|
.. autoclass:: Markup
|
||||||
|
|
@ -301,6 +313,36 @@ Useful Internals
|
||||||
instance and can be used by extensions and application code but the
|
instance and can be used by extensions and application code but the
|
||||||
use is discouraged in general.
|
use is discouraged in general.
|
||||||
|
|
||||||
|
The following attributes are always present on each layer of the
|
||||||
|
stack:
|
||||||
|
|
||||||
|
`app`
|
||||||
|
the active Flask application.
|
||||||
|
|
||||||
|
`url_adapter`
|
||||||
|
the URL adapter that was used to match the request.
|
||||||
|
|
||||||
|
`request`
|
||||||
|
the current request object.
|
||||||
|
|
||||||
|
`session`
|
||||||
|
the active session object.
|
||||||
|
|
||||||
|
`g`
|
||||||
|
an object with all the attributes of the :data:`flask.g` object.
|
||||||
|
|
||||||
|
`flashes`
|
||||||
|
an internal cache for the flashed messages.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
from flask import _request_ctx_stack
|
||||||
|
|
||||||
|
def get_session():
|
||||||
|
ctx = _request_ctx_stack.top
|
||||||
|
if ctx is not None:
|
||||||
|
return ctx.session
|
||||||
|
|
||||||
.. versionchanged:: 0.4
|
.. versionchanged:: 0.4
|
||||||
|
|
||||||
The request context is automatically popped at the end of the request
|
The request context is automatically popped at the end of the request
|
||||||
|
|
@ -317,3 +359,83 @@ Useful Internals
|
||||||
information from the context local around for a little longer. Make
|
information from the context local around for a little longer. Make
|
||||||
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
|
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
|
||||||
that situation, otherwise your unittests will leak memory.
|
that situation, otherwise your unittests will leak memory.
|
||||||
|
|
||||||
|
Signals
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. when modifying this list, also update the one in signals.rst
|
||||||
|
|
||||||
|
.. versionadded:: 0.6
|
||||||
|
|
||||||
|
.. data:: signals_available
|
||||||
|
|
||||||
|
`True` if the signalling system is available. This is the case
|
||||||
|
when `blinker`_ is installed.
|
||||||
|
|
||||||
|
.. data:: template_rendered
|
||||||
|
|
||||||
|
This signal is sent when a template was successfully rendered. The
|
||||||
|
signal is invoked with the instance of the template as `template`
|
||||||
|
and the context as dictionary (named `context`).
|
||||||
|
|
||||||
|
.. data:: request_started
|
||||||
|
|
||||||
|
This signal is sent before any request processing started but when the
|
||||||
|
request context was set up. Because the request context is already
|
||||||
|
bound, the subscriber can access the request with the standard global
|
||||||
|
proxies such as :class:`~flask.request`.
|
||||||
|
|
||||||
|
.. data:: request_finished
|
||||||
|
|
||||||
|
This signal is sent right before the response is sent to the client.
|
||||||
|
It is passed the response to be sent named `response`.
|
||||||
|
|
||||||
|
.. data:: got_request_exception
|
||||||
|
|
||||||
|
This signal is sent when an exception happens during request processing.
|
||||||
|
It is sent *before* the standard exception handling kicks in and even
|
||||||
|
in debug mode, where no exception handling happens. The exception
|
||||||
|
itself is passed to the subscriber as `exception`.
|
||||||
|
|
||||||
|
.. currentmodule:: None
|
||||||
|
|
||||||
|
.. class:: flask.signals.Namespace
|
||||||
|
|
||||||
|
An alias for :class:`blinker.base.Namespace` if blinker is available,
|
||||||
|
otherwise a dummy class that creates fake signals. This class is
|
||||||
|
available for Flask extensions that want to provide the same fallback
|
||||||
|
system as Flask itself.
|
||||||
|
|
||||||
|
.. method:: signal(name, doc=None)
|
||||||
|
|
||||||
|
Creates a new signal for this namespace if blinker is available,
|
||||||
|
otherwise returns a fake signal that has a send method that will
|
||||||
|
do nothing but will fail with a :exc:`RuntimeError` for all other
|
||||||
|
operations, including connecting.
|
||||||
|
|
||||||
|
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||||
|
|
||||||
|
.. _notes-on-proxies:
|
||||||
|
|
||||||
|
Notes On Proxies
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Some of the objects provided by Flask are proxies to other objects. The
|
||||||
|
reason behind this is that these proxies are shared between threads and
|
||||||
|
they have to dispatch to the actual object bound to a thread behind the
|
||||||
|
scenes as necessary.
|
||||||
|
|
||||||
|
Most of the time you don't have to care about that, but there are some
|
||||||
|
exceptions where it is good to know that this object is an actual proxy:
|
||||||
|
|
||||||
|
- The proxy objects do not fake their inherited types, so if you want to
|
||||||
|
perform actual instance checks, you have to do that on the instance
|
||||||
|
that is being proxied (see `_get_current_object` below).
|
||||||
|
- if the object reference is important (so for example for sending
|
||||||
|
:ref:`signals`)
|
||||||
|
|
||||||
|
If you need to get access to the underlying object that is proxied, you
|
||||||
|
can use the :meth:`~werkzeug.LocalProxy._get_current_object` method::
|
||||||
|
|
||||||
|
app = current_app._get_current_object()
|
||||||
|
my_signal.send(app)
|
||||||
|
|
|
||||||
|
|
@ -3,54 +3,86 @@
|
||||||
Becoming Big
|
Becoming Big
|
||||||
============
|
============
|
||||||
|
|
||||||
Your application is becoming more and more complex? Flask is really not
|
Your application is becoming more and more complex? If you suddenly
|
||||||
designed for large scale applications and does not attempt to do so, but
|
realize that Flask does things in a way that does not work out for your
|
||||||
that does not mean you picked the wrong tool in the first place.
|
application there are ways to deal with that.
|
||||||
|
|
||||||
Flask is powered by Werkzeug and Jinja2, two libraries that are in use at
|
Flask is powered by Werkzeug and Jinja2, two libraries that are in use at
|
||||||
a number of large websites out there and all Flask does is bring those
|
a number of large websites out there and all Flask does is bring those
|
||||||
two together. Being a microframework, Flask is literally a single file.
|
two together. Being a microframework Flask does not do much more than
|
||||||
What that means for large applications is that it's probably a good idea
|
combining existing libraries - there is not a lot of code involved.
|
||||||
to take the code from Flask and put it into a new module within the
|
What that means for large applications is that it's very easy to take the
|
||||||
applications and expand on that.
|
code from Flask and put it into a new module within the applications and
|
||||||
|
expand on that.
|
||||||
|
|
||||||
What Could Be Improved?
|
Flask is designed to be extended and modified in a couple of different
|
||||||
-----------------------
|
ways:
|
||||||
|
|
||||||
For instance it makes a lot of sense to change the way endpoints (the
|
- Flask extensions. For a lot of reusable functionality you can create
|
||||||
names of the functions / URL rules) are handled to also take the module
|
extensions. For extensions a number of hooks exist throughout Flask
|
||||||
name into account. Right now the function name is the URL name, but
|
with signals and callback functions.
|
||||||
imagine you have a large application consisting of multiple components.
|
|
||||||
In that case, it makes a lot of sense to use dotted names for the URL
|
|
||||||
endpoints.
|
|
||||||
|
|
||||||
Here are some suggestions for how Flask can be modified to better
|
- Subclassing. The majority of functionality can be changed by creating
|
||||||
accommodate large-scale applications:
|
a new subclass of the :class:`~flask.Flask` class and overriding
|
||||||
|
methods provided for this exact purpose.
|
||||||
|
|
||||||
- get rid of the decorator function registering which causes a lot
|
- Forking. If nothing else works out you can just take the Flask
|
||||||
of troubles for applications that have circular dependencies. It
|
codebase at a given point and copy/paste it into your application
|
||||||
also requires that the whole application is imported when the system
|
and change it. Flask is designed with that in mind and makes this
|
||||||
initializes or certain URLs will not be available right away. A
|
incredible easy. You just have to take the package and copy it
|
||||||
better solution would be to have one module with all URLs in there and
|
into your application's code and rename it (for example to
|
||||||
specifying the target functions explicitly or by name and importing
|
`framework`). Then you can start modifying the code in there.
|
||||||
them when needed.
|
|
||||||
- switch to explicit request object passing. This requires more typing
|
|
||||||
(because you now have something to pass around) but it makes it a
|
|
||||||
whole lot easier to debug hairy situations and to test the code.
|
|
||||||
- integrate the `Babel`_ i18n package or `SQLAlchemy`_ directly into the
|
|
||||||
core framework.
|
|
||||||
|
|
||||||
.. _Babel: http://babel.edgewall.org/
|
Why consider Forking?
|
||||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
---------------------
|
||||||
|
|
||||||
Why does Flask not do all that by Default?
|
The majority of code of Flask is within Werkzeug and Jinja2. These
|
||||||
------------------------------------------
|
libraries do the majority of the work. Flask is just the paste that glues
|
||||||
|
those together. For every project there is the point where the underlying
|
||||||
|
framework gets in the way (due to assumptions the original developers
|
||||||
|
had). This is natural because if this would not be the case, the
|
||||||
|
framework would be a very complex system to begin with which causes a
|
||||||
|
steep learning curve and a lot of user frustration.
|
||||||
|
|
||||||
There is a huge difference between a small application that only has to
|
This is not unique to Flask. Many people use patched and modified
|
||||||
handle a couple of requests per second and with an overall code complexity
|
versions of their framework to counter shortcomings. This idea is also
|
||||||
of less than 4000 lines of code and something of larger scale. At some
|
reflected in the license of Flask. You don't have to contribute any
|
||||||
point it becomes important to integrate external systems, different
|
changes back if you decide to modify the framework.
|
||||||
storage backends and more.
|
|
||||||
|
|
||||||
If Flask was designed with all these contingencies in mind, it would be a
|
The downside of forking is of course that Flask extensions will most
|
||||||
much more complex framework and harder to get started with.
|
likely break because the new framework has a different import name.
|
||||||
|
Furthermore integrating upstream changes can be a complex process,
|
||||||
|
depending on the number of changes. Because of that, forking should be
|
||||||
|
the very last resort.
|
||||||
|
|
||||||
|
Scaling like a Pro
|
||||||
|
------------------
|
||||||
|
|
||||||
|
For many web applications the complexity of the code is less an issue than
|
||||||
|
the scaling for the number of users or data entries expected. Flask by
|
||||||
|
itself is only limited in terms of scaling by your application code, the
|
||||||
|
data store you want to use and the Python implementation and webserver you
|
||||||
|
are running on.
|
||||||
|
|
||||||
|
Scaling well means for example that if you double the amount of servers
|
||||||
|
you get about twice the performance. Scaling bad means that if you add a
|
||||||
|
new server the application won't perform any better or would not even
|
||||||
|
support a second server.
|
||||||
|
|
||||||
|
There is only one limiting factor regarding scaling in Flask which are
|
||||||
|
the context local proxies. They depend on context which in Flask is
|
||||||
|
defined as being either a thread, process or greenlet. If your server
|
||||||
|
uses some kind of concurrency that is not based on threads or greenlets,
|
||||||
|
Flask will no longer be able to support these global proxies. However the
|
||||||
|
majority of servers are using either threads, greenlets or separate
|
||||||
|
processes to achieve concurrency which are all methods well supported by
|
||||||
|
the underlying Werkzeug library.
|
||||||
|
|
||||||
|
Dialogue with the Community
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
The Flask developers are very interested to keep everybody happy, so as
|
||||||
|
soon as you find an obstacle in your way, caused by Flask, don't hesitate
|
||||||
|
to contact the developers on the mailinglist or IRC channel. The best way
|
||||||
|
for the Flask and Flask-extension developers to improve it for larger
|
||||||
|
applications is getting feedback from users.
|
||||||
|
|
|
||||||
18
docs/conf.py
18
docs/conf.py
|
|
@ -245,7 +245,23 @@ intersphinx_mapping = {
|
||||||
'http://docs.python.org/dev': None,
|
'http://docs.python.org/dev': None,
|
||||||
'http://werkzeug.pocoo.org/documentation/dev/': None,
|
'http://werkzeug.pocoo.org/documentation/dev/': None,
|
||||||
'http://www.sqlalchemy.org/docs/': None,
|
'http://www.sqlalchemy.org/docs/': None,
|
||||||
'http://wtforms.simplecodes.com/docs/0.5/': None
|
'http://wtforms.simplecodes.com/docs/0.5/': None,
|
||||||
|
'http://discorporate.us/projects/Blinker/docs/1.1/': None
|
||||||
}
|
}
|
||||||
|
|
||||||
pygments_style = 'flask_theme_support.FlaskyStyle'
|
pygments_style = 'flask_theme_support.FlaskyStyle'
|
||||||
|
|
||||||
|
# fall back if theme is not there
|
||||||
|
try:
|
||||||
|
__import__('flask_theme_support')
|
||||||
|
except ImportError, e:
|
||||||
|
print '-' * 74
|
||||||
|
print 'Warning: Flask themes unavailable. Building with default theme'
|
||||||
|
print 'If you want the Flask themes, run this command and build again:'
|
||||||
|
print
|
||||||
|
print ' git submodule update --init'
|
||||||
|
print '-' * 74
|
||||||
|
|
||||||
|
pygments_style = 'tango'
|
||||||
|
html_theme = 'default'
|
||||||
|
html_theme_options = {}
|
||||||
|
|
|
||||||
117
docs/config.rst
117
docs/config.rst
|
|
@ -6,12 +6,12 @@ Configuration Handling
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
|
|
||||||
Applications need some kind of configuration. There are different things
|
Applications need some kind of configuration. There are different things
|
||||||
you might want to change. Like toggling debug mode, the secret key and a
|
you might want to change like toggling debug mode, the secret key, and a
|
||||||
lot of very similar things.
|
lot of very similar things.
|
||||||
|
|
||||||
The way Flask is designed usually requires the configuration to be
|
The way Flask is designed usually requires the configuration to be
|
||||||
available when the application starts up. You can either hardcode the
|
available when the application starts up. You can hardcode the
|
||||||
configuration in the code which for many small applications is not
|
configuration in the code, which for many small applications is not
|
||||||
actually that bad, but there are better ways.
|
actually that bad, but there are better ways.
|
||||||
|
|
||||||
Independent of how you load your config, there is a config object
|
Independent of how you load your config, there is a config object
|
||||||
|
|
@ -59,13 +59,48 @@ The following configuration values are used internally by Flask:
|
||||||
``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
|
||||||
|
``LOGGER_NAME`` the name of the logger
|
||||||
|
``SERVER_NAME`` the name of the server. Required for
|
||||||
|
subdomain support (e.g.: ``'localhost'``)
|
||||||
|
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
|
||||||
|
reject incoming requests with a
|
||||||
|
content length greater than this by
|
||||||
|
returning a 413 status code.
|
||||||
=============================== =========================================
|
=============================== =========================================
|
||||||
|
|
||||||
|
.. admonition:: More on ``SERVER_NAME``
|
||||||
|
|
||||||
|
The ``SERVER_NAME`` key is used for the subdomain support. Because
|
||||||
|
Flask cannot guess the subdomain part without the knowledge of the
|
||||||
|
actual server name, this is required if you want to work with
|
||||||
|
subdomains. This is also used for the session cookie.
|
||||||
|
|
||||||
|
Please keep in mind that not only Flask has the problem of not knowing
|
||||||
|
what subdomains are, your web browser does as well. Most modern web
|
||||||
|
browsers will not allow cross-subdomain cookies to be set on a
|
||||||
|
server name without dots in it. So if your server name is
|
||||||
|
``'localhost'`` you will not be able to set a cookie for
|
||||||
|
``'localhost'`` and every subdomain of it. Please chose a different
|
||||||
|
server name in that case, like ``'myapplication.local'`` and add
|
||||||
|
this name + the subdomains you want to use into your host config
|
||||||
|
or setup a local `bind`_.
|
||||||
|
|
||||||
|
.. _bind: https://www.isc.org/software/bind
|
||||||
|
|
||||||
|
.. versionadded:: 0.4
|
||||||
|
``LOGGER_NAME``
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
``SERVER_NAME``
|
||||||
|
|
||||||
|
.. versionadded:: 0.6
|
||||||
|
``MAX_CONTENT_LENGTH``
|
||||||
|
|
||||||
Configuring from Files
|
Configuring from Files
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Configuration becomes more useful if you can configure from a file. And
|
Configuration becomes more useful if you can configure from a file, and
|
||||||
ideally that file would be outside of the actual application package that
|
ideally that file would be outside of the actual application package so that
|
||||||
you can install the package with distribute (:ref:`distribute-deployment`)
|
you can install the package with distribute (:ref:`distribute-deployment`)
|
||||||
and still modify that file afterwards.
|
and still modify that file afterwards.
|
||||||
|
|
||||||
|
|
@ -75,7 +110,7 @@ So a common pattern is this::
|
||||||
app.config.from_object('yourapplication.default_settings')
|
app.config.from_object('yourapplication.default_settings')
|
||||||
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
||||||
|
|
||||||
What this does is first loading the configuration from the
|
This first loads the configuration from the
|
||||||
`yourapplication.default_settings` module and then overrides the values
|
`yourapplication.default_settings` module and then overrides the values
|
||||||
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
||||||
environment variable points to. This environment variable can be set on
|
environment variable points to. This environment variable can be set on
|
||||||
|
|
@ -95,7 +130,7 @@ The configuration files themselves are actual Python files. Only values
|
||||||
in uppercase are actually stored in the config object later on. So make
|
in uppercase are actually stored in the config object later on. So make
|
||||||
sure to use uppercase letters for your config keys.
|
sure to use uppercase letters for your config keys.
|
||||||
|
|
||||||
Here an example configuration file::
|
Here is an example configuration file::
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
|
SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
|
||||||
|
|
@ -123,3 +158,71 @@ experience:
|
||||||
2. Do not write code that needs the configuration at import time. If you
|
2. Do not write code that needs the configuration at import time. If you
|
||||||
limit yourself to request-only accesses to the configuration you can
|
limit yourself to request-only accesses to the configuration you can
|
||||||
reconfigure the object later on as needed.
|
reconfigure the object later on as needed.
|
||||||
|
|
||||||
|
|
||||||
|
Development / Production
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Most applications need more than one configuration. There will at least
|
||||||
|
be a separate configuration for a production server and one used during
|
||||||
|
development. The easiest way to handle this is to use a default
|
||||||
|
configuration that is always loaded and part of version control, and a
|
||||||
|
separate configuration that overrides the values as necessary as mentioned
|
||||||
|
in the example above::
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object('yourapplication.default_settings')
|
||||||
|
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
||||||
|
|
||||||
|
Then you just have to add a separate `config.py` file and export
|
||||||
|
``YOURAPPLICATION_SETTINGS=/path/to/config.py`` and you are done. However
|
||||||
|
there are alternative ways as well. For example you could use imports or
|
||||||
|
subclassing.
|
||||||
|
|
||||||
|
What is very popular in the Django world is to make the import explicit in
|
||||||
|
the config file by adding an ``from yourapplication.default_settings
|
||||||
|
import *`` to the top of the file and then overriding the changes by hand.
|
||||||
|
You could also inspect an environment variable like
|
||||||
|
``YOURAPPLICATION_MODE`` and set that to `production`, `development` etc
|
||||||
|
and import different hardcoded files based on that.
|
||||||
|
|
||||||
|
An interesting pattern is also to use classes and inheritance for
|
||||||
|
configuration::
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
DEBUG = False
|
||||||
|
TESTING = False
|
||||||
|
DATABASE_URI = 'sqlite://:memory:'
|
||||||
|
|
||||||
|
class ProductionConfig(Config):
|
||||||
|
DATABASE_URI = 'mysql://user@localhost/foo'
|
||||||
|
|
||||||
|
class DevelopmentConfig(Config):
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
class TestinConfig(Config):
|
||||||
|
TESTING = True
|
||||||
|
|
||||||
|
To enable such a config you just have to call into
|
||||||
|
:meth:`~flask.Config.from_object`::
|
||||||
|
|
||||||
|
app.config.from_object('configmodule.ProductionConfig')
|
||||||
|
|
||||||
|
There are many different ways and it's up to you how you want to manage
|
||||||
|
your configuration files. However here a list of good recommendations:
|
||||||
|
|
||||||
|
- keep a default configuration in version control. Either populate the
|
||||||
|
config with this default configuration or import it in your own
|
||||||
|
configuration files before overriding values.
|
||||||
|
- use an environment variable to switch between the configurations.
|
||||||
|
This can be done from outside the Python interpreter and makes
|
||||||
|
development and deployment much easier because you can quickly and
|
||||||
|
easily switch between different configs without having to touch the
|
||||||
|
code at all. If you are working often on different projects you can
|
||||||
|
even create your own script for sourcing that activates a virtualenv
|
||||||
|
and exports the development configuration for you.
|
||||||
|
- Use a tool like `fabric`_ in production to push code and
|
||||||
|
configurations separately to the production server(s). For some
|
||||||
|
details about how to do that, head over to the :ref:`deploy` pattern.
|
||||||
|
|
||||||
|
.. _fabric: http://fabfile.org/
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
User's Guide
|
User's Guide
|
||||||
------------
|
------------
|
||||||
|
|
||||||
This part of the documentation is written text and should give you an idea
|
This part of the documentation, which is mostly prose, begins with some
|
||||||
how to work with Flask. It's a series of step-by-step instructions for
|
background information about Flask, then focuses on step-by-step
|
||||||
web development.
|
instructions for web development with Flask.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
@ -12,9 +12,11 @@ web development.
|
||||||
installation
|
installation
|
||||||
quickstart
|
quickstart
|
||||||
tutorial/index
|
tutorial/index
|
||||||
|
templating
|
||||||
testing
|
testing
|
||||||
errorhandling
|
errorhandling
|
||||||
config
|
config
|
||||||
|
signals
|
||||||
shell
|
shell
|
||||||
patterns/index
|
patterns/index
|
||||||
deploying/index
|
deploying/index
|
||||||
|
|
@ -44,6 +46,7 @@ Design notes, legal information and changelog are here for the interested.
|
||||||
security
|
security
|
||||||
unicode
|
unicode
|
||||||
extensiondev
|
extensiondev
|
||||||
|
styleguide
|
||||||
upgrading
|
upgrading
|
||||||
changelog
|
changelog
|
||||||
license
|
license
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ Server Setup
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Usually there are two ways to configure the server. Either just copy the
|
Usually there are two ways to configure the server. Either just copy the
|
||||||
`.cgi` into a `cgi-bin` (and use `mod_rerwite` or something similar to
|
`.cgi` into a `cgi-bin` (and use `mod_rewrite` or something similar to
|
||||||
rewrite the URL) or let the server point to the file directly.
|
rewrite the URL) or let the server point to the file directly.
|
||||||
|
|
||||||
In Apache for example you can put a like like this into the config:
|
In Apache for example you can put a like like this into the config:
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ webserver user is `www-data`::
|
||||||
$ cd /var/www/yourapplication
|
$ cd /var/www/yourapplication
|
||||||
$ python application.fcgi
|
$ python application.fcgi
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "yourapplication.fcg", line 4, in <module>
|
File "yourapplication.fcgi", line 4, in <module>
|
||||||
ImportError: No module named yourapplication
|
ImportError: No module named yourapplication
|
||||||
|
|
||||||
In this case the error seems to be "yourapplication" not being on the python
|
In this case the error seems to be "yourapplication" not being on the python
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _mod_wsgi-deployment:
|
||||||
|
|
||||||
mod_wsgi (Apache)
|
mod_wsgi (Apache)
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
|
@ -91,8 +93,8 @@ For more information consult the `mod_wsgi wiki`_.
|
||||||
.. _virtual python: http://pypi.python.org/pypi/virtualenv
|
.. _virtual python: http://pypi.python.org/pypi/virtualenv
|
||||||
.. _mod_wsgi wiki: http://code.google.com/p/modwsgi/wiki/
|
.. _mod_wsgi wiki: http://code.google.com/p/modwsgi/wiki/
|
||||||
|
|
||||||
Toubleshooting
|
Troubleshooting
|
||||||
--------------
|
---------------
|
||||||
|
|
||||||
If your application does not run, follow this guide to troubleshoot:
|
If your application does not run, follow this guide to troubleshoot:
|
||||||
|
|
||||||
|
|
@ -133,3 +135,32 @@ If your application does not run, follow this guide to troubleshoot:
|
||||||
The reason for this is that for non-installed packages, the module
|
The reason for this is that for non-installed packages, the module
|
||||||
filename is used to locate the resources and for symlinks the wrong
|
filename is used to locate the resources and for symlinks the wrong
|
||||||
filename is picked up.
|
filename is picked up.
|
||||||
|
|
||||||
|
Support for Automatic Reloading
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
To help deployment tools you can activate support for automatic reloading.
|
||||||
|
Whenever something changes the `.wsgi` file, `mod_wsgi` will reload all
|
||||||
|
the daemon processes for us.
|
||||||
|
|
||||||
|
For that, just add the following directive to your `Directory` section:
|
||||||
|
|
||||||
|
.. sourcecode:: apache
|
||||||
|
|
||||||
|
WSGIScriptReloading On
|
||||||
|
|
||||||
|
Working with Virtual Environments
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Virtual environments have the advantage that they never install the
|
||||||
|
required dependencies system wide so you have a better control over what
|
||||||
|
is used where. If you want to use a virtual environment with mod_wsgi you
|
||||||
|
have to modify your `.wsgi` file slightly.
|
||||||
|
|
||||||
|
Add the following lines to the top of your `.wsgi` file::
|
||||||
|
|
||||||
|
activate_this = '/path/to/env/bin/activate_this.py'
|
||||||
|
execfile(activate_this, dict(__file__=activate_this))
|
||||||
|
|
||||||
|
This sets up the load paths according to the settings of the virtual
|
||||||
|
environment. Keep in mind that the path has to be absolute.
|
||||||
|
|
|
||||||
|
|
@ -61,3 +61,38 @@ and `greenlet`_. Running a Flask application on this server is quite simple::
|
||||||
.. _eventlet: http://eventlet.net/
|
.. _eventlet: http://eventlet.net/
|
||||||
.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html
|
.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html
|
||||||
|
|
||||||
|
|
||||||
|
Proxy Setups
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you deploy your application behind an HTTP proxy you will need to
|
||||||
|
rewrite a few headers in order for the application to work. The two
|
||||||
|
problematic values in the WSGI environment usually are `REMOTE_ADDR` and
|
||||||
|
`HTTP_HOST`. Werkzeug ships a fixer that will solve some common setups,
|
||||||
|
but you might want to write your own WSGI middleware for specific setups.
|
||||||
|
|
||||||
|
The most common setup invokes the host being set from `X-Forwarded-Host`
|
||||||
|
and the remote address from `X-Forward-For`::
|
||||||
|
|
||||||
|
from werkzeug.contrib.fixers import ProxyFix
|
||||||
|
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||||
|
|
||||||
|
Please keep in mind that it is a security issue to use such a middleware
|
||||||
|
in a non-proxy setup because it will blindly trust the incoming
|
||||||
|
headers which might be forged by malicious clients.
|
||||||
|
|
||||||
|
If you want to rewrite the headers from another header, you might want to
|
||||||
|
use a fixer like this::
|
||||||
|
|
||||||
|
class CustomProxyFix(object):
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
host = environ.get('HTTP_X_FHOST', '')
|
||||||
|
if host:
|
||||||
|
environ['HTTP_HOST'] = host
|
||||||
|
return self.app(environ, start_response)
|
||||||
|
|
||||||
|
app.wsgi_app = CustomProxyFix(app.wsgi_app)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _design:
|
||||||
|
|
||||||
Design Decisions in Flask
|
Design Decisions in Flask
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
@ -109,6 +111,10 @@ A template abstraction layer that would not take the unique features of
|
||||||
the template engines away is a science on its own and a too large
|
the template engines away is a science on its own and a too large
|
||||||
undertaking for a microframework like Flask.
|
undertaking for a microframework like Flask.
|
||||||
|
|
||||||
|
Furthermore extensions can then easily depend on one template language
|
||||||
|
being present. You can easily use your own templating language, but an
|
||||||
|
extension could still depend on Jinja itself.
|
||||||
|
|
||||||
|
|
||||||
Micro with Dependencies
|
Micro with Dependencies
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Handling Application Errors
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
|
|
||||||
Applications fail, server fail. Sooner or later you will see an exception
|
Applications fail, servers fail. Sooner or later you will see an exception
|
||||||
in production. Even if your code is 100% correct, you will still see
|
in production. Even if your code is 100% correct, you will still see
|
||||||
exceptions from time to time. Why? Because everything else involved will
|
exceptions from time to time. Why? Because everything else involved will
|
||||||
fail. Here some situations where perfectly fine code can lead to server
|
fail. Here some situations where perfectly fine code can lead to server
|
||||||
|
|
@ -20,7 +20,7 @@ errors:
|
||||||
- a programming error in a library you are using
|
- a programming error in a library you are using
|
||||||
- network connection of the server to another system failed.
|
- network connection of the server to another system failed.
|
||||||
|
|
||||||
And that's just a small sample of issues you could be facing. So how to
|
And that's just a small sample of issues you could be facing. So how do we
|
||||||
deal with that sort of problem? By default if your application runs in
|
deal with that sort of problem? By default if your application runs in
|
||||||
production mode, Flask will display a very simple page for you and log the
|
production mode, Flask will display a very simple page for you and log the
|
||||||
exception to the :attr:`~flask.Flask.logger`.
|
exception to the :attr:`~flask.Flask.logger`.
|
||||||
|
|
@ -32,10 +32,10 @@ Error Mails
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
If the application runs in production mode (which it will do on your
|
If the application runs in production mode (which it will do on your
|
||||||
server) you won't see any log messages by default. Why that? Flask tries
|
server) you won't see any log messages by default. Why is that? Flask
|
||||||
to be a zero-configuration framework and where should it drop the logs for
|
tries to be a zero-configuration framework. Where should it drop the logs
|
||||||
you if there is no configuration. Guessing is not a good idea because
|
for you if there is no configuration? Guessing is not a good idea because
|
||||||
chances are, the place it guessed is not the place where the user has the
|
chances are, the place it guessed is not the place where the user has
|
||||||
permission to create a logfile. Also, for most small applications nobody
|
permission to create a logfile. Also, for most small applications nobody
|
||||||
will look at the logs anyways.
|
will look at the logs anyways.
|
||||||
|
|
||||||
|
|
@ -45,9 +45,9 @@ when a user reported it for you. What you want instead is a mail the
|
||||||
second the exception happened. Then you get an alert and you can do
|
second the exception happened. Then you get an alert and you can do
|
||||||
something about it.
|
something about it.
|
||||||
|
|
||||||
Flask is using the Python builtin logging system and that one can actually
|
Flask uses the Python builtin logging system, and it can actually send
|
||||||
send you mails for errors which is probably what you want. Here is how
|
you mails for errors which is probably what you want. Here is how you can
|
||||||
you can configure the Flask logger to send you mails for exceptions::
|
configure the Flask logger to send you mails for exceptions::
|
||||||
|
|
||||||
ADMINS = ['yourname@example.com']
|
ADMINS = ['yourname@example.com']
|
||||||
if not app.debug:
|
if not app.debug:
|
||||||
|
|
@ -63,16 +63,17 @@ So what just happened? We created a new
|
||||||
:class:`~logging.handlers.SMTPHandler` that will send mails with the mail
|
:class:`~logging.handlers.SMTPHandler` that will send mails with the mail
|
||||||
server listening on ``127.0.0.1`` to all the `ADMINS` from the address
|
server listening on ``127.0.0.1`` to all the `ADMINS` from the address
|
||||||
*server-error@example.com* with the subject "YourApplication Failed". If
|
*server-error@example.com* with the subject "YourApplication Failed". If
|
||||||
your mail server requires credentials these can also provided, for that
|
your mail server requires credentials, these can also be provided. For
|
||||||
check out the documentation for the :class:`~logging.handlers.SMTPHandler`.
|
that check out the documentation for the
|
||||||
|
:class:`~logging.handlers.SMTPHandler`.
|
||||||
|
|
||||||
We also tell the handler to only send errors and more critical messages.
|
We also tell the handler to only send errors and more critical messages.
|
||||||
Because we certainly don't want to get a mail for warnings or other
|
Because we certainly don't want to get a mail for warnings or other
|
||||||
useless logs that might happen during request handling.
|
useless logs that might happen during request handling.
|
||||||
|
|
||||||
Before you run that in production, please also look at :ref:`log-format`
|
Before you run that in production, please also look at :ref:`logformat` to
|
||||||
to put more information into that error mail. That will save you from a
|
put more information into that error mail. That will save you from a lot
|
||||||
lot of frustration.
|
of frustration.
|
||||||
|
|
||||||
|
|
||||||
Logging to a File
|
Logging to a File
|
||||||
|
|
@ -88,7 +89,7 @@ There are a couple of handlers provided by the logging system out of the
|
||||||
box but not all of them are useful for basic error logging. The most
|
box but not all of them are useful for basic error logging. The most
|
||||||
interesting are probably the following:
|
interesting are probably the following:
|
||||||
|
|
||||||
- :class:`~logging.handlers.FileHandler` - logs messages to a file on the
|
- :class:`~logging.FileHandler` - logs messages to a file on the
|
||||||
filesystem.
|
filesystem.
|
||||||
- :class:`~logging.handlers.RotatingFileHandler` - logs messages to a file
|
- :class:`~logging.handlers.RotatingFileHandler` - logs messages to a file
|
||||||
on the filesystem and will rotate after a certain number of messages.
|
on the filesystem and will rotate after a certain number of messages.
|
||||||
|
|
@ -104,23 +105,23 @@ above, just make sure to use a lower setting (I would recommend
|
||||||
|
|
||||||
if not app.debug:
|
if not app.debug:
|
||||||
import logging
|
import logging
|
||||||
from logging.handlers import TheHandlerYouWant
|
from themodule import TheHandler YouWant
|
||||||
file_handler = TheHandlerYouWant(...)
|
file_handler = TheHandlerYouWant(...)
|
||||||
file_handler.setLevel(logging.WARNING)
|
file_handler.setLevel(logging.WARNING)
|
||||||
app.logger.addHandler(file_handler)
|
app.logger.addHandler(file_handler)
|
||||||
|
|
||||||
.. _log-format:
|
.. _logformat:
|
||||||
|
|
||||||
Controlling the Log Format
|
Controlling the Log Format
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
By default a handler will only write the message string into a file or
|
By default a handler will only write the message string into a file or
|
||||||
send you that message as mail. But a log record stores more information
|
send you that message as mail. A log record stores more information,
|
||||||
and it makes a lot of sense to configure your logger to also contain that
|
and it makes a lot of sense to configure your logger to also contain that
|
||||||
information so that you have a better idea of why that error happened, and
|
information so that you have a better idea of why that error happened, and
|
||||||
more importantly, where it did.
|
more importantly, where it did.
|
||||||
|
|
||||||
A formatter can be instanciated with a format string. Note that
|
A formatter can be instantiated with a format string. Note that
|
||||||
tracebacks are appended to the log entry automatically. You don't have to
|
tracebacks are appended to the log entry automatically. You don't have to
|
||||||
do that in the log formatter format string.
|
do that in the log formatter format string.
|
||||||
|
|
||||||
|
|
@ -206,7 +207,7 @@ formatter. The formatter has three interesting methods:
|
||||||
called for `asctime` formatting. If you want a different time format
|
called for `asctime` formatting. If you want a different time format
|
||||||
you can override this method.
|
you can override this method.
|
||||||
:meth:`~logging.Formatter.formatException`
|
:meth:`~logging.Formatter.formatException`
|
||||||
called for exception formatting. It is passed a :attr:`~sys.exc_info`
|
called for exception formatting. It is passed an :attr:`~sys.exc_info`
|
||||||
tuple and has to return a string. The default is usually fine, you
|
tuple and has to return a string. The default is usually fine, you
|
||||||
don't have to override it.
|
don't have to override it.
|
||||||
|
|
||||||
|
|
@ -217,8 +218,8 @@ Other Libraries
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
So far we only configured the logger your application created itself.
|
So far we only configured the logger your application created itself.
|
||||||
Other libraries might log themselves as well. For example, SQLAlchemy use
|
Other libraries might log themselves as well. For example, SQLAlchemy uses
|
||||||
logging heavily in the core. While there is a method to configure all
|
logging heavily in its core. While there is a method to configure all
|
||||||
loggers at once in the :mod:`logging` package, I would not recommend using
|
loggers at once in the :mod:`logging` package, I would not recommend using
|
||||||
it. There might be a situation in which you want to have multiple
|
it. There might be a situation in which you want to have multiple
|
||||||
separate applications running side by side in the same Python interpreter
|
separate applications running side by side in the same Python interpreter
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ First we create the following folder structure::
|
||||||
setup.py
|
setup.py
|
||||||
LICENSE
|
LICENSE
|
||||||
|
|
||||||
Here the contents of the most important files:
|
Here's the contents of the most important files:
|
||||||
|
|
||||||
flaskext/__init__.py
|
flaskext/__init__.py
|
||||||
````````````````````
|
````````````````````
|
||||||
|
|
@ -153,7 +153,7 @@ There are two recommended ways for an extension to initialize:
|
||||||
|
|
||||||
initialization functions:
|
initialization functions:
|
||||||
If your extension is called `helloworld` you might have a function
|
If your extension is called `helloworld` you might have a function
|
||||||
called ``init_helloworld(app[, extra_args])`` that initalizes the
|
called ``init_helloworld(app[, extra_args])`` that initializes the
|
||||||
extension for that application. It could attach before / after
|
extension for that application. It could attach before / after
|
||||||
handlers etc.
|
handlers etc.
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@ controller object that can be used to connect to the database.
|
||||||
The Extension Code
|
The Extension Code
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Here the contents of the `flaskext/sqlite3.py` for copy/paste::
|
Here's the contents of the `flaskext/sqlite3.py` for copy/paste::
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
@ -196,7 +196,7 @@ Here the contents of the `flaskext/sqlite3.py` for copy/paste::
|
||||||
g.sqlite3_db.close()
|
g.sqlite3_db.close()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
So here what the lines of code do:
|
So here's what the lines of code do:
|
||||||
|
|
||||||
1. the ``__future__`` import is necessary to activate absolute imports.
|
1. the ``__future__`` import is necessary to activate absolute imports.
|
||||||
This is needed because otherwise we could not call our module
|
This is needed because otherwise we could not call our module
|
||||||
|
|
@ -237,7 +237,7 @@ If you don't need that, you can go with initialization functions.
|
||||||
Initialization Functions
|
Initialization Functions
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
Here how the module would look like with initialization functions::
|
Here's what the module would look like with initialization functions::
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
@ -264,9 +264,60 @@ development. If you want to learn more, it's a very good idea to check
|
||||||
out existing extensions on the `Flask Extension Registry`_. If you feel
|
out existing extensions on the `Flask Extension Registry`_. If you feel
|
||||||
lost there is still the `mailinglist`_ and the `IRC channel`_ to get some
|
lost there is still the `mailinglist`_ and the `IRC channel`_ to get some
|
||||||
ideas for nice looking APIs. Especially if you do something nobody before
|
ideas for nice looking APIs. Especially if you do something nobody before
|
||||||
you did, it might be a very good idea to get some more input.
|
you did, it might be a very good idea to get some more input. This not
|
||||||
|
only to get an idea about what people might want to have from an
|
||||||
|
extension, but also to avoid having multiple developers working on pretty
|
||||||
|
much the same side by side.
|
||||||
|
|
||||||
Remember: good API design is hard :(
|
Remember: good API design is hard, so introduce your project on the
|
||||||
|
mailinglist, and let other developers give you a helping hand with
|
||||||
|
designing the API.
|
||||||
|
|
||||||
|
The best Flask extensions are extensions that share common idioms for the
|
||||||
|
API. And this can only work if collaboration happens early.
|
||||||
|
|
||||||
|
|
||||||
|
Approved Extensions
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Flask also has the concept of approved extensions. Approved extensions
|
||||||
|
are tested as part of Flask itself to ensure extensions do not break on
|
||||||
|
new releases. These approved extensions are listed on the `Flask
|
||||||
|
Extension Registry`_ and marked appropriately. If you want your own
|
||||||
|
extension to be approved you have to follow these guidelines:
|
||||||
|
|
||||||
|
1. An approved Flask extension must provide exactly one package or module
|
||||||
|
inside the `flaskext` namespace package.
|
||||||
|
2. It must ship a testsuite that can either be invoked with ``make test``
|
||||||
|
or ``python setup.py test``. For testsuites invoked with ``make
|
||||||
|
test`` the extension has to ensure that all dependencies for the test
|
||||||
|
are installed automatically, in case of ``python setup.py test``
|
||||||
|
dependencies for tests alone can be specified in the `setup.py`
|
||||||
|
file. The testsuite also has to be part of the distribution.
|
||||||
|
3. APIs of approved extensions will be checked for the following
|
||||||
|
characteristics:
|
||||||
|
|
||||||
|
- an approved extension has to support multiple applications
|
||||||
|
running in the same Python process.
|
||||||
|
- it must be possible to use the factory pattern for creating
|
||||||
|
applications.
|
||||||
|
|
||||||
|
4. The license must be BSD/MIT/WTFPL licensed.
|
||||||
|
5. The naming scheme for official extensions is *Flask-ExtensionName* or
|
||||||
|
*ExtensionName-Flask*.
|
||||||
|
6. Approved extensions must define all their dependencies in the
|
||||||
|
`setup.py` file unless a dependency cannot be met because it is not
|
||||||
|
available on PyPI.
|
||||||
|
7. The extension must have documentation that uses one of the two Flask
|
||||||
|
themes for Sphinx documentation.
|
||||||
|
8. The setup.py description (and thus the PyPI description) has to
|
||||||
|
link to the documentation, website (if there is one) and there
|
||||||
|
must be a link to automatically install the development version
|
||||||
|
(``PackageName==dev``).
|
||||||
|
9. The ``zip_safe`` flag in the setup script must be set to ``False``,
|
||||||
|
even if the extension would be safe for zipping.
|
||||||
|
10. An extension currently has to support Python 2.5, 2.6 as well as
|
||||||
|
Python 2.7
|
||||||
|
|
||||||
|
|
||||||
.. _Flask Extension Wizard:
|
.. _Flask Extension Wizard:
|
||||||
|
|
|
||||||
|
|
@ -2,90 +2,108 @@ Foreword
|
||||||
========
|
========
|
||||||
|
|
||||||
Read this before you get started with Flask. This hopefully answers some
|
Read this before you get started with Flask. This hopefully answers some
|
||||||
questions about the intention of the project, what it aims at and when you
|
questions about the purpose and goals of the project, and when you
|
||||||
should or should not be using it.
|
should or should not be using it.
|
||||||
|
|
||||||
What does Micro Mean?
|
What does "micro" mean?
|
||||||
---------------------
|
-----------------------
|
||||||
|
|
||||||
The micro in microframework for me means on the one hand being small in
|
To me, the "micro" in microframework refers not only to the simplicity and
|
||||||
size and complexity but on the other hand also that the complexity of the
|
small size of the framework, but also to the typically limited complexity
|
||||||
applications that are written with these frameworks do not exceed a
|
and size of applications that are written with the framework. Also the
|
||||||
certain size. A microframework like Flask sacrifices a few things in
|
fact that you can have an entire application in a single Python file. To
|
||||||
order to be approachable and to be as concise as possible.
|
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 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
|
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
|
order to stay threadsafe. While this is a really easy approach and saves
|
||||||
you a lot of time, it also does not scale well to large applications.
|
you a lot of time, it might also cause some troubles for very large
|
||||||
It's especially painful for more complex unittests and when you suddenly
|
applications because changes on these thread-local objects can happen
|
||||||
have to deal with code being executed outside of the context of a request
|
anywhere in the same thread.
|
||||||
(for example if you have cronjobs).
|
|
||||||
|
|
||||||
Flask provides some tools to deal with the downsides of this approach but
|
Flask provides some tools to deal with the downsides of this approach but
|
||||||
the core problem of this approach obviously stays. It is also based on
|
it might be an issue for larger applications because in theory
|
||||||
convention over configuration which means that a lot of things are
|
modifications on these objects might happen anywhere in the same thread.
|
||||||
preconfigured in Flask and will work well for smaller applications but not
|
|
||||||
so much for larger ones (where and how it looks for templates, static
|
|
||||||
files etc.)
|
|
||||||
|
|
||||||
But don't worry if your application suddenly grows larger than it was
|
Flask is also based on convention over configuration, which means that
|
||||||
initially and you're afraid Flask might not grow with it. Even with
|
many things are preconfigured. For example, by convention, templates and
|
||||||
larger frameworks you sooner or later will find out that you need
|
static files are in subdirectories within the Python source tree of the
|
||||||
something the framework just cannot do for you without modification.
|
application.
|
||||||
If you are ever in that situation, check out the :ref:`becomingbig`
|
|
||||||
chapter.
|
|
||||||
|
|
||||||
A Framework and An Example
|
The main reason however why Flask is called a "microframework" is the idea
|
||||||
|
to keep the core simple but extensible. There is database abstraction
|
||||||
|
layer, no form validation or anything else where different libraries
|
||||||
|
already exist that can handle that. However Flask knows the concept of
|
||||||
|
extensions that can add this functionality into your application as if it
|
||||||
|
was implemented in Flask itself. There are currently extensions for
|
||||||
|
object relational mappers, form validation, upload handling, various open
|
||||||
|
authentication technologies and more.
|
||||||
|
|
||||||
|
However Flask is not much code and it is built on a very solid foundation
|
||||||
|
and with that it is very easy to adapt for large applications. If you are
|
||||||
|
interested in that, check out the :ref:`becomingbig` chapter.
|
||||||
|
|
||||||
|
If you are curious about the Flask design principles, head over to the
|
||||||
|
section about :ref:`design`.
|
||||||
|
|
||||||
|
A Framework and an Example
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
Flask is not only a microframework, it is also an example. Based on
|
Flask 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
|
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
|
framework. Flask itself is just one way to implement a framework on top
|
||||||
of existing libraries. Unlike many other microframeworks Flask does not
|
of existing libraries. Unlike many other microframeworks, Flask does not
|
||||||
try to implement anything on its own, it reuses existing code.
|
try to implement everything on its own; it reuses existing code.
|
||||||
|
|
||||||
Web Development is Dangerous
|
Web Development is Dangerous
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
I'm not even joking. Well, maybe a little. If you write a web
|
I'm not joking. Well, maybe a little. If you write a web
|
||||||
application you are probably allowing users to register and leave their
|
application, you are probably allowing users to register and leave their
|
||||||
data on your server. The users are entrusting you with data. And even if
|
data on your server. The users are entrusting you with data. And even if
|
||||||
you are the only user that might leave data in your application, you still
|
you are the only user that might leave data in your application, you still
|
||||||
want that data to be stored in a secure manner.
|
want that data to be stored securely.
|
||||||
|
|
||||||
Unfortunately there are many ways security of a web application can be
|
Unfortunately, there are many ways the security of a web application can be
|
||||||
compromised. Flask protects you against one of the most common security
|
compromised. Flask protects you against one of the most common security
|
||||||
problems of modern web applications: cross site scripting (XSS). Unless
|
problems of modern web applications: cross-site scripting (XSS). Unless
|
||||||
you deliberately mark insecure HTML as secure Flask (and the underlying
|
you deliberately mark insecure HTML as secure, Flask and the underlying
|
||||||
Jinja2 template engine) have you covered. But there are many more ways to
|
Jinja2 template engine have you covered. But there are many more ways to
|
||||||
cause security problems.
|
cause security problems.
|
||||||
|
|
||||||
Whenever something is dangerous where you have to watch out, the
|
The documentation will warn you about aspects of web development that
|
||||||
documentation will tell you so. Some of the security concerns of web
|
require attention to security. Some of these security concerns
|
||||||
development are far more complex than one might think and often we all end
|
are far more complex than one might think, and we all sometimes underestimate
|
||||||
up in situations where we think "well, this is just far fetched, how could
|
the likelihood that a vulnerability will be exploited, until a clever
|
||||||
that possibly be exploited" and then an intelligent guy comes along and
|
attacker figures out a way to exploit our applications. And don't think
|
||||||
figures a way out to exploit that application. And don't think, your
|
that your application is not important enough to attract an attacker.
|
||||||
application is not important enough for hackers to take notice. Depending
|
Depending on the kind of attack, chances are that automated bots are
|
||||||
on the kind of attack, chances are there are automated botnets out there
|
probing for ways to fill your database with spam, links to malicious
|
||||||
trying to figure out how to fill your database with viagra advertisements.
|
software, and the like.
|
||||||
|
|
||||||
So always keep that in mind when doing web development.
|
So always keep security in mind when doing web development.
|
||||||
|
|
||||||
Target Audience
|
The Status of Python 3
|
||||||
---------------
|
----------------------
|
||||||
|
|
||||||
Is Flask for you? If your application small-ish and does not depend on
|
Currently the Python community is in the process of improving libraries to
|
||||||
too complex database structures, Flask is the Framework for you. It was
|
support the new iteration of the Python programming language.
|
||||||
designed from the ground up to be easy to use, based on established
|
Unfortunately there are a few problems with Python 3, namely the missing
|
||||||
principles, good intentions and on top of two established libraries in
|
consent on what WSGI for Python 3 should look like. These problems are
|
||||||
widespread usage. Recent versions of Flask scale nicely within reasonable
|
partially caused by changes in the language that went unreviewed for too
|
||||||
bounds and if you grow larger, you won't have any troubles adjusting Flask
|
long, also partially the ambitions of everyone involved to drive the WSGI
|
||||||
for your new application size.
|
standard forward.
|
||||||
|
|
||||||
If you suddenly discover that your application grows larger than
|
Because of that we strongly recommend against using Python 3 for web
|
||||||
originally intended, head over to the :ref:`becomingbig` section to see
|
development of any kind and wait until the WSGI situation is resolved.
|
||||||
some possible solutions for larger applications.
|
You will find a couple of frameworks and web libraries on PyPI that claim
|
||||||
|
Python 3 support, but this support is based on the broken WSGI
|
||||||
|
implementation provided by Python 3.0 and 3.1 which will most likely
|
||||||
|
change in the near future.
|
||||||
|
|
||||||
Satisfied? Then head over to the :ref:`installation`.
|
Werkzeug and Flask will be ported to Python 3 as soon as a solution for
|
||||||
|
WSGI is found, and we will provide helpful tips how to upgrade existing
|
||||||
|
applications to Python 3. Until then, we strongly recommend using Python
|
||||||
|
2.6 and 2.7 with activated Python 3 warnings during development, as well
|
||||||
|
as the unicode literals `__future__` feature.
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ supported by browsers:
|
||||||
- Wrapping the document in an ``<html>`` tag
|
- Wrapping the document in an ``<html>`` tag
|
||||||
- Wrapping header elements in ``<head>`` or the body elements in
|
- Wrapping header elements in ``<head>`` or the body elements in
|
||||||
``<body>``
|
``<body>``
|
||||||
- Closing the ``<p>``, ``<li>``, ``<dl>``, ``<dd>``, ``<tr>``,
|
- Closing the ``<p>``, ``<li>``, ``<dt>``, ``<dd>``, ``<tr>``,
|
||||||
``<td>``, ``<th>``, ``<tbody>``, ``<thead>``, or ``<tfoot>`` tags.
|
``<td>``, ``<th>``, ``<tbody>``, ``<thead>``, or ``<tfoot>`` tags.
|
||||||
- Quoting attributes, so long as they contain no whitespace or
|
- Quoting attributes, so long as they contain no whitespace or
|
||||||
special characters (like ``<``, ``>``, ``'``, or ``"``).
|
special characters (like ``<``, ``>``, ``'``, or ``"``).
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,20 @@ Welcome to Flask
|
||||||
================
|
================
|
||||||
|
|
||||||
.. image:: _static/logo-full.png
|
.. image:: _static/logo-full.png
|
||||||
:alt: The Flask Logo with Subtitle
|
:alt: Flask: web development, one drop at a time
|
||||||
:class: floatingflask
|
:class: floatingflask
|
||||||
|
|
||||||
Welcome to Flask's documentation. This documentation is divided in
|
Welcome to Flask's documentation. This documentation is divided into
|
||||||
different parts. I would suggest to get started with the
|
different parts. I recommend that you get started with
|
||||||
:ref:`installation` and then heading over to the :ref:`quickstart`.
|
:ref:`installation` and then head over to the :ref:`quickstart`.
|
||||||
Besides the quickstart there is also a more detailed :ref:`tutorial` that
|
Besides the quickstart there is also a more detailed :ref:`tutorial` that
|
||||||
shows how to create a complete (albeit small) application with Flask. If
|
shows how to create a complete (albeit small) application with Flask. If
|
||||||
you rather want to dive into all the internal parts of Flask, check out
|
you'd rather dive into the internals of Flask, check out
|
||||||
the :ref:`api` documentation. Common patterns are described in the
|
the :ref:`api` documentation. Common patterns are described in the
|
||||||
:ref:`patterns` section.
|
:ref:`patterns` section.
|
||||||
|
|
||||||
Flask also depends on two external libraries: the `Jinja2`_ template
|
Flask depends on two external libraries: the `Jinja2`_ template
|
||||||
engine and the `Werkzeug`_ WSGI toolkit. both of which are not documented
|
engine and the `Werkzeug`_ WSGI toolkit. These libraries are not documented
|
||||||
here. If you want to dive into their documentation check out the
|
here. If you want to dive into their documentation check out the
|
||||||
following links:
|
following links:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,44 +3,46 @@
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
Flask is a microframework and yet it depends on external libraries. There
|
Flask depends on two external libraries, `Werkzeug
|
||||||
are various ways how you can install that library and this explains each
|
|
||||||
way and why there are multiple ways.
|
|
||||||
|
|
||||||
Flask depends on two external libraries: `Werkzeug
|
|
||||||
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/2/>`_.
|
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/2/>`_.
|
||||||
The first one is responsible for interfacing WSGI the latter for rendering
|
Werkzeug is a toolkit for WSGI, the standard Python interface between web
|
||||||
templates. Now you are maybe asking, what is WSGI? WSGI is a standard
|
applications and a variety of servers for both development and deployment.
|
||||||
in Python that is basically responsible for ensuring that your application
|
Jinja2 renders templates.
|
||||||
is behaving in a specific way so that you can run it on different
|
|
||||||
environments (for example on a local development server, on an Apache2, on
|
|
||||||
lighttpd, on Google's App Engine or whatever you have in mind).
|
|
||||||
|
|
||||||
So how do you get all that on your computer in no time? The most kick-ass
|
So how do you get all that on your computer quickly? There are many ways
|
||||||
method is virtualenv, so let's look at that first.
|
which this section will explain, but the most kick-ass method is
|
||||||
|
virtualenv, so let's look at that first.
|
||||||
|
|
||||||
|
Either way, you will need Python 2.5 or higher to get started, so be sure
|
||||||
|
to have an up to date Python 2.x installation. At the time of writing,
|
||||||
|
the WSGI specification is not yet finalized for Python 3, so Flask cannot
|
||||||
|
support the 3.x series of Python.
|
||||||
|
|
||||||
.. _virtualenv:
|
.. _virtualenv:
|
||||||
|
|
||||||
virtualenv
|
virtualenv
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Virtualenv is what you want to use during development and in production if
|
Virtualenv is probably what you want to use during development, and in
|
||||||
you have shell access. So first: what does virtualenv do? If you are
|
production too if you have shell access there.
|
||||||
like me and you like Python, chances are you want to use it for another
|
|
||||||
project as well. Now the more projects you have, the more likely it is
|
|
||||||
that you will be working with different versions of Python itself or at
|
|
||||||
least an individual library. Because let's face it: quite often libraries
|
|
||||||
break backwards compatibility and it's unlikely that your application will
|
|
||||||
not have any dependencies, that just won't happen. So virtualenv to the
|
|
||||||
rescue!
|
|
||||||
|
|
||||||
It basically makes it possible to have multiple side-by-side
|
What problem does virtualenv solve? If you like Python as I do,
|
||||||
"installations" of Python, each for your own project. It's not actually
|
chances are you want to use it for other projects besides Flask-based
|
||||||
an installation but a clever way to keep things separated.
|
web applications. But the more projects you have, the more likely it is
|
||||||
|
that you will be working with different versions of Python itself, or at
|
||||||
|
least different versions of Python libraries. Let's face it; quite often
|
||||||
|
libraries break backwards compatibility, and it's unlikely that any serious
|
||||||
|
application will have zero dependencies. So what do you do if two or more
|
||||||
|
of your projects have conflicting dependencies?
|
||||||
|
|
||||||
So let's see how that works!
|
Virtualenv to the rescue! It basically enables multiple side-by-side
|
||||||
|
installations of Python, one for each project. It doesn't actually
|
||||||
|
install separate copies of Python, but it does provide a clever way
|
||||||
|
to keep different project environments isolated.
|
||||||
|
|
||||||
If you are on OS X or Linux chances are that one of the following two
|
So let's see how virtualenv works!
|
||||||
|
|
||||||
|
If you are on Mac OS X or Linux, chances are that one of the following two
|
||||||
commands will work for you::
|
commands will work for you::
|
||||||
|
|
||||||
$ sudo easy_install virtualenv
|
$ sudo easy_install virtualenv
|
||||||
|
|
@ -49,18 +51,19 @@ or even better::
|
||||||
|
|
||||||
$ sudo pip install virtualenv
|
$ sudo pip install virtualenv
|
||||||
|
|
||||||
Chances are you have virtualenv installed on your system then. Maybe it's
|
One of these will probably install virtualenv on your system. Maybe it's
|
||||||
even in your package manager (on ubuntu try ``sudo apt-get install
|
even in your package manager. If you use Ubuntu, try::
|
||||||
python-virtualenv``).
|
|
||||||
|
|
||||||
If you are on Windows and missing the `easy_install` command you have to
|
$ sudo apt-get install python-virtualenv
|
||||||
|
|
||||||
|
If you are on Windows and don't have the `easy_install` command, you must
|
||||||
install it first. Check the :ref:`windows-easy-install` section for more
|
install it first. Check the :ref:`windows-easy-install` section for more
|
||||||
information about how to do that. Once you have it installed, run the
|
information about how to do that. Once you have it installed, run the
|
||||||
same commands as above, but without the `sudo` part.
|
same commands as above, but without the `sudo` prefix.
|
||||||
|
|
||||||
So now that you have virtualenv running just fire up a shell and create
|
Once you have virtualenv installed, just fire up a shell and create
|
||||||
your own environment. I usually create a folder and a `env` folder
|
your own environment. I usually create a project folder and an `env`
|
||||||
within::
|
folder within::
|
||||||
|
|
||||||
$ mkdir myproject
|
$ mkdir myproject
|
||||||
$ cd myproject
|
$ cd myproject
|
||||||
|
|
@ -68,14 +71,14 @@ within::
|
||||||
New python executable in env/bin/python
|
New python executable in env/bin/python
|
||||||
Installing setuptools............done.
|
Installing setuptools............done.
|
||||||
|
|
||||||
Now you only have to activate it, whenever you work with it. On OS X and
|
Now, whenever you want to work on a project, you only have to activate
|
||||||
Linux do the following::
|
the corresponding environment. On OS X and Linux, do the following::
|
||||||
|
|
||||||
$ . env/bin/activate
|
$ . env/bin/activate
|
||||||
|
|
||||||
(Note the whitespace between the dot and the script name. This means
|
(Note the space between the dot and the script name. The dot means that
|
||||||
execute this file in context of the shell. If the dot does not work for
|
this script should run in the context of the current shell. If this command
|
||||||
whatever reason in your shell, try substituting it with ``source``)
|
does not work in your shell, try replacing the dot with ``source``)
|
||||||
|
|
||||||
If you are a Windows user, the following command is for you::
|
If you are a Windows user, the following command is for you::
|
||||||
|
|
||||||
|
|
@ -95,23 +98,22 @@ A few seconds later you are good to go.
|
||||||
System Wide Installation
|
System Wide Installation
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
This is possible as well, but I would not recommend it. Just run
|
This is possible as well, but I do not recommend it. Just run
|
||||||
`easy_install` with root rights::
|
`easy_install` with root rights::
|
||||||
|
|
||||||
sudo easy_install Flask
|
$ sudo easy_install Flask
|
||||||
|
|
||||||
(Run it in an Admin shell on Windows systems and without the `sudo`).
|
(Run it in an Admin shell on Windows systems and without `sudo`).
|
||||||
|
|
||||||
|
|
||||||
Living on the Edge
|
Living on the Edge
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
You want to work with the latest version of Flask, there are two ways: you
|
If you want to work with the latest version of Flask, there are two ways: you
|
||||||
can either let `easy_install` pull in the development version or tell it
|
can either let `easy_install` pull in the development version, or tell it
|
||||||
to operate on a git checkout. Either way it's recommended to do that in a
|
to operate on a git checkout. Either way, virtualenv is recommended.
|
||||||
virtualenv.
|
|
||||||
|
|
||||||
Get the git checkout in a new virtualenv and run in develop mode::
|
Get the git checkout in a new virtualenv and run in development mode::
|
||||||
|
|
||||||
$ git clone http://github.com/mitsuhiko/flask.git
|
$ git clone http://github.com/mitsuhiko/flask.git
|
||||||
Initialized empty Git repository in ~/dev/flask/.git/
|
Initialized empty Git repository in ~/dev/flask/.git/
|
||||||
|
|
@ -124,9 +126,9 @@ Get the git checkout in a new virtualenv and run in develop mode::
|
||||||
...
|
...
|
||||||
Finished processing dependencies for Flask
|
Finished processing dependencies for Flask
|
||||||
|
|
||||||
This will pull in the dependencies and activate the git head as current
|
This will pull in the dependencies and activate the git head as the current
|
||||||
version. Then you just have to ``git pull origin`` to get the latest
|
version inside the virtualenv. Then you just have to ``git pull origin``
|
||||||
version.
|
to get the latest version.
|
||||||
|
|
||||||
To just get the development version without git, do this instead::
|
To just get the development version without git, do this instead::
|
||||||
|
|
||||||
|
|
@ -145,31 +147,29 @@ To just get the development version without git, do this instead::
|
||||||
`easy_install` on Windows
|
`easy_install` on Windows
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
On Windows installation of `easy_install` is a little bit tricker because
|
On Windows, installation of `easy_install` is a little bit tricker because
|
||||||
on Windows slightly different rules apply, but it's not a biggy. The
|
slightly different rules apply on Windows than on Unix-like systems, but
|
||||||
easiest way to accomplish that is downloading the `ez_setup.py`_ file and
|
it's not difficult. The easiest way to do it is to download the
|
||||||
running it. (Double clicking should do the trick)
|
`ez_setup.py`_ file and run it. The easiest way to run the file is to
|
||||||
|
open your downloads folder and double-click on the file.
|
||||||
|
|
||||||
Once you have done that it's important to add the `easy_install` command
|
Next, add the `easy_install` command and other Python scripts to the
|
||||||
and other Python scripts to the path. To do that you have to add the
|
command search path, by adding your Python installation's Scripts folder
|
||||||
Python installation's Script folder to the `PATH` variable.
|
to the `PATH` environment variable. To do that, right-click on the
|
||||||
|
"Computer" icon on the Desktop or in the Start menu, and choose
|
||||||
To do that, right-click on your "Computer" desktop icon and click
|
"Properties". Then, on Windows Vista and Windows 7 click on "Advanced System
|
||||||
"Properties". On Windows Vista and Windows 7 then click on "Advanced System
|
settings"; on Windows XP, click on the "Advanced" tab instead. Then click
|
||||||
settings", on Windows XP click on the "Advanced" tab instead. Then click
|
|
||||||
on the "Environment variables" button and double click on the "Path"
|
on the "Environment variables" button and double click on the "Path"
|
||||||
variable in the "System variables" section.
|
variable in the "System variables" section. There append the path of your
|
||||||
|
Python interpreter's Scripts folder; make sure you delimit it from
|
||||||
There append the path of your Python interpreter's Script folder to the
|
existing values with a semicolon. Assuming you are using Python 2.6 on
|
||||||
end of the last (make sure you delimit it from existing values with a
|
the default path, add the following value::
|
||||||
semicolon). Assuming you are using Python 2.6 on the default path, add
|
|
||||||
the following value::
|
|
||||||
|
|
||||||
;C:\Python26\Scripts
|
;C:\Python26\Scripts
|
||||||
|
|
||||||
Then you are done. To check that it worked, open the cmd and execute
|
Then you are done. To check that it worked, open the Command Prompt and
|
||||||
"easy_install". If you have UAC enabled it should prompt you for admin
|
execute ``easy_install``. If you have User Account Control enabled on
|
||||||
privileges.
|
Windows Vista or Windows 7, it should prompt you for admin privileges.
|
||||||
|
|
||||||
|
|
||||||
.. _ez_setup.py: http://peak.telecommunity.com/dist/ez_setup.py
|
.. _ez_setup.py: http://peak.telecommunity.com/dist/ez_setup.py
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ License
|
||||||
Flask is licensed under a three clause BSD License. It basically means:
|
Flask is licensed under a three clause BSD License. It basically means:
|
||||||
do whatever you want with it as long as the copyright in Flask sticks
|
do whatever you want with it as long as the copyright in Flask sticks
|
||||||
around, the conditions are not modified and the disclaimer is present.
|
around, the conditions are not modified and the disclaimer is present.
|
||||||
Furthermore you must not use the names of the authors to promote derivates
|
Furthermore you must not use the names of the authors to promote derivatives
|
||||||
of the software without written consent.
|
of the software without written consent.
|
||||||
|
|
||||||
The full license text can be found below (:ref:`flask-license`). For the
|
The full license text can be found below (:ref:`flask-license`). For the
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@ not supported by `distribute`_ so we will not bother with it. If you have
|
||||||
not yet converted your application into a package, head over to the
|
not yet converted your application into a package, head over to the
|
||||||
:ref:`larger-applications` pattern to see how this can be done.
|
:ref:`larger-applications` pattern to see how this can be done.
|
||||||
|
|
||||||
|
A working deployment with distribute is the first step into more complex
|
||||||
|
and more automated deployment scenarios. If you want to fully automate
|
||||||
|
the process, also read the :ref:`fabric-deployment` chapter.
|
||||||
|
|
||||||
Basic Setup Script
|
Basic Setup Script
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Flask comes with a handy :func:`~flask.abort` function that aborts a
|
||||||
request with an HTTP error code early. It will also provide a plain black
|
request with an HTTP error code early. It will also provide a plain black
|
||||||
and white error page for you with a basic description, but nothing fancy.
|
and white error page for you with a basic description, but nothing fancy.
|
||||||
|
|
||||||
Depening on the error code it is less or more likely for the user to
|
Depending on the error code it is less or more likely for the user to
|
||||||
actually see such an error.
|
actually see such an error.
|
||||||
|
|
||||||
Common Error Codes
|
Common Error Codes
|
||||||
|
|
@ -33,7 +33,7 @@ even if the application behaves correctly:
|
||||||
instead of 404. If you are not deleting documents permanently from
|
instead of 404. If you are not deleting documents permanently from
|
||||||
the database but just mark them as deleted, do the user a favour and
|
the database but just mark them as deleted, do the user a favour and
|
||||||
use the 410 code instead and display a message that what he was
|
use the 410 code instead and display a message that what he was
|
||||||
looking for was deleted for all ethernity.
|
looking for was deleted for all eternity.
|
||||||
|
|
||||||
*500 Internal Server Error*
|
*500 Internal Server Error*
|
||||||
Usually happens on programming errors or if the server is overloaded.
|
Usually happens on programming errors or if the server is overloaded.
|
||||||
|
|
@ -49,7 +49,7 @@ An error handler is a function, just like a view function, but it is
|
||||||
called when an error happens and is passed that error. The error is most
|
called when an error happens and is passed that error. The error is most
|
||||||
likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it
|
likely a :exc:`~werkzeug.exceptions.HTTPException`, but in one case it
|
||||||
can be a different error: a handler for internal server errors will be
|
can be a different error: a handler for internal server errors will be
|
||||||
passed other exception instances as well if they are uncought.
|
passed other exception instances as well if they are uncaught.
|
||||||
|
|
||||||
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
An error handler is registered with the :meth:`~flask.Flask.errorhandler`
|
||||||
decorator and the error code of the exception. Keep in mind that Flask
|
decorator and the error code of the exception. Keep in mind that Flask
|
||||||
|
|
|
||||||
196
docs/patterns/fabric.rst
Normal file
196
docs/patterns/fabric.rst
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
.. _fabric-deployment:
|
||||||
|
|
||||||
|
Deploying with Fabric
|
||||||
|
=====================
|
||||||
|
|
||||||
|
`Fabric`_ is a tool for Python similar to Makefiles but with the ability
|
||||||
|
to execute commands on a remote server. In combination with a properly
|
||||||
|
set up Python package (:ref:`larger-applications`) and a good concept for
|
||||||
|
configurations (:ref:`config`) it is very easy to deploy Flask
|
||||||
|
applications to external servers.
|
||||||
|
|
||||||
|
Before we get started, here a quick checklist of things we have to ensure
|
||||||
|
upfront:
|
||||||
|
|
||||||
|
- Fabric 1.0 has to be installed locally. This tutorial assumes the
|
||||||
|
latest version of Fabric.
|
||||||
|
- The application already has to be a package and requires a working
|
||||||
|
`setup.py` file (:ref:`distribute-deployment`).
|
||||||
|
- In the following example we are using `mod_wsgi` for the remote
|
||||||
|
servers. You can of course use your own favourite server there, but
|
||||||
|
for this example we chose Apache + `mod_wsgi` because it's very easy
|
||||||
|
to setup and has a simple way to reload applications without root
|
||||||
|
access.
|
||||||
|
|
||||||
|
Creating the first Fabfile
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
A fabfile is what controls what Fabric executes. It is named `fabfile.py`
|
||||||
|
and executed by the `fab` command. All the functions defined in that file
|
||||||
|
will show up as `fab` subcommands. They are executed on one or more
|
||||||
|
hosts. These hosts can be defined either in the fabfile or on the command
|
||||||
|
line. In this case we will add them to the fabfile.
|
||||||
|
|
||||||
|
This is a basic first example that has the ability to upload the current
|
||||||
|
sourcecode to the server and install it into a already existing
|
||||||
|
virtual environment::
|
||||||
|
|
||||||
|
from fabric.api import *
|
||||||
|
|
||||||
|
# the user to use for the remote commands
|
||||||
|
env.user = 'appuser'
|
||||||
|
# the servers where the commands are executed
|
||||||
|
env.hosts = ['server1.example.com', 'server2.example.com']
|
||||||
|
|
||||||
|
def pack():
|
||||||
|
# create a new source distribution as tarball
|
||||||
|
local('python setup.py sdist --formats=gztar', capture=False)
|
||||||
|
|
||||||
|
def deploy():
|
||||||
|
# figure out the release name and version
|
||||||
|
dist = local('python setup.py --fullname').strip()
|
||||||
|
# upload the source tarball to the temporary folder on the server
|
||||||
|
put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz')
|
||||||
|
# create a place where we can unzip the tarball, then enter
|
||||||
|
# that directory and unzip it
|
||||||
|
run('mkdir yourapplication')
|
||||||
|
with cd('/tmp/yourapplication'):
|
||||||
|
run('tar xzf /tmp/yourapplication.tar.gz')
|
||||||
|
# now setup the package with our virtual environment's
|
||||||
|
# python interpreter
|
||||||
|
run('/var/www/yourapplication/env/bin/python setup.py install')
|
||||||
|
# now that all is set up, delete the folder again
|
||||||
|
run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz')
|
||||||
|
# and finally touch the .wsgi file so that mod_wsgi triggers
|
||||||
|
# a reload of the application
|
||||||
|
run('touch /var/www/yourapplication.wsgi')
|
||||||
|
|
||||||
|
The example above is well documented and should be straightforward. Here
|
||||||
|
a recap of the most common commands fabric provides:
|
||||||
|
|
||||||
|
- `run` - executes a command on a remote server
|
||||||
|
- `local` - executes a command on the local machine
|
||||||
|
- `put` - uploads a file to the remote server
|
||||||
|
- `cd` - changes the directory on the serverside. This has to be used
|
||||||
|
in combination with the `with` statement.
|
||||||
|
|
||||||
|
Running Fabfiles
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Now how do you execute that fabfile? You use the `fab` command. To
|
||||||
|
deploy the current version of the code on the remote server you would use
|
||||||
|
this command::
|
||||||
|
|
||||||
|
$ fab pack deploy
|
||||||
|
|
||||||
|
However this requires that our server already has the
|
||||||
|
``/var/www/yourapplication`` folder created and
|
||||||
|
``/var/www/yourapplication/env`` to be a virtual environment. Furthermore
|
||||||
|
are we not creating the configuration or `.wsgi` file on the server. So
|
||||||
|
how do we bootstrap a new server into our infrastructure?
|
||||||
|
|
||||||
|
This now depends on the number of servers we want to set up. If we just
|
||||||
|
have one application server (which the majority of applications will
|
||||||
|
have), creating a command in the fabfile for this is overkill. But
|
||||||
|
obviously you can do that. In that case you would probably call it
|
||||||
|
`setup` or `bootstrap` and then pass the servername explicitly on the
|
||||||
|
command line::
|
||||||
|
|
||||||
|
$ fab -H newserver.example.com bootstrap
|
||||||
|
|
||||||
|
To setup a new server you would roughly do these steps:
|
||||||
|
|
||||||
|
1. Create the directory structure in ``/var/www``::
|
||||||
|
|
||||||
|
$ mkdir /var/www/yourapplication
|
||||||
|
$ cd /var/www/yourapplication
|
||||||
|
$ virtualenv --distribute env
|
||||||
|
|
||||||
|
2. Upload a new `application.wsgi` file to the server and the
|
||||||
|
configuration file for the application (eg: `application.cfg`)
|
||||||
|
|
||||||
|
3. Create a new Apache config for `yourapplication` and activate it.
|
||||||
|
Make sure to activate watching for changes of the `.wsgi` file so
|
||||||
|
that we can automatically reload the application by touching it.
|
||||||
|
(See :ref:`mod_wsgi-deployment` for more information)
|
||||||
|
|
||||||
|
So now the question is, where do the `application.wsgi` and
|
||||||
|
`application.cfg` files come from?
|
||||||
|
|
||||||
|
The WSGI File
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The WSGI file has to import the application and also to set an environment
|
||||||
|
variable so that the application knows where to look for the config. This
|
||||||
|
is a short example that does exactly that::
|
||||||
|
|
||||||
|
import os
|
||||||
|
os.environ['YOURAPPLICATION_CONFIG'] = '/var/www/yourapplication/application.cfg'
|
||||||
|
from yourapplication import app
|
||||||
|
|
||||||
|
The application itself then has to initialize itself like this to look for
|
||||||
|
the config at that environment variable::
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object('yourapplication.default_config')
|
||||||
|
app.config.from_envvar('YOURAPPLICATION_CONFIG')
|
||||||
|
|
||||||
|
This approach is explained in detail in the :ref:`config` section of the
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
The Configuration File
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Now as mentioned above, the application will find the correct
|
||||||
|
configuration file by looking up the `YOURAPPLICATION_CONFIG` environment
|
||||||
|
variable. So we have to put the configuration in a place where the
|
||||||
|
application will able to find it. Configuration files have the unfriendly
|
||||||
|
quality of being different on all computers, so you do not version them
|
||||||
|
usually.
|
||||||
|
|
||||||
|
A popular approach is to store configuration files for different servers
|
||||||
|
in a separate version control repository and check them out on all
|
||||||
|
servers. Then symlink the file that is active for the server into the
|
||||||
|
location where it's expected (eg: ``/var/www/yourapplication``).
|
||||||
|
|
||||||
|
Either way, in our case here we only expect one or two servers and we can
|
||||||
|
upload them ahead of time by hand.
|
||||||
|
|
||||||
|
First Deployment
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Now we can do our first deployment. We have set up the servers so that
|
||||||
|
they have their virtual environments and activated apache configs. Now we
|
||||||
|
can pack up the application and deploy it::
|
||||||
|
|
||||||
|
$ fab pack deploy
|
||||||
|
|
||||||
|
Fabric will now connect to all servers and run the commands as written
|
||||||
|
down in the fabfile. First it will execute pack so that we have our
|
||||||
|
tarball ready and then it will execute deploy and upload the source code
|
||||||
|
to all servers and install it there. Thanks to the `setup.py` file we
|
||||||
|
will automatically pull in the required libraries into our virtual
|
||||||
|
environment.
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
----------
|
||||||
|
|
||||||
|
From that point onwards there is so much that can be done to make
|
||||||
|
deployment actually fun:
|
||||||
|
|
||||||
|
- Create a `bootstrap` command that initializes new servers. It could
|
||||||
|
initialize a new virtual environment, setup apache appropriately etc.
|
||||||
|
- Put configuration files into a separate version control repository
|
||||||
|
and symlink the active configs into place.
|
||||||
|
- You could also put your application code into a repository and check
|
||||||
|
out the latest version on the server and then install. That way you
|
||||||
|
can also easily go back to older versions.
|
||||||
|
- hook in testing functionality so that you can deploy to an external
|
||||||
|
server and run the testsuite.
|
||||||
|
|
||||||
|
Working with Fabric is fun and you will notice that it's quite magical to
|
||||||
|
type ``fab deploy`` and see your application being deployed automatically
|
||||||
|
to one or more remote servers.
|
||||||
|
|
||||||
|
|
||||||
|
.. _Fabric: http://fabfile.org/
|
||||||
|
|
@ -28,8 +28,6 @@ bootstrapping code for our application::
|
||||||
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
|
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.add_url_rule('/uploads/<filename>', 'uploaded_file',
|
|
||||||
build_only=True)
|
|
||||||
|
|
||||||
So first we need a couple of imports. Most should be straightforward, the
|
So first we need a couple of imports. Most should be straightforward, the
|
||||||
:func:`werkzeug.secure_filename` is explained a little bit later. The
|
:func:`werkzeug.secure_filename` is explained a little bit later. The
|
||||||
|
|
@ -43,9 +41,9 @@ the URL to these files.
|
||||||
Why do we limit the extensions that are allowed? You probably don't want
|
Why do we limit the extensions that are allowed? You probably don't want
|
||||||
your users to be able to upload everything there if the server is directly
|
your users to be able to upload everything there if the server is directly
|
||||||
sending out the data to the client. That way you can make sure that users
|
sending out the data to the client. That way you can make sure that users
|
||||||
are not able to upload HTML files that would cause XSS problems. Also
|
are not able to upload HTML files that would cause XSS problems (see
|
||||||
make sure to disallow `.php` files if the server executes them, but who
|
:ref:`xss`). Also make sure to disallow `.php` files if the server
|
||||||
has PHP installed on his server, right? :)
|
executes them, but who has PHP installed on his server, right? :)
|
||||||
|
|
||||||
Next the functions that check if an extension is valid and that uploads
|
Next the functions that check if an extension is valid and that uploads
|
||||||
the file and redirects the user to the URL for the uploaded file::
|
the file and redirects the user to the URL for the uploaded file::
|
||||||
|
|
@ -100,14 +98,23 @@ before storing it directly on the filesystem.
|
||||||
>>> secure_filename('../../../../home/username/.bashrc')
|
>>> secure_filename('../../../../home/username/.bashrc')
|
||||||
'home_username_.bashrc'
|
'home_username_.bashrc'
|
||||||
|
|
||||||
Now if we run that application, you will notice that uploading works, but
|
Now one last thing is missing: the serving of the uploaded files. As of
|
||||||
you won't actually see that uploaded file. Well, you would have to
|
Flask 0.5 we can use a function that does that for us::
|
||||||
configure the server to serve that file for you. This is not handy for
|
|
||||||
development situations or when you are just too lazy to properly set up
|
from flask import send_from_directory
|
||||||
the server. Would be nice to have the files still be available in that
|
|
||||||
situation, and that is really easy to do, just hook in a middleware::
|
@app.route('/uploads/<filename>')
|
||||||
|
def uploaded_file(filename):
|
||||||
|
return send_from_directory(app.config['UPLOAD_FOLDER'],
|
||||||
|
filename)
|
||||||
|
|
||||||
|
Alternatively you can register `uploaded_file` as `build_only` rule and
|
||||||
|
use the :class:`~werkzeug.SharedDataMiddleware`. This also works with
|
||||||
|
older versions of Flask::
|
||||||
|
|
||||||
from werkzeug import SharedDataMiddleware
|
from werkzeug import SharedDataMiddleware
|
||||||
|
app.add_url_rule('/uploads/<filename>', 'uploaded_file',
|
||||||
|
build_only=True)
|
||||||
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
|
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
|
||||||
'/uploads': UPLOAD_FOLDER
|
'/uploads': UPLOAD_FOLDER
|
||||||
})
|
})
|
||||||
|
|
@ -118,27 +125,29 @@ If you now run the application everything should work as expected.
|
||||||
Improving Uploads
|
Improving Uploads
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
.. versionadded:: 0.6
|
||||||
|
|
||||||
So how exactly does Flask handle uploads? Well it will store them in the
|
So how exactly does Flask handle uploads? Well it will store them in the
|
||||||
webserver's memory if the files are reasonable small otherwise in a
|
webserver's memory if the files are reasonable small otherwise in a
|
||||||
temporary location (as returned by :func:`tempfile.gettempdir`). But how
|
temporary location (as returned by :func:`tempfile.gettempdir`). But how
|
||||||
do you specify the maximum file size after which an upload is aborted? By
|
do you specify the maximum file size after which an upload is aborted? By
|
||||||
default Flask will happily accept file uploads to an unlimited amount of
|
default Flask will happily accept file uploads to an unlimited amount of
|
||||||
memory, but you can limit that by subclassing the request and overriding
|
memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH``
|
||||||
the Werkzeug provided :attr:`~werkzeug.BaseRequest.max_form_memory_size`
|
config key::
|
||||||
attribute::
|
|
||||||
|
|
||||||
from flask import Flask, Request
|
from flask import Flask, Request
|
||||||
|
|
||||||
class LimitedRequest(Request):
|
|
||||||
max_form_memory_size = 16 * 1024 * 1024
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.request_class = LimitedRequest
|
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
|
||||||
|
|
||||||
The code above will limited the maximum allowed payload to 16 megabytes.
|
The code above will limited the maximum allowed payload to 16 megabytes.
|
||||||
If a larger file is transmitted, Flask will raise an
|
If a larger file is transmitted, Flask will raise an
|
||||||
:exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception.
|
:exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception.
|
||||||
|
|
||||||
|
This feature was added in Flask 0.6 but can be achieved in older versions
|
||||||
|
as well by subclassing the request object. For more information on that
|
||||||
|
consult the Werkzeug documentation on file handling.
|
||||||
|
|
||||||
|
|
||||||
Upload Progress Bars
|
Upload Progress Bars
|
||||||
--------------------
|
--------------------
|
||||||
|
|
@ -158,3 +167,14 @@ following libraries for some nice examples how to do that:
|
||||||
- `Plupload <http://www.plupload.com/>`_ - HTML5, Java, Flash
|
- `Plupload <http://www.plupload.com/>`_ - HTML5, Java, Flash
|
||||||
- `SWFUpload <http://www.swfupload.org/>`_ - Flash
|
- `SWFUpload <http://www.swfupload.org/>`_ - Flash
|
||||||
- `JumpLoader <http://jumploader.com/>`_ - Java
|
- `JumpLoader <http://jumploader.com/>`_ - Java
|
||||||
|
|
||||||
|
|
||||||
|
An Easier Solution
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Because the common pattern for file uploads exists almost unchanged in all
|
||||||
|
applications dealing with uploads, there is a Flask extension called
|
||||||
|
`Flask-Uploads`_ that implements a full fledged upload mechanism with
|
||||||
|
white and blacklisting of extensions and more.
|
||||||
|
|
||||||
|
.. _Flask-Uploads: http://packages.python.org/Flask-Uploads/
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ template that does this.
|
||||||
Simple Flashing
|
Simple Flashing
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
So here a full example::
|
So here is a full example::
|
||||||
|
|
||||||
from flask import flash, redirect, url_for, render_template
|
from flask import flash, redirect, url_for, render_template
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ So here a full example::
|
||||||
request.form['password'] != 'secret':
|
request.form['password'] != 'secret':
|
||||||
error = 'Invalid credentials'
|
error = 'Invalid credentials'
|
||||||
else:
|
else:
|
||||||
flash('You were sucessfully logged in')
|
flash('You were successfully logged in')
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
return render_template('login.html', error=error)
|
return render_template('login.html', error=error)
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ to the :func:`~flask.flash` function::
|
||||||
|
|
||||||
Inside the template you then have to tell the
|
Inside the template you then have to tell the
|
||||||
:func:`~flask.get_flashed_messages` function to also return the
|
:func:`~flask.get_flashed_messages` function to also return the
|
||||||
categories. The loop looks slighty different in that situation then:
|
categories. The loop looks slightly different in that situation then:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_.
|
||||||
packages
|
packages
|
||||||
appfactories
|
appfactories
|
||||||
distribute
|
distribute
|
||||||
|
fabric
|
||||||
sqlite3
|
sqlite3
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
fileuploads
|
fileuploads
|
||||||
|
|
@ -30,3 +31,4 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_.
|
||||||
jquery
|
jquery
|
||||||
errorpages
|
errorpages
|
||||||
lazyloading
|
lazyloading
|
||||||
|
mongokit
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ Another method is using Google's `AJAX Libraries API
|
||||||
|
|
||||||
In this case you don't have to put jQuery into your static folder, it will
|
In this case you don't have to put jQuery into your static folder, it will
|
||||||
instead be loaded from Google directly. This has the advantage that your
|
instead be loaded from Google directly. This has the advantage that your
|
||||||
website will probably load faster for users if they were to at least one
|
website will probably load faster for users if they went to at least one
|
||||||
other website before using the same jQuery version from Google because it
|
other website before using the same jQuery version from Google because it
|
||||||
will already be in the browser cache. Downside is that if you don't have
|
will already be in the browser cache. Downside is that if you don't have
|
||||||
network connectivity during development jQuery will not load.
|
network connectivity during development jQuery will not load.
|
||||||
|
|
@ -53,8 +53,8 @@ is quite simple: it's on localhost port something and directly on the root
|
||||||
of that server. But what if you later decide to move your application to
|
of that server. But what if you later decide to move your application to
|
||||||
a different location? For example to ``http://example.com/myapp``? On
|
a different location? For example to ``http://example.com/myapp``? On
|
||||||
the server side this never was a problem because we were using the handy
|
the server side this never was a problem because we were using the handy
|
||||||
:func:`~flask.url_for` function that did could answer that question for
|
:func:`~flask.url_for` function that could answer that question for
|
||||||
us, but if we are using jQuery we should better not hardcode the path to
|
us, but if we are using jQuery we should not hardcode the path to
|
||||||
the application but make that dynamic, so how can we do that?
|
the application but make that dynamic, so how can we do that?
|
||||||
|
|
||||||
A simple method would be to add a script tag to our page that sets a
|
A simple method would be to add a script tag to our page that sets a
|
||||||
|
|
@ -118,9 +118,9 @@ special error reporting in that case.
|
||||||
The HTML
|
The HTML
|
||||||
--------
|
--------
|
||||||
|
|
||||||
You index.html template either has to extend a `layout.html` template with
|
Your index.html template either has to extend a `layout.html` template with
|
||||||
jQuery loaded and the `$SCRIPT_ROOT` variable set, or do that on the top.
|
jQuery loaded and the `$SCRIPT_ROOT` variable set, or do that on the top.
|
||||||
Here the HTML code needed for our little application (`index.html`).
|
Here's the HTML code needed for our little application (`index.html`).
|
||||||
Notice that we also drop the script directly into the HTML here. It is
|
Notice that we also drop the script directly into the HTML here. It is
|
||||||
usually a better idea to have that in a separate script file:
|
usually a better idea to have that in a separate script file:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ function but internally imports the real function on first use::
|
||||||
return self.view(*args, **kwargs)
|
return self.view(*args, **kwargs)
|
||||||
|
|
||||||
What's important here is is that `__module__` and `__name__` are properly
|
What's important here is is that `__module__` and `__name__` are properly
|
||||||
set. This is used by Flask internally to figure out how to do name the
|
set. This is used by Flask internally to figure out how to name the
|
||||||
URL rules in case you don't provide a name for the rule yourself.
|
URL rules in case you don't provide a name for the rule yourself.
|
||||||
|
|
||||||
Then you can define your central place to combine the views like this::
|
Then you can define your central place to combine the views like this::
|
||||||
|
|
@ -100,5 +100,5 @@ name and a dot, and by wrapping `view_func` in a `LazyView` as needed::
|
||||||
url('/user/<username>', 'views.user')
|
url('/user/<username>', 'views.user')
|
||||||
|
|
||||||
One thing to keep in mind is that before and after request handlers have
|
One thing to keep in mind is that before and after request handlers have
|
||||||
to be in a file that is imported upfront to work propery on the first
|
to be in a file that is imported upfront to work properly on the first
|
||||||
request. The same goes for any kind of remaining decorator.
|
request. The same goes for any kind of remaining decorator.
|
||||||
|
|
|
||||||
144
docs/patterns/mongokit.rst
Normal file
144
docs/patterns/mongokit.rst
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
.. mongokit-pattern:
|
||||||
|
|
||||||
|
MongoKit in Flask
|
||||||
|
=================
|
||||||
|
|
||||||
|
Using a document database rather than a full DBMS gets more common these days.
|
||||||
|
This pattern shows how to use MongoKit, a document mapper library, to
|
||||||
|
integrate with MongoDB.
|
||||||
|
|
||||||
|
This pattern requires a running MongoDB server and the MongoKit library
|
||||||
|
installed.
|
||||||
|
|
||||||
|
There are two very common ways to use MongoKit. I will outline each of them
|
||||||
|
here:
|
||||||
|
|
||||||
|
|
||||||
|
Declarative
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The default behaviour of MongoKit is the declarative one that is based on
|
||||||
|
common ideas from Django or the SQLAlchemy declarative extension.
|
||||||
|
|
||||||
|
Here an example `app.py` module for your application::
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
from mongokit import Connection, Document
|
||||||
|
|
||||||
|
# configuration
|
||||||
|
MONGODB_HOST = 'localhost'
|
||||||
|
MONGODB_PORT = 27017
|
||||||
|
|
||||||
|
# create the little application object
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(__name__)
|
||||||
|
|
||||||
|
# connect to the database
|
||||||
|
connection = Connection(app.config['MONGODB_HOST'],
|
||||||
|
app.config['MONGODB_PORT'])
|
||||||
|
|
||||||
|
|
||||||
|
To define your models, just subclass the `Document` class that is imported
|
||||||
|
from MongoKit. If you've seen the SQLAlchemy pattern you may wonder why we do
|
||||||
|
not have a session and even do not define a `init_db` function here. On the
|
||||||
|
one hand, MongoKit does not have something like a session. This sometimes
|
||||||
|
makes it more to type but also makes it blazingly fast. On the other hand,
|
||||||
|
MongoDB is schemaless. This means you can modify the data structure from one
|
||||||
|
insert query to the next without any problem. MongoKit is just schemaless
|
||||||
|
too, but implements some validation to ensure data integrity.
|
||||||
|
|
||||||
|
Here is an example document (put this also into `app.py`, e.g.)::
|
||||||
|
|
||||||
|
def max_length(length):
|
||||||
|
def validate(value):
|
||||||
|
if len(value) <= length:
|
||||||
|
return True
|
||||||
|
raise Exception('%s must be at most %s characters long' % length)
|
||||||
|
return validate
|
||||||
|
|
||||||
|
class User(Document):
|
||||||
|
structure = {
|
||||||
|
'name': unicode,
|
||||||
|
'email': unicode,
|
||||||
|
}
|
||||||
|
validators = {
|
||||||
|
'name': max_length(50),
|
||||||
|
'email': max_length(120)
|
||||||
|
}
|
||||||
|
use_dot_notation = True
|
||||||
|
def __repr__(self):
|
||||||
|
return '<User %r>' % (self.name)
|
||||||
|
|
||||||
|
# register the User document with our current connection
|
||||||
|
connection.register([User])
|
||||||
|
|
||||||
|
|
||||||
|
This example shows you how to define your schema (named structure), a
|
||||||
|
validator for the maximum character length and uses a special MongoKit feature
|
||||||
|
called `use_dot_notation`. Per default MongoKit behaves like a python
|
||||||
|
dictionary but with `use_dot_notation` set to `True` you can use your
|
||||||
|
documents like you use models in nearly any other ORM by using dots to
|
||||||
|
separate between attributes.
|
||||||
|
|
||||||
|
You can insert entries into the database like this:
|
||||||
|
|
||||||
|
>>> from yourapplication.database import connection
|
||||||
|
>>> from yourapplication.models import User
|
||||||
|
>>> collection = connection['test'].users
|
||||||
|
>>> user = collection.User()
|
||||||
|
>>> user['name'] = u'admin'
|
||||||
|
>>> user['email'] = u'admin@localhost'
|
||||||
|
>>> user.save()
|
||||||
|
|
||||||
|
Note that MongoKit is kinda strict with used column types, you must not use a
|
||||||
|
common `str` type for either `name` or `email` but unicode.
|
||||||
|
|
||||||
|
Querying is simple as well:
|
||||||
|
|
||||||
|
>>> list(collection.User.find())
|
||||||
|
[<User u'admin'>]
|
||||||
|
>>> collection.User.find_one({'name': u'admin'})
|
||||||
|
<User u'admin'>
|
||||||
|
|
||||||
|
.. _MongoKit: http://bytebucket.org/namlook/mongokit/
|
||||||
|
|
||||||
|
|
||||||
|
PyMongo Compatibility Layer
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
If you just want to use PyMongo, you can do that with MongoKit as well. You
|
||||||
|
may use this process if you need the best performance to get. Note that this
|
||||||
|
example does not show how to couple it with Flask, see the above MongoKit code
|
||||||
|
for examples::
|
||||||
|
|
||||||
|
from MongoKit import Connection
|
||||||
|
|
||||||
|
connection = Connection()
|
||||||
|
|
||||||
|
To insert data you can use the `insert` method. We have to get a
|
||||||
|
collection first, this is somewhat the same as a table in the SQL world.
|
||||||
|
|
||||||
|
>>> collection = connection['test'].users
|
||||||
|
>>> user = {'name': u'admin', 'email': u'admin@localhost'}
|
||||||
|
>>> collection.insert(user)
|
||||||
|
|
||||||
|
print list(collection.find())
|
||||||
|
print collection.find_one({'name': u'admin'})
|
||||||
|
|
||||||
|
MongoKit will automatically commit for us.
|
||||||
|
|
||||||
|
To query your database, you use the collection directly:
|
||||||
|
|
||||||
|
>>> list(collection.find())
|
||||||
|
[{u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'}]
|
||||||
|
>>> collection.find_one({'name': u'admin'})
|
||||||
|
{u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'}
|
||||||
|
|
||||||
|
These results are also dict-like objects:
|
||||||
|
|
||||||
|
>>> r = collection.find_one({'name': u'admin'})
|
||||||
|
>>> r['email']
|
||||||
|
u'admin@localhost'
|
||||||
|
|
||||||
|
For more information about MongoKit, head over to the
|
||||||
|
`website <http://bytebucket.org/namlook/mongokit/>`_.
|
||||||
|
|
@ -162,9 +162,14 @@ modules in the application (`__init__.py`) like this::
|
||||||
from yourapplication.views.frontend import frontend
|
from yourapplication.views.frontend import frontend
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.register_module(admin)
|
app.register_module(admin, url_prefix='/admin')
|
||||||
app.register_module(frontend)
|
app.register_module(frontend)
|
||||||
|
|
||||||
|
We register the modules with the app so that it can add them to the
|
||||||
|
URL map for our application. Note the prefix argument to the admin
|
||||||
|
module: by default when we register a module, that module's end-points
|
||||||
|
will be registered on `/` unless we specify this argument.
|
||||||
|
|
||||||
So what is different when working with modules? It mainly affects URL
|
So what is different when working with modules? It mainly affects URL
|
||||||
generation. Remember the :func:`~flask.url_for` function? When not
|
generation. Remember the :func:`~flask.url_for` function? When not
|
||||||
working with modules it accepts the name of the function as first
|
working with modules it accepts the name of the function as first
|
||||||
|
|
@ -197,3 +202,85 @@ did in the example above, or we just use the function name::
|
||||||
@frontend.route('/')
|
@frontend.route('/')
|
||||||
def index():
|
def index():
|
||||||
return "I'm the index"
|
return "I'm the index"
|
||||||
|
|
||||||
|
.. _modules-and-resources:
|
||||||
|
|
||||||
|
Modules and Resources
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
|
||||||
|
If a module is located inside an actual Python package it may contain
|
||||||
|
static files and templates. Imagine you have an application like this::
|
||||||
|
|
||||||
|
|
||||||
|
/yourapplication
|
||||||
|
__init__.py
|
||||||
|
/apps
|
||||||
|
__init__.py
|
||||||
|
/frontend
|
||||||
|
__init__.py
|
||||||
|
views.py
|
||||||
|
/static
|
||||||
|
style.css
|
||||||
|
/templates
|
||||||
|
index.html
|
||||||
|
about.html
|
||||||
|
...
|
||||||
|
/admin
|
||||||
|
__init__.py
|
||||||
|
views.py
|
||||||
|
/static
|
||||||
|
style.css
|
||||||
|
/templates
|
||||||
|
list_items.html
|
||||||
|
show_item.html
|
||||||
|
...
|
||||||
|
|
||||||
|
The static folders automatically become exposed as URLs. For example if
|
||||||
|
the `admin` module is exported with an URL prefix of ``/admin`` you can
|
||||||
|
access the style css from its static folder by going to
|
||||||
|
``/admin/static/style.css``. The URL endpoint for the static files of the
|
||||||
|
admin would be ``'admin.static'``, similar to how you refer to the regular
|
||||||
|
static folder of the whole application as ``'static'``.
|
||||||
|
|
||||||
|
If you want to refer to the templates you just have to prefix it with the
|
||||||
|
name of the module. So for the admin it would be
|
||||||
|
``render_template('admin/list_items.html')`` and so on. It is not
|
||||||
|
possible to refer to templates without the prefixed module name. This is
|
||||||
|
explicit unlike URL rules.
|
||||||
|
|
||||||
|
You also need to explicitly pass the ``url_prefix`` argument when
|
||||||
|
registering your modules this way::
|
||||||
|
|
||||||
|
# in yourapplication/__init__.py
|
||||||
|
from flask import Flask
|
||||||
|
from yourapplication.apps.admin.views import admin
|
||||||
|
from yourapplication.apps.frontend.views import frontend
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.register_module(admin, url_prefix='/admin')
|
||||||
|
app.register_module(frontend, url_prefix='/frontend')
|
||||||
|
|
||||||
|
This is because Flask cannot infer the prefix from the package names.
|
||||||
|
|
||||||
|
.. admonition:: References to Static Folders
|
||||||
|
|
||||||
|
Please keep in mind that if you are using unqualified endpoints by
|
||||||
|
default Flask will always assume the module's static folder, even if
|
||||||
|
there is no such folder.
|
||||||
|
|
||||||
|
If you want to refer to the application's static folder, use a leading
|
||||||
|
dot::
|
||||||
|
|
||||||
|
# this refers to the application's static folder
|
||||||
|
url_for('.static', filename='static.css')
|
||||||
|
|
||||||
|
# this refers to the current module's static folder
|
||||||
|
url_for('static', filename='static.css')
|
||||||
|
|
||||||
|
This is the case for all endpoints, not just static folders, but for
|
||||||
|
static folders it's more common that you will stumble upon this because
|
||||||
|
most applications will have a static folder in the application and not
|
||||||
|
a specific module.
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,23 @@ encouraged to use a package instead of a module for your flask application
|
||||||
and drop the models into a separate module (:ref:`larger-applications`).
|
and drop the models into a separate module (:ref:`larger-applications`).
|
||||||
While that is not necessary, it makes a lot of sense.
|
While that is not necessary, it makes a lot of sense.
|
||||||
|
|
||||||
There are three very common ways to use SQLAlchemy. I will outline each
|
There are four very common ways to use SQLAlchemy. I will outline each
|
||||||
of them here:
|
of them here:
|
||||||
|
|
||||||
|
Flask-SQLAlchemy Extension
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Because SQLAlchemy is a common database abstraction layer and object
|
||||||
|
relational mapper that requires a little bit of configuration effort,
|
||||||
|
there is a Flask extension that handles that for you. This is recommended
|
||||||
|
if you want to get started quickly.
|
||||||
|
|
||||||
|
You can download `Flask-SQLAlchemy`_ from `PyPI
|
||||||
|
<http://pypi.python.org/pypi/Flask-SQLAlchemy>`_.
|
||||||
|
|
||||||
|
.. _Flask-SQLAlchemy: http://packages.python.org/Flask-SQLAlchemy/
|
||||||
|
|
||||||
|
|
||||||
Declarative
|
Declarative
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
@ -68,7 +82,7 @@ Here is an example model (put this into `models.py`, e.g.)::
|
||||||
self.email = email
|
self.email = email
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<User %r>' % (self.name, self.email)
|
return '<User %r>' % (self.name)
|
||||||
|
|
||||||
You can insert entries into the database like this:
|
You can insert entries into the database like this:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@
|
||||||
Using SQLite 3 with Flask
|
Using SQLite 3 with Flask
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
In Flask you can implement opening of database connections at the beginning
|
In Flask you can implement the opening of database connections at the
|
||||||
of the request and closing at the end with the
|
beginning of the request and closing at the end with the
|
||||||
:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.after_request`
|
:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.after_request`
|
||||||
decorators in combination with the special :class:`~flask.g` object.
|
decorators in combination with the special :class:`~flask.g` object.
|
||||||
|
|
||||||
So here a simple example of how you can use SQLite 3 with Flask::
|
So here is a simple example of how you can use SQLite 3 with Flask::
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from flask import g
|
from flask import g
|
||||||
|
|
@ -33,7 +33,7 @@ Easy Querying
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Now in each request handling function you can access `g.db` to get the
|
Now in each request handling function you can access `g.db` to get the
|
||||||
current open database connection. To simplify working with SQLite a
|
current open database connection. To simplify working with SQLite, a
|
||||||
helper function can be useful::
|
helper function can be useful::
|
||||||
|
|
||||||
def query_db(query, args=(), one=False):
|
def query_db(query, args=(), one=False):
|
||||||
|
|
@ -61,7 +61,7 @@ Or if you just want a single result::
|
||||||
|
|
||||||
To pass variable parts to the SQL statement, use a question mark in the
|
To pass variable parts to the SQL statement, use a question mark in the
|
||||||
statement and pass in the arguments as a list. Never directly add them to
|
statement and pass in the arguments as a list. Never directly add them to
|
||||||
the SQL statement with string formattings because this makes it possible
|
the SQL statement with string formatting because this makes it possible
|
||||||
to attack the application using `SQL Injections
|
to attack the application using `SQL Injections
|
||||||
<http://en.wikipedia.org/wiki/SQL_injection>`_.
|
<http://en.wikipedia.org/wiki/SQL_injection>`_.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ Here the code::
|
||||||
return decorated_function
|
return decorated_function
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
Notice that this assumes an instanciated `cache` object is available, see
|
Notice that this assumes an instantiated `cache` object is available, see
|
||||||
:ref:`caching-pattern` for more information.
|
:ref:`caching-pattern` for more information.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -120,7 +120,9 @@ As you can see, if no template name is provided it will use the endpoint
|
||||||
of the URL map with dots converted to slashes + ``'.html'``. Otherwise
|
of the URL map with dots converted to slashes + ``'.html'``. Otherwise
|
||||||
the provided template name is used. When the decorated function returns,
|
the provided template name is used. When the decorated function returns,
|
||||||
the dictionary returned is passed to the template rendering function. If
|
the dictionary returned is passed to the template rendering function. If
|
||||||
`None` is returned, an empty dictionary is assumed.
|
`None` is returned, an empty dictionary is assumed, if something else than
|
||||||
|
a dictionary is returned we return it from the function unchanged. That
|
||||||
|
way you can still use the redirect function or return simple strings.
|
||||||
|
|
||||||
Here the code for that decorator::
|
Here the code for that decorator::
|
||||||
|
|
||||||
|
|
@ -138,6 +140,8 @@ Here the code for that decorator::
|
||||||
ctx = f(*args, **kwargs)
|
ctx = f(*args, **kwargs)
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
ctx = {}
|
ctx = {}
|
||||||
|
elif not isinstance(ctx, dict):
|
||||||
|
return ctx
|
||||||
return render_template(template_name, **ctx)
|
return render_template(template_name, **ctx)
|
||||||
return decorated_function
|
return decorated_function
|
||||||
return decorator
|
return decorator
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,15 @@ first. I recommend breaking up the application into multiple modules
|
||||||
(:ref:`larger-applications`) for that and adding a separate module for the
|
(:ref:`larger-applications`) for that and adding a separate module for the
|
||||||
forms.
|
forms.
|
||||||
|
|
||||||
|
.. admonition:: Getting most of WTForms with an Extension
|
||||||
|
|
||||||
|
The `Flask-WTF`_ extension expands on this pattern and adds a few
|
||||||
|
handful little helpers that make working with forms and Flask more
|
||||||
|
fun. You can get it from `PyPI
|
||||||
|
<http://pypi.python.org/pypi/Flask-WTF>`_.
|
||||||
|
|
||||||
|
.. _Flask-WTF: http://packages.python.org/Flask-WTF/
|
||||||
|
|
||||||
The Forms
|
The Forms
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|
@ -68,7 +77,7 @@ how easy this is. WTForms does half the form generation for us already.
|
||||||
To make it even nicer, we can write a macro that renders a field with
|
To make it even nicer, we can write a macro that renders a field with
|
||||||
label and a list of errors if there are any.
|
label and a list of errors if there are any.
|
||||||
|
|
||||||
Here an example `_formhelpers.html` template with such a macro:
|
Here's an example `_formhelpers.html` template with such a macro:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
|
@ -84,7 +93,7 @@ Here an example `_formhelpers.html` template with such a macro:
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
This macro accepts a couple of keyword arguments that are forwarded to
|
This macro accepts a couple of keyword arguments that are forwarded to
|
||||||
WTForm's field function that renders the field for us. They keyword
|
WTForm's field function that renders the field for us. The keyword
|
||||||
arguments will be inserted as HTML attributes. So for example you can
|
arguments will be inserted as HTML attributes. So for example you can
|
||||||
call ``render_field(form.username, class='username')`` to add a class to
|
call ``render_field(form.username, class='username')`` to add a class to
|
||||||
the input element. Note that WTForms returns standard Python unicode
|
the input element. Note that WTForms returns standard Python unicode
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Quickstart
|
Quickstart
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Eager to get started? This page gives a good introduction in how to gets
|
Eager to get started? This page gives a good introduction in how to get
|
||||||
started with Flask. This assumes you already have Flask installed. If
|
started with Flask. This assumes you already have Flask installed. If
|
||||||
you do not, head over to the :ref:`installation` section.
|
you do not, head over to the :ref:`installation` section.
|
||||||
|
|
||||||
|
|
@ -37,9 +37,14 @@ see your hello world greeting.
|
||||||
|
|
||||||
So what did that code do?
|
So what did that code do?
|
||||||
|
|
||||||
1. first we imported the :class:`~flask.Flask` class. An instance of this
|
1. First we imported the :class:`~flask.Flask` class. An instance of this
|
||||||
class will be our WSGI application.
|
class will be our WSGI application. The first argument is the name of
|
||||||
2. next we create an instance of it. We pass it the name of the module /
|
the application's module. If you are using a single module (like here)
|
||||||
|
you should use `__name__` because depending on if it's started as
|
||||||
|
application or imported as module the name will be different
|
||||||
|
(``'__main__'`` versus the actual import name). For more information
|
||||||
|
on that, have a look at the :class:`~flask.Flask` documentation.
|
||||||
|
2. Next we create an instance of it. We pass it the name of the module /
|
||||||
package. This is needed so that Flask knows where it should look for
|
package. This is needed so that Flask knows where it should look for
|
||||||
templates, static files and so on.
|
templates, static files and so on.
|
||||||
3. Then we use the :meth:`~flask.Flask.route` decorator to tell Flask
|
3. Then we use the :meth:`~flask.Flask.route` decorator to tell Flask
|
||||||
|
|
@ -76,7 +81,7 @@ To stop the server, hit control-C.
|
||||||
Debug Mode
|
Debug Mode
|
||||||
----------
|
----------
|
||||||
|
|
||||||
Now that :meth:`~flask.Flask.run` method is nice to start a local
|
The :meth:`~flask.Flask.run` method is nice to start a local
|
||||||
development server, but you would have to restart it manually after each
|
development server, but you would have to restart it manually after each
|
||||||
change you do to code. That is not very nice and Flask can do better. If
|
change you do to code. That is not very nice and Flask can do better. If
|
||||||
you enable the debug support the server will reload itself on code changes
|
you enable the debug support the server will reload itself on code changes
|
||||||
|
|
@ -96,11 +101,10 @@ Both will have exactly the same effect.
|
||||||
|
|
||||||
.. admonition:: Attention
|
.. admonition:: Attention
|
||||||
|
|
||||||
The interactive debugger however does not work in forking environments
|
Even though the interactive debugger does not work in forking environments
|
||||||
which makes it nearly impossible to use on production servers but the
|
(which makes it nearly impossible to use on production servers), it still
|
||||||
debugger still allows the execution of arbitrary code which makes it a
|
allows the execution of arbitrary code. That makes it a major security
|
||||||
major security risk and **must never be used on production machines**
|
risk and therefore it **must never be used on production machines**.
|
||||||
because of that.
|
|
||||||
|
|
||||||
Screenshot of the debugger in action:
|
Screenshot of the debugger in action:
|
||||||
|
|
||||||
|
|
@ -113,11 +117,14 @@ Screenshot of the debugger in action:
|
||||||
Routing
|
Routing
|
||||||
-------
|
-------
|
||||||
|
|
||||||
As you have seen above, the :meth:`~flask.Flask.route` decorator is used
|
Modern web applications have beautiful URLs. This helps people remember
|
||||||
to bind a function to a URL. But there is more to it! You can make
|
the URLs which is especially handy for applications that are used from
|
||||||
certain parts of the URL dynamic and attach multiple rules to a function.
|
mobile devices with slower network connections. If the user can directly
|
||||||
|
go to the desired page without having to hit the index page it is more
|
||||||
|
likely he will like the page and come back next time.
|
||||||
|
|
||||||
Here some examples::
|
As you have seen above, the :meth:`~flask.Flask.route` decorator is used
|
||||||
|
to bind a function to a URL. Here are some basic examples::
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
|
|
@ -127,20 +134,16 @@ Here some examples::
|
||||||
def hello():
|
def hello():
|
||||||
return 'Hello World'
|
return 'Hello World'
|
||||||
|
|
||||||
|
But there is more to it! You can make certain parts of the URL dynamic
|
||||||
|
and attach multiple rules to a function.
|
||||||
|
|
||||||
Variable Rules
|
Variable Rules
|
||||||
``````````````
|
``````````````
|
||||||
|
|
||||||
Modern web applications have beautiful URLs. This helps people remember
|
|
||||||
the URLs which is especially handy for applications that are used from
|
|
||||||
mobile devices with slower network connections. If the user can directly
|
|
||||||
go to the desired page without having to hit the index page it is more
|
|
||||||
likely he will like the page and come back next time.
|
|
||||||
|
|
||||||
To add variable parts to a URL you can mark these special sections as
|
To add variable parts to a URL you can mark these special sections as
|
||||||
``<variable_name>``. Such a part is then passed as keyword argument to
|
``<variable_name>``. Such a part is then passed as keyword argument to
|
||||||
your function. Optionally a converter can be specified by specifying a
|
your function. Optionally a converter can be specified by specifying a
|
||||||
rule with ``<converter:variable_name>``. Here some nice examples::
|
rule with ``<converter:variable_name>``. Here are some nice examples::
|
||||||
|
|
||||||
@app.route('/user/<username>')
|
@app.route('/user/<username>')
|
||||||
def show_user_profile(username):
|
def show_user_profile(username):
|
||||||
|
|
@ -198,12 +201,12 @@ The following converters exist:
|
||||||
URL Building
|
URL Building
|
||||||
````````````
|
````````````
|
||||||
|
|
||||||
If it can match URLs, can it also generate them? Of course you can. To
|
If it can match URLs, can it also generate them? Of course it can. To
|
||||||
build a URL to a specific function you can use the :func:`~flask.url_for`
|
build a URL to a specific function you can use the :func:`~flask.url_for`
|
||||||
function. It accepts the name of the function as first argument and a
|
function. It accepts the name of the function as first argument and a
|
||||||
number of keyword arguments, each corresponding to the variable part of
|
number of keyword arguments, each corresponding to the variable part of
|
||||||
the URL rule. Unknown variable parts are appended to the URL as query
|
the URL rule. Unknown variable parts are appended to the URL as query
|
||||||
parameter. Here some examples:
|
parameter. Here are some examples:
|
||||||
|
|
||||||
>>> from flask import Flask, url_for
|
>>> from flask import Flask, url_for
|
||||||
>>> app = Flask(__name__)
|
>>> app = Flask(__name__)
|
||||||
|
|
@ -228,7 +231,7 @@ parameter. Here some examples:
|
||||||
/user/John%20Doe
|
/user/John%20Doe
|
||||||
|
|
||||||
(This also uses the :meth:`~flask.Flask.test_request_context` method
|
(This also uses the :meth:`~flask.Flask.test_request_context` method
|
||||||
explained below. It basically tells flask to think we are handling a
|
explained below. It basically tells Flask to think we are handling a
|
||||||
request even though we are not, we are in an interactive Python shell.
|
request even though we are not, we are in an interactive Python shell.
|
||||||
Have a look at the explanation below. :ref:`context-locals`).
|
Have a look at the explanation below. :ref:`context-locals`).
|
||||||
|
|
||||||
|
|
@ -251,7 +254,7 @@ HTTP Methods
|
||||||
HTTP (the protocol web applications are speaking) knows different methods
|
HTTP (the protocol web applications are speaking) knows different methods
|
||||||
to access URLs. By default a route only answers to `GET` requests, but
|
to access URLs. By default a route only answers to `GET` requests, but
|
||||||
that can be changed by providing the `methods` argument to the
|
that can be changed by providing the `methods` argument to the
|
||||||
:meth:`~flask.Flask.route` decorator. Here some examples::
|
:meth:`~flask.Flask.route` decorator. Here are some examples::
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
|
|
@ -264,27 +267,27 @@ If `GET` is present, `HEAD` will be added automatically for you. You
|
||||||
don't have to deal with that. It will also make sure that `HEAD` requests
|
don't have to deal with that. It will also make sure that `HEAD` requests
|
||||||
are handled like the `HTTP RFC`_ (the document describing the HTTP
|
are handled like the `HTTP RFC`_ (the document describing the HTTP
|
||||||
protocol) demands, so you can completely ignore that part of the HTTP
|
protocol) demands, so you can completely ignore that part of the HTTP
|
||||||
specification.
|
specification. Likewise as of Flask 0.6, `OPTIONS` is implemented for you
|
||||||
|
as well automatically.
|
||||||
|
|
||||||
You have no idea what an HTTP method is? Worry not, here quick
|
You have no idea what an HTTP method is? Worry not, here is a quick
|
||||||
introduction in HTTP methods and why they matter:
|
introduction to HTTP methods and why they matter:
|
||||||
|
|
||||||
The HTTP method (also often called "the verb") tells the server what the
|
The HTTP method (also often called "the verb") tells the server what the
|
||||||
clients wants to *do* with the requested page. The following methods are
|
clients wants to *do* with the requested page. The following methods are
|
||||||
very common:
|
very common:
|
||||||
|
|
||||||
`GET`
|
`GET`
|
||||||
The Browser tells the server: just *get* me the information stored on
|
The browser tells the server to just *get* the information stored on
|
||||||
that page and send them to me. This is probably the most common
|
that page and send it. This is probably the most common method.
|
||||||
method.
|
|
||||||
|
|
||||||
`HEAD`
|
`HEAD`
|
||||||
The Browser tells the server: get me the information, but I am only
|
The browser tells the server to get the information, but it is only
|
||||||
interested in the *headers*, not the content of the page. An
|
interested in the *headers*, not the content of the page. An
|
||||||
application is supposed to handle that as if a `GET` request was
|
application is supposed to handle that as if a `GET` request was
|
||||||
received but not deliver the actual contents. In Flask you don't have
|
received but to not deliver the actual content. In Flask you don't
|
||||||
to deal with that at all, the underlying Werkzeug library handles that
|
have to deal with that at all, the underlying Werkzeug library handles
|
||||||
for you.
|
that for you.
|
||||||
|
|
||||||
`POST`
|
`POST`
|
||||||
The browser tells the server that it wants to *post* some new
|
The browser tells the server that it wants to *post* some new
|
||||||
|
|
@ -295,22 +298,27 @@ very common:
|
||||||
`PUT`
|
`PUT`
|
||||||
Similar to `POST` but the server might trigger the store procedure
|
Similar to `POST` but the server might trigger the store procedure
|
||||||
multiple times by overwriting the old values more than once. Now you
|
multiple times by overwriting the old values more than once. Now you
|
||||||
might be asking why this is any useful, but there are some good
|
might be asking why is this useful, but there are some good reasons
|
||||||
reasons to do that. Consider the connection is lost during
|
to do it this way. Consider that the connection gets lost during
|
||||||
transmission, in that situation a system between the browser and the
|
transmission: in this situation a system between the browser and the
|
||||||
server might sent the request safely a second time without breaking
|
server might receive the request safely a second time without breaking
|
||||||
things. With `POST` that would not be possible because it must only
|
things. With `POST` that would not be possible because it must only
|
||||||
be triggered once.
|
be triggered once.
|
||||||
|
|
||||||
`DELETE`
|
`DELETE`
|
||||||
Remove the information that the given location.
|
Remove the information at the given location.
|
||||||
|
|
||||||
|
`OPTIONS`
|
||||||
|
Provides a quick way for a client to figure out which methods are
|
||||||
|
supported by this URL. Starting with Flask 0.6, this is implemented
|
||||||
|
for you automatically.
|
||||||
|
|
||||||
Now the interesting part is that in HTML4 and XHTML1, the only methods a
|
Now the interesting part is that in HTML4 and XHTML1, the only methods a
|
||||||
form might submit to the server are `GET` and `POST`. But with JavaScript
|
form can submit to the server are `GET` and `POST`. But with JavaScript
|
||||||
and future HTML standards you can use other methods as well. Furthermore
|
and future HTML standards you can use the other methods as well. Furthermore
|
||||||
HTTP became quite popular lately and there are more things than browsers
|
HTTP has become quite popular lately and browsers are no longer the only
|
||||||
that are speaking HTTP. (Your revision control system for instance might
|
clients that are using HTTP. For instance, many revision control system
|
||||||
speak HTTP)
|
use it.
|
||||||
|
|
||||||
.. _HTTP RFC: http://www.ietf.org/rfc/rfc2068.txt
|
.. _HTTP RFC: http://www.ietf.org/rfc/rfc2068.txt
|
||||||
|
|
||||||
|
|
@ -368,10 +376,11 @@ package it's actually inside your package:
|
||||||
/hello.html
|
/hello.html
|
||||||
|
|
||||||
For templates you can use the full power of Jinja2 templates. Head over
|
For templates you can use the full power of Jinja2 templates. Head over
|
||||||
to the `Jinja2 Template Documentation
|
to the :ref:`templating` section of the documentation or the official
|
||||||
|
`Jinja2 Template Documentation
|
||||||
<http://jinja.pocoo.org/2/documentation/templates>`_ for more information.
|
<http://jinja.pocoo.org/2/documentation/templates>`_ for more information.
|
||||||
|
|
||||||
Here an example template:
|
Here is an example template:
|
||||||
|
|
||||||
.. sourcecode:: html+jinja
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
|
@ -399,7 +408,7 @@ markup to HTML) you can mark it as safe by using the
|
||||||
:class:`~jinja2.Markup` class or by using the ``|safe`` filter in the
|
:class:`~jinja2.Markup` class or by using the ``|safe`` filter in the
|
||||||
template. Head over to the Jinja 2 documentation for more examples.
|
template. Head over to the Jinja 2 documentation for more examples.
|
||||||
|
|
||||||
Here a basic introduction in how the :class:`~jinja2.Markup` class works:
|
Here is a basic introduction to how the :class:`~jinja2.Markup` class works:
|
||||||
|
|
||||||
>>> from flask import Markup
|
>>> from flask import Markup
|
||||||
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
|
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
|
||||||
|
|
@ -409,9 +418,16 @@ Markup(u'<blink>hacker</blink>')
|
||||||
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
||||||
u'Marked up \xbb HTML'
|
u'Marked up \xbb HTML'
|
||||||
|
|
||||||
.. [#] Unsure what that :class:`~flask.g` object is? It's something you
|
.. versionchanged:: 0.5
|
||||||
can store information on yourself, check the documentation of that
|
|
||||||
object (:class:`~flask.g`) and the :ref:`sqlite3` for more
|
Autoescaping is no longer enabled for all templates. The following
|
||||||
|
extensions for templates trigger autoescaping: ``.html``, ``.htm``,
|
||||||
|
``.xml``, ``.xhtml``. Templates loaded from a string will have
|
||||||
|
autoescaping disabled.
|
||||||
|
|
||||||
|
.. [#] Unsure what that :class:`~flask.g` object is? It's something in which
|
||||||
|
you can store information for your own needs, check the documentation of
|
||||||
|
that object (:class:`~flask.g`) and the :ref:`sqlite3` for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -435,10 +451,9 @@ Context Locals
|
||||||
If you want to understand how that works and how you can implement
|
If you want to understand how that works and how you can implement
|
||||||
tests with context locals, read this section, otherwise just skip it.
|
tests with context locals, read this section, otherwise just skip it.
|
||||||
|
|
||||||
Certain objects in Flask are global objects, but not just a standard
|
Certain objects in Flask are global objects, but not of the usual kind.
|
||||||
global object, but actually a proxy to an object that is local to a
|
These objects are actually proxies to objects that are local to a specific
|
||||||
specific context. What a mouthful. But that is actually quite easy to
|
context. What a mouthful. But that is actually quite easy to understand.
|
||||||
understand.
|
|
||||||
|
|
||||||
Imagine the context being the handling thread. A request comes in and the
|
Imagine the context being the handling thread. A request comes in and the
|
||||||
webserver decides to spawn a new thread (or something else, the
|
webserver decides to spawn a new thread (or something else, the
|
||||||
|
|
@ -450,13 +465,13 @@ It does that in an intelligent way that one application can invoke another
|
||||||
application without breaking.
|
application without breaking.
|
||||||
|
|
||||||
So what does this mean to you? Basically you can completely ignore that
|
So what does this mean to you? Basically you can completely ignore that
|
||||||
this is the case unless you are unittesting or something different. You
|
this is the case unless you are doing something like unittesting. You
|
||||||
will notice that code that depends on a request object will suddenly break
|
will notice that code that depends on a request object will suddenly break
|
||||||
because there is no request object. The solution is creating a request
|
because there is no request object. The solution is creating a request
|
||||||
object yourself and binding it to the context. The easiest solution for
|
object yourself and binding it to the context. The easiest solution for
|
||||||
unittesting is by using the :meth:`~flask.Flask.test_request_context`
|
unittesting is by using the :meth:`~flask.Flask.test_request_context`
|
||||||
context manager. In combination with the `with` statement it will bind a
|
context manager. In combination with the `with` statement it will bind a
|
||||||
test request so that you can interact with it. Here an example::
|
test request so that you can interact with it. Here is an example::
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
|
|
@ -478,8 +493,8 @@ The Request Object
|
||||||
``````````````````
|
``````````````````
|
||||||
|
|
||||||
The request object is documented in the API section and we will not cover
|
The request object is documented in the API section and we will not cover
|
||||||
it here in detail (see :class:`~flask.request`), but just mention some of
|
it here in detail (see :class:`~flask.request`). Here is a broad overview of
|
||||||
the most common operations. First of all you have to import it from the
|
some of the most common operations. First of all you have to import it from
|
||||||
the `flask` module::
|
the `flask` module::
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
@ -487,7 +502,7 @@ the `flask` module::
|
||||||
The current request method is available by using the
|
The current request method is available by using the
|
||||||
:attr:`~flask.request.method` attribute. To access form data (data
|
:attr:`~flask.request.method` attribute. To access form data (data
|
||||||
transmitted in a `POST` or `PUT` request) you can use the
|
transmitted in a `POST` or `PUT` request) you can use the
|
||||||
:attr:`~flask.request.form` attribute. Here a full example of the two
|
:attr:`~flask.request.form` attribute. Here is a full example of the two
|
||||||
attributes mentioned above::
|
attributes mentioned above::
|
||||||
|
|
||||||
@app.route('/login', methods=['POST', 'GET'])
|
@app.route('/login', methods=['POST', 'GET'])
|
||||||
|
|
@ -515,19 +530,18 @@ To access parameters submitted in the URL (``?key=value``) you can use the
|
||||||
|
|
||||||
We recommend accessing URL parameters with `get` or by catching the
|
We recommend accessing URL parameters with `get` or by catching the
|
||||||
`KeyError` because users might change the URL and presenting them a 400
|
`KeyError` because users might change the URL and presenting them a 400
|
||||||
bad request page in that case is a bit user unfriendly.
|
bad request page in that case is not user friendly.
|
||||||
|
|
||||||
For a full list of methods and attributes on that object, head over to the
|
For a full list of methods and attributes of the request object, head over
|
||||||
:class:`~flask.request` documentation.
|
to the :class:`~flask.request` documentation.
|
||||||
|
|
||||||
|
|
||||||
File Uploads
|
File Uploads
|
||||||
````````````
|
````````````
|
||||||
|
|
||||||
Obviously you can handle uploaded files with Flask just as easy. Just
|
You can handle uploaded files with Flask easily. Just make sure not to
|
||||||
make sure not to forget to set the ``enctype="multipart/form-data"``
|
forget to set the ``enctype="multipart/form-data"`` attribute on your HTML
|
||||||
attribute on your HTML form, otherwise the browser will not transmit your
|
form, otherwise the browser will not transmit your files at all.
|
||||||
files at all.
|
|
||||||
|
|
||||||
Uploaded files are stored in memory or at a temporary location on the
|
Uploaded files are stored in memory or at a temporary location on the
|
||||||
filesystem. You can access those files by looking at the
|
filesystem. You can access those files by looking at the
|
||||||
|
|
@ -535,8 +549,8 @@ filesystem. You can access those files by looking at the
|
||||||
uploaded file is stored in that dictionary. It behaves just like a
|
uploaded file is stored in that dictionary. It behaves just like a
|
||||||
standard Python :class:`file` object, but it also has a
|
standard Python :class:`file` object, but it also has a
|
||||||
:meth:`~werkzeug.FileStorage.save` method that allows you to store that
|
:meth:`~werkzeug.FileStorage.save` method that allows you to store that
|
||||||
file on the filesystem of the server. Here a simple example how that
|
file on the filesystem of the server. Here is a simple example showing how
|
||||||
works::
|
that works::
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
|
|
@ -581,8 +595,8 @@ Redirects and Errors
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
To redirect a user to somewhere else you can use the
|
To redirect a user to somewhere else you can use the
|
||||||
:func:`~flask.redirect` function, to abort a request early with an error
|
:func:`~flask.redirect` function. To abort a request early with an error
|
||||||
code the :func:`~flask.abort` function. Here an example how this works::
|
code use the :func:`~flask.abort` function. Here an example how this works::
|
||||||
|
|
||||||
from flask import abort, redirect, url_for
|
from flask import abort, redirect, url_for
|
||||||
|
|
||||||
|
|
@ -628,7 +642,9 @@ unless he knows the secret key used for signing.
|
||||||
In order to use sessions you have to set a secret key. Here is how
|
In order to use sessions you have to set a secret key. Here is how
|
||||||
sessions work::
|
sessions work::
|
||||||
|
|
||||||
from flask import session, redirect, url_for, escape
|
from flask import Flask, session, redirect, url_for, escape, request
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
|
|
@ -652,6 +668,7 @@ sessions work::
|
||||||
def logout():
|
def logout():
|
||||||
# remove the username from the session if its there
|
# remove the username from the session if its there
|
||||||
session.pop('username', None)
|
session.pop('username', None)
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
# set the secret key. keep this really secret:
|
# set the secret key. keep this really secret:
|
||||||
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
|
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
|
||||||
|
|
@ -659,7 +676,7 @@ sessions work::
|
||||||
The here mentioned :func:`~flask.escape` does escaping for you if you are
|
The here mentioned :func:`~flask.escape` does escaping for you if you are
|
||||||
not using the template engine (like in this example).
|
not using the template engine (like in this example).
|
||||||
|
|
||||||
.. admonition:: How to generate good Secret Keys
|
.. admonition:: How to generate good secret keys
|
||||||
|
|
||||||
The problem with random is that it's hard to judge what random is. And
|
The problem with random is that it's hard to judge what random is. And
|
||||||
a secret key should be as random as possible. Your operating system
|
a secret key should be as random as possible. Your operating system
|
||||||
|
|
@ -693,16 +710,17 @@ Logging
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
|
|
||||||
Sometimes you might be in the situation where you deal with data that
|
Sometimes you might be in a situation where you deal with data that
|
||||||
should be correct, but actually is not. For example you have some client
|
should be correct, but actually is not. For example you may have some client
|
||||||
side code that sends an HTTP request to the server, and it's obviously
|
side code that sends an HTTP request to the server but it's obviously
|
||||||
malformed. This might be caused by a user tempering with the data, or the
|
malformed. This might be caused by a user tempering with the data, or the
|
||||||
client code failed. Most the time, it's okay to reply with ``400 Bad
|
client code failing. Most of the time, it's okay to reply with ``400 Bad
|
||||||
Request`` in that situation, but other times it is not and the code has to
|
Request`` in that situation, but sometimes that won't do and the code has
|
||||||
continue working.
|
to continue working.
|
||||||
|
|
||||||
Yet you want to log that something fishy happened. This is where loggers
|
You may still want to log that something fishy happened. This is where
|
||||||
come in handy. As of Flask 0.3 a logger is preconfigured for you to use.
|
loggers come in handy. As of Flask 0.3 a logger is preconfigured for you
|
||||||
|
to use.
|
||||||
|
|
||||||
Here are some example log calls::
|
Here are some example log calls::
|
||||||
|
|
||||||
|
|
@ -711,5 +729,17 @@ Here are some example log calls::
|
||||||
app.logger.error('An error occurred')
|
app.logger.error('An error occurred')
|
||||||
|
|
||||||
The attached :attr:`~flask.Flask.logger` is a standard logging
|
The attached :attr:`~flask.Flask.logger` is a standard logging
|
||||||
:class:`~logging.Logger`, so head over to the official stdlib
|
:class:`~logging.Logger`, so head over to the official `logging
|
||||||
documentation for more information.
|
documentation <http://docs.python.org/library/logging.html>`_ for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
Hooking in WSGI Middlewares
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
If you want to add a WSGI middleware to your application you can wrap the
|
||||||
|
internal WSGI application. For example if you want to use one of the
|
||||||
|
middlewares from the Werkzeug package to work around bugs in lighttpd, you
|
||||||
|
can do it like this::
|
||||||
|
|
||||||
|
from werkzeug.contrib.fixers import LighttpdCGIRootFix
|
||||||
|
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,18 @@ Web applications usually face all kinds of security problems and it's very
|
||||||
hard to get everything right. Flask tries to solve a few of these things
|
hard to get everything right. Flask tries to solve a few of these things
|
||||||
for you, but there are a couple more you have to take care of yourself.
|
for you, but there are a couple more you have to take care of yourself.
|
||||||
|
|
||||||
|
.. _xss:
|
||||||
|
|
||||||
Cross-Site Scripting (XSS)
|
Cross-Site Scripting (XSS)
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
Cross site scripting is the concept of injecting arbitrary HTML (and with
|
||||||
|
it JavaScript) into the context of a website. To rememdy this, developers
|
||||||
|
have to properly escape text so that it cannot include arbitrary HTML
|
||||||
|
tags. For more information on that have a look at the Wikipedia article
|
||||||
|
on `Cross-Site Scripting
|
||||||
|
<http://en.wikipedia.org/wiki/Cross-site_scripting>`_.
|
||||||
|
|
||||||
Flask configures Jinja2 to automatically escape all values unless
|
Flask configures Jinja2 to automatically escape all values unless
|
||||||
explicitly told otherwise. This should rule out all XSS problems caused
|
explicitly told otherwise. This should rule out all XSS problems caused
|
||||||
in templates, but there are still other places where you have to be
|
in templates, but there are still other places where you have to be
|
||||||
|
|
@ -21,6 +30,31 @@ careful:
|
||||||
content-type guessing based on the first few bytes so users could
|
content-type guessing based on the first few bytes so users could
|
||||||
trick a browser to execute HTML.
|
trick a browser to execute HTML.
|
||||||
|
|
||||||
|
Another thing that is very important are unquoted attributes. While
|
||||||
|
Jinja2 can protect you from XSS issues by escaping HTML, there is one
|
||||||
|
thing it cannot protect you from: XSS by attribute injection. To counter
|
||||||
|
this possible attack vector, be sure to always quote your attributes with
|
||||||
|
either double or single quotes when using Jinja expressions in them:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
<a href="{{ href }}">the text</a>
|
||||||
|
|
||||||
|
Why is this necessary? Because if you would not be doing that, an
|
||||||
|
attacker could easily inject custom JavaScript handlers. For example an
|
||||||
|
attacker could inject this piece of HTML+JavaScript:
|
||||||
|
|
||||||
|
.. sourcecode:: html
|
||||||
|
|
||||||
|
onmouseover=alert(document.cookie)
|
||||||
|
|
||||||
|
When the user would then move with the mouse over the link, the cookie
|
||||||
|
would be presented to the user in an alert window. But instead of showing
|
||||||
|
the cookie to the user, a good attacker might also execute any other
|
||||||
|
JavaScript code. In combination with CSS injections the attacker might
|
||||||
|
even make the element fill out the entire page so that the user would
|
||||||
|
just have to have the mouse anywhere on the page to trigger the attack.
|
||||||
|
|
||||||
Cross-Site Request Forgery (CSRF)
|
Cross-Site Request Forgery (CSRF)
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
|
@ -28,33 +62,33 @@ Another big problem is CSRF. This is a very complex topic and I won't
|
||||||
outline it here in detail just mention what it is and how to theoretically
|
outline it here in detail just mention what it is and how to theoretically
|
||||||
prevent it.
|
prevent it.
|
||||||
|
|
||||||
So if your authentication information is stored in cookies you have
|
If your authentication information is stored in cookies, you have implicit
|
||||||
implicit state management. By that I mean that the state of "being logged
|
state management. The state of "being logged in" is controlled by a
|
||||||
in" is controlled by a cookie and that cookie is sent with each request to
|
cookie, and that cookie is sent with each request to a page.
|
||||||
a page. Unfortunately that really means "each request" so also requests
|
Unfortunately that includes requests triggered by 3rd party sites. If you
|
||||||
triggered by 3rd party sites. If you don't keep that in mind some people
|
don't keep that in mind, some people might be able to trick your
|
||||||
might be able to trick your application's users with social engineering to
|
application's users with social engineering to do stupid things without
|
||||||
do stupid things without them knowing.
|
them knowing.
|
||||||
|
|
||||||
Say you have a specific URL that, when you sent `POST` requests to will
|
Say you have a specific URL that, when you sent `POST` requests to will
|
||||||
delete a user's profile (say `http://example.com/user/delete`). If an
|
delete a user's profile (say `http://example.com/user/delete`). If an
|
||||||
attacker now creates a page that sents a post request to that page with
|
attacker now creates a page that sends a post request to that page with
|
||||||
some JavaScript he just has to trick some users to that page and their
|
some JavaScript he just has to trick some users to load that page and
|
||||||
profiles will end up being deleted.
|
their profiles will end up being deleted.
|
||||||
|
|
||||||
Imagine you would run Facebook with millions of concurrent users and
|
Imagine you were to run Facebook with millions of concurrent users and
|
||||||
someone would send out links to images of little kittens. When a user
|
someone would send out links to images of little kittens. When users
|
||||||
would go to that page their profiles would get deleted while they are
|
would go to that page, their profiles would get deleted while they are
|
||||||
looking at images of fluffy cats.
|
looking at images of fluffy cats.
|
||||||
|
|
||||||
So how can you prevent yourself from that? Basically for each request
|
How can you prevent that? Basically for each request that modifies
|
||||||
that modifies content on the server you would have to either use a
|
content on the server you would have to either use a one-time token and
|
||||||
one-time token and store that in the cookie **and** also transmit it with
|
store that in the cookie **and** also transmit it with the form data.
|
||||||
the form data. After recieving the data on the server again you would
|
After receiving the data on the server again, you would then have to
|
||||||
then have to compare the two tokens and ensure they are equal.
|
compare the two tokens and ensure they are equal.
|
||||||
|
|
||||||
Why does not Flask do that for you? The ideal place for this to happen is
|
Why does Flask not do that for you? The ideal place for this to happen is
|
||||||
the form validation framework which does not exist in Flask.
|
the form validation framework, which does not exist in Flask.
|
||||||
|
|
||||||
.. _json-security:
|
.. _json-security:
|
||||||
|
|
||||||
|
|
@ -77,8 +111,8 @@ generate JSON.
|
||||||
|
|
||||||
So what is the issue and how to avoid it? The problem are arrays at
|
So what is the issue and how to avoid it? The problem are arrays at
|
||||||
toplevel in JSON. Imagine you send the following data out in a JSON
|
toplevel in JSON. Imagine you send the following data out in a JSON
|
||||||
request. Say that's exporting the names and email adresses of all your
|
request. Say that's exporting the names and email addresses of all your
|
||||||
friends for a part of the userinterface that is written in JavaScript.
|
friends for a part of the user interface that is written in JavaScript.
|
||||||
Not very uncommon:
|
Not very uncommon:
|
||||||
|
|
||||||
.. sourcecode:: javascript
|
.. sourcecode:: javascript
|
||||||
|
|
@ -129,6 +163,6 @@ page loaded the data from the JSON response is in the `captured` array.
|
||||||
Because it is a syntax error in JavaScript to have an object literal
|
Because it is a syntax error in JavaScript to have an object literal
|
||||||
(``{...}``) toplevel an attacker could not just do a request to an
|
(``{...}``) toplevel an attacker could not just do a request to an
|
||||||
external URL with the script tag to load up the data. So what Flask does
|
external URL with the script tag to load up the data. So what Flask does
|
||||||
is only allowing objects as toplevel elements when using
|
is to only allow objects as toplevel elements when using
|
||||||
:func:`~flask.jsonify`. Make sure to do the same when using an ordinary
|
:func:`~flask.jsonify`. Make sure to do the same when using an ordinary
|
||||||
JSON generate function.
|
JSON generate function.
|
||||||
|
|
|
||||||
232
docs/signals.rst
Normal file
232
docs/signals.rst
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
.. _signals:
|
||||||
|
|
||||||
|
Signals
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. versionadded:: 0.6
|
||||||
|
|
||||||
|
Starting with Flask 0.6, there is integrated support for signalling in
|
||||||
|
Flask. This support is provided by the excellent `blinker`_ library and
|
||||||
|
will gracefully fall back if it is not available.
|
||||||
|
|
||||||
|
What are signals? Signals help you decouple applications by sending
|
||||||
|
notifications when actions occur elsewhere in the core framework or
|
||||||
|
another Flask extensions. In short, signals allow certain senders to
|
||||||
|
notify subscribers that something happened.
|
||||||
|
|
||||||
|
Flask comes with a couple of signals and other extensions might provide
|
||||||
|
more. Also keep in mind that signals are intended to notify subscribers
|
||||||
|
and should not encourage subscribers to modify data. You will notice that
|
||||||
|
there are signals that appear to do the same thing like some of the
|
||||||
|
builtin decorators do (eg: :data:`~flask.request_started` is very similar
|
||||||
|
to :meth:`~flask.Flask.before_request`). There are however difference in
|
||||||
|
how they work. The core :meth:`~flask.Flask.before_request` handler for
|
||||||
|
example is executed in a specific order and is able to abort the request
|
||||||
|
early by returning a response. In contrast all signal handlers are
|
||||||
|
executed in undefined order and do not modify any data.
|
||||||
|
|
||||||
|
The big advantage of signals over handlers is that you can safely
|
||||||
|
subscribe to them for the split of a second. These temporary
|
||||||
|
subscriptions are helpful for unittesting for example. Say you want to
|
||||||
|
know what templates were rendered as part of a request: signals allow you
|
||||||
|
to do exactly that.
|
||||||
|
|
||||||
|
Subscribing to Signals
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
To subscribe to a signal, you can use the
|
||||||
|
:meth:`~blinker.base.Signal.connect` method of a signal. The first
|
||||||
|
argument is the function that should be called when the signal is emitted,
|
||||||
|
the optional second argument specifies a sender. To unsubscribe from a
|
||||||
|
signal, you can use the :meth:`~blinker.base.Signal.disconnect` method.
|
||||||
|
|
||||||
|
For all core Flask signals, the sender is the application that issued the
|
||||||
|
signal. When you subscribe to a signal, be sure to also provide a sender
|
||||||
|
unless you really want to listen for signals of all applications. This is
|
||||||
|
especially true if you are developing an extension.
|
||||||
|
|
||||||
|
Here for example a helper context manager that can be used to figure out
|
||||||
|
in a unittest which templates were rendered and what variables were passed
|
||||||
|
to the template::
|
||||||
|
|
||||||
|
from flask import template_rendered
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def captured_templates(app):
|
||||||
|
recorded = []
|
||||||
|
def record(sender, template, context):
|
||||||
|
recorded.append((template, context))
|
||||||
|
template_rendered.connect(record, app)
|
||||||
|
try:
|
||||||
|
yield recorded
|
||||||
|
finally:
|
||||||
|
template_rendered.disconnect(record, app)
|
||||||
|
|
||||||
|
This can now easily be paired with a test client::
|
||||||
|
|
||||||
|
with captured_templates(app) as templates:
|
||||||
|
rv = app.test_client().get('/')
|
||||||
|
assert rv.status_code == 200
|
||||||
|
assert len(templates) == 1
|
||||||
|
template, context = templates[0]
|
||||||
|
assert template.name == 'index.html'
|
||||||
|
assert len(context['items']) == 10
|
||||||
|
|
||||||
|
All the template rendering in the code issued by the application `app`
|
||||||
|
in the body of the `with` block will now be recorded in the `templates`
|
||||||
|
variable. Whenever a template is rendered, the template object as well as
|
||||||
|
context are appended to it.
|
||||||
|
|
||||||
|
Additionally there is a convenient helper method
|
||||||
|
(:meth:`~blinker.base.Signal.connected_to`). that allows you to
|
||||||
|
temporarily subscribe a function to a signal with is a context manager on
|
||||||
|
its own which simplifies the example above::
|
||||||
|
|
||||||
|
from flask import template_rendered
|
||||||
|
|
||||||
|
def captured_templates(app):
|
||||||
|
recorded = []
|
||||||
|
def record(sender, template, context):
|
||||||
|
recorded.append((template, context))
|
||||||
|
return template_rendered.connected_to(record, app)
|
||||||
|
|
||||||
|
.. admonition:: Blinker API Changes
|
||||||
|
|
||||||
|
The :meth:`~blinker.base.Signal.connected_to` method arrived in Blinker
|
||||||
|
with version 1.1.
|
||||||
|
|
||||||
|
Creating Signals
|
||||||
|
----------------
|
||||||
|
|
||||||
|
If you want to use signals in your own application, you can use the
|
||||||
|
blinker library directly. The most common use case are named signals in a
|
||||||
|
custom :class:`~blinker.base.Namespace`.. This is what is recommended
|
||||||
|
most of the time::
|
||||||
|
|
||||||
|
from blinker import Namespace
|
||||||
|
my_signals = Namespace()
|
||||||
|
|
||||||
|
Now you can create new signals like this::
|
||||||
|
|
||||||
|
model_saved = my_signals.signal('model-saved')
|
||||||
|
|
||||||
|
The name for the signal here makes it unique and also simplifies
|
||||||
|
debugging. You can access the name of the signal with the
|
||||||
|
:attr:`~blinker.base.NamedSignal.name` attribute.
|
||||||
|
|
||||||
|
.. admonition:: For Extension Developers
|
||||||
|
|
||||||
|
If you are writing a Flask extension and you to gracefully degrade for
|
||||||
|
missing blinker installations, you can do so by using the
|
||||||
|
:class:`flask.signals.Namespace` class.
|
||||||
|
|
||||||
|
Sending Signals
|
||||||
|
---------------
|
||||||
|
|
||||||
|
If you want to emit a signal, you can do so by calling the
|
||||||
|
:meth:`~blinker.base.Signal.send` method. It accepts a sender as first
|
||||||
|
argument and optionally some keyword arguments that are forwarded to the
|
||||||
|
signal subscribers::
|
||||||
|
|
||||||
|
class Model(object):
|
||||||
|
...
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
model_saved.send(self)
|
||||||
|
|
||||||
|
Try to always pick a good sender. If you have a class that is emitting a
|
||||||
|
signal, pass `self` as sender. If you emitting a signal from a random
|
||||||
|
function, you can pass ``current_app._get_current_object()`` as sender.
|
||||||
|
|
||||||
|
.. admonition:: Passing Proxies as Senders
|
||||||
|
|
||||||
|
Never pass :data:`~flask.current_app` as sender to a signal. Use
|
||||||
|
``current_app._get_current_object()`` instead. The reason for this is
|
||||||
|
that :data:`~flask.current_app` is a proxy and not the real application
|
||||||
|
object.
|
||||||
|
|
||||||
|
Decorator Based Signal Subscriptions
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
With Blinker 1.1 you can also easily subscribe to signals by using the new
|
||||||
|
:meth:`~blinker.base.NamedSignal.connect_via` decorator::
|
||||||
|
|
||||||
|
from flask import template_rendered
|
||||||
|
|
||||||
|
@template_rendered.connect_via(app)
|
||||||
|
def when_template_rendered(sender, template, context):
|
||||||
|
print 'Template %s is rendered with %s' % (template.name, context)
|
||||||
|
|
||||||
|
Core Signals
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. when modifying this list, also update the one in api.rst
|
||||||
|
|
||||||
|
The following signals exist in Flask:
|
||||||
|
|
||||||
|
.. data:: flask.template_rendered
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
This signal is sent when a template was successfully rendered. The
|
||||||
|
signal is invoked with the instance of the template as `template`
|
||||||
|
and the context as dictionary (named `context`).
|
||||||
|
|
||||||
|
Example subscriber::
|
||||||
|
|
||||||
|
def log_template_renders(sender, template, context):
|
||||||
|
sender.logger.debug('Rendering template "%s" with context %s',
|
||||||
|
template.name or 'string template',
|
||||||
|
context)
|
||||||
|
|
||||||
|
from flask import request_started
|
||||||
|
request_started.connect(log_template_renders, app)
|
||||||
|
|
||||||
|
.. data:: flask.request_started
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
This signal is sent before any request processing started but when the
|
||||||
|
request context was set up. Because the request context is already
|
||||||
|
bound, the subscriber can access the request with the standard global
|
||||||
|
proxies such as :class:`~flask.request`.
|
||||||
|
|
||||||
|
Example subscriber::
|
||||||
|
|
||||||
|
def log_request(sender):
|
||||||
|
sender.logger.debug('Request context is set up')
|
||||||
|
|
||||||
|
from flask import request_started
|
||||||
|
request_started.connect(log_request, app)
|
||||||
|
|
||||||
|
.. data:: flask.request_finished
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
This signal is sent right before the response is sent to the client.
|
||||||
|
It is passed the response to be sent named `response`.
|
||||||
|
|
||||||
|
Example subscriber::
|
||||||
|
|
||||||
|
def log_response(sender, response):
|
||||||
|
sender.logger.debug('Request context is about to close down. '
|
||||||
|
'Response: %s', response)
|
||||||
|
|
||||||
|
from flask import request_finished
|
||||||
|
request_finished.connect(log_response, app)
|
||||||
|
|
||||||
|
.. data:: flask.got_request_exception
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
This signal is sent when an exception happens during request processing.
|
||||||
|
It is sent *before* the standard exception handling kicks in and even
|
||||||
|
in debug mode, where no exception handling happens. The exception
|
||||||
|
itself is passed to the subscriber as `exception`.
|
||||||
|
|
||||||
|
Example subscriber::
|
||||||
|
|
||||||
|
def log_exception(sender, exception):
|
||||||
|
sender.logger.debug('Got exception during processing: %s', exception)
|
||||||
|
|
||||||
|
from flask import got_request_exception
|
||||||
|
got_request_exception.connect(log_exception, app)
|
||||||
|
|
||||||
|
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||||
200
docs/styleguide.rst
Normal file
200
docs/styleguide.rst
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
Pocoo Styleguide
|
||||||
|
================
|
||||||
|
|
||||||
|
The Pocoo styleguide is the styleguide for all Pocoo Projects, including
|
||||||
|
Flask. This styleguide is a requirement for Patches to Flask and a
|
||||||
|
recommendation for Flask extensions.
|
||||||
|
|
||||||
|
In general the Pocoo Styleguide closely follows :pep:`8` with some small
|
||||||
|
differences and extensions.
|
||||||
|
|
||||||
|
General Layout
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Indentation:
|
||||||
|
4 real spaces. No tabs, no exceptions.
|
||||||
|
|
||||||
|
Maximum line length:
|
||||||
|
79 characters with a soft limit for 84 if absolutely necessary. Try
|
||||||
|
to avoid too nested code by cleverly placing `break`, `continue` and
|
||||||
|
`return` statements.
|
||||||
|
|
||||||
|
Continuing long statements:
|
||||||
|
To continue a statement you can use backslashes in which case you should
|
||||||
|
align the next line with the last dot or equal sign, or indent four
|
||||||
|
spaces::
|
||||||
|
|
||||||
|
this_is_a_very_long(function_call, 'with many parameters') \
|
||||||
|
.that_returns_an_object_with_an_attribute
|
||||||
|
|
||||||
|
MyModel.query.filter(MyModel.scalar > 120) \
|
||||||
|
.order_by(MyModel.name.desc()) \
|
||||||
|
.limit(10)
|
||||||
|
|
||||||
|
If you break in a statement with parentheses or braces, align to the
|
||||||
|
braces::
|
||||||
|
|
||||||
|
this_is_a_very_long(function_call, 'with many parameters',
|
||||||
|
23, 42, 'and even more')
|
||||||
|
|
||||||
|
For lists or tuples with many items, break immediately after the
|
||||||
|
opening brace::
|
||||||
|
|
||||||
|
items = [
|
||||||
|
'this is the first', 'set of items', 'with more items',
|
||||||
|
'to come in this line', 'like this'
|
||||||
|
]
|
||||||
|
|
||||||
|
Blank lines:
|
||||||
|
Top level functions and classes are separated by two lines, everything
|
||||||
|
else by one. Do not use too many blank lines to separate logical
|
||||||
|
segments in code. Example::
|
||||||
|
|
||||||
|
def hello(name):
|
||||||
|
print 'Hello %s!' % name
|
||||||
|
|
||||||
|
|
||||||
|
def goodbye(name):
|
||||||
|
print 'See you %s.' % name
|
||||||
|
|
||||||
|
|
||||||
|
class MyClass(object):
|
||||||
|
"""This is a simple docstring"""
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def get_annoying_name(self):
|
||||||
|
return self.name.upper() + '!!!!111'
|
||||||
|
|
||||||
|
Expressions and Statements
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
General whitespace rules:
|
||||||
|
- No whitespace for unary operators that are not words
|
||||||
|
(e.g.: ``-``, ``~`` etc.) as well on the inner side of parentheses.
|
||||||
|
- Whitespace is placed between binary operators.
|
||||||
|
|
||||||
|
Good::
|
||||||
|
|
||||||
|
exp = -1.05
|
||||||
|
value = (item_value / item_count) * offset / exp
|
||||||
|
value = my_list[index]
|
||||||
|
value = my_dict['key']
|
||||||
|
|
||||||
|
Bad::
|
||||||
|
|
||||||
|
exp = - 1.05
|
||||||
|
value = ( item_value / item_count ) * offset / exp
|
||||||
|
value = (item_value/item_count)*offset/exp
|
||||||
|
value=( item_value/item_count ) * offset/exp
|
||||||
|
value = my_list[ index ]
|
||||||
|
value = my_dict ['key']
|
||||||
|
|
||||||
|
Yoda statements are a nogo:
|
||||||
|
Never compare constant with variable, always variable with constant:
|
||||||
|
|
||||||
|
Good::
|
||||||
|
|
||||||
|
if method == 'md5':
|
||||||
|
pass
|
||||||
|
|
||||||
|
Bad::
|
||||||
|
|
||||||
|
if 'md5' == method:
|
||||||
|
pass
|
||||||
|
|
||||||
|
Comparisons:
|
||||||
|
- against arbitrary types: ``==`` and ``!=``
|
||||||
|
- against singletons with ``is`` and ``is not`` (eg: ``foo is not
|
||||||
|
None``)
|
||||||
|
- never compare something with `True` or `False` (for example never
|
||||||
|
do ``foo == False``, do ``not foo`` instead)
|
||||||
|
|
||||||
|
Negated containment checks:
|
||||||
|
use ``foo not in bar`` instead of ``not foo in bar``
|
||||||
|
|
||||||
|
Instance checks:
|
||||||
|
``isinstance(a, C)`` instead of ``type(A) is C``, but try to avoid
|
||||||
|
instance checks in general. Check for features.
|
||||||
|
|
||||||
|
|
||||||
|
Naming Conventions
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Class names: ``CamelCase``, with acronyms kept uppercase (``HTTPWriter``
|
||||||
|
and not ``HttpWriter``)
|
||||||
|
- Variable names: ``lowercase_with_underscores``
|
||||||
|
- Method and function names: ``lowercase_with_underscores``
|
||||||
|
- Constants: ``UPPERCASE_WITH_UNDERSCORES``
|
||||||
|
- precompiled regular expressions: ``name_re``
|
||||||
|
|
||||||
|
Protected members are prefixed with a single underscore. Double
|
||||||
|
underscores are reserved for mixin classes.
|
||||||
|
|
||||||
|
On classes with keywords, trailing underscores are appended. Clashes with
|
||||||
|
builtins are allowed and **must not** be resolved by appending an
|
||||||
|
underline to the variable name. If the function needs to access a
|
||||||
|
shadowed builtin, rebind the builtin to a different name instead.
|
||||||
|
|
||||||
|
Function and method arguments:
|
||||||
|
- class methods: ``cls`` as first parameter
|
||||||
|
- instance methods: ``self`` as first parameter
|
||||||
|
- lambdas for properties might have the first parameter replaced
|
||||||
|
with ``x`` like in ``display_name = property(lambda x: x.real_name
|
||||||
|
or x.username)``
|
||||||
|
|
||||||
|
|
||||||
|
Docstrings
|
||||||
|
----------
|
||||||
|
|
||||||
|
Docstring conventions:
|
||||||
|
All docstrings are formatted with reStructuredText as understood by
|
||||||
|
Sphinx. Depending on the number of lines in the docstring, they are
|
||||||
|
laid out differently. If it's just one line, the closing triple
|
||||||
|
quote is on the same line as the opening, otherwise the text is on
|
||||||
|
the same line as the opening quote and the triple quote that closes
|
||||||
|
the string on its own line::
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
"""This is a simple docstring"""
|
||||||
|
|
||||||
|
|
||||||
|
def bar():
|
||||||
|
"""This is a longer docstring with so much information in there
|
||||||
|
that it spans three lines. In this case the closing triple quote
|
||||||
|
is on its own line.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Module header:
|
||||||
|
The module header consists of an utf-8 encoding declaration (if non
|
||||||
|
ASCII letters are used, but it is recommended all the time) and a
|
||||||
|
standard docstring::
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
package.module
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A brief description goes here.
|
||||||
|
|
||||||
|
:copyright: (c) YEAR by AUTHOR.
|
||||||
|
:license: LICENSE_NAME, see LICENSE_FILE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Please keep in mind that proper copyrights and license files are a
|
||||||
|
requirement for approved Flask extensions.
|
||||||
|
|
||||||
|
|
||||||
|
Comments
|
||||||
|
--------
|
||||||
|
|
||||||
|
Rules for comments are similar to docstrings. Both are formatted with
|
||||||
|
reStructuredText. If a comment is used to document an attribute, put a
|
||||||
|
colon after the opening pound sign (``#``)::
|
||||||
|
|
||||||
|
class User(object):
|
||||||
|
#: the name of the user as unicode string
|
||||||
|
name = Column(String)
|
||||||
|
#: the sha1 hash of the password + inline salt
|
||||||
|
pw_hash = Column(String)
|
||||||
188
docs/templating.rst
Normal file
188
docs/templating.rst
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
Templates
|
||||||
|
=========
|
||||||
|
|
||||||
|
Flask leverages Jinja2 as template engine. You are obviously free to use
|
||||||
|
a different template engine, but you still have to install Jinja2 to run
|
||||||
|
Flask itself. This requirement is necessary to enable rich extensions.
|
||||||
|
An extension can depend on Jinja2 being present.
|
||||||
|
|
||||||
|
This section only gives a very quick introduction into how Jinja2
|
||||||
|
is integrated into Flask. If you want information on the template
|
||||||
|
engine's syntax itself, head over to the official `Jinja2 Template
|
||||||
|
Documentation <http://jinja.pocoo.org/2/documentation/templates>`_ for
|
||||||
|
more information.
|
||||||
|
|
||||||
|
Jinja Setup
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Unless customized, Jinja2 is configured by Flask as follows:
|
||||||
|
|
||||||
|
- autoescaping is enabled for all templates ending in ``.html``,
|
||||||
|
``.htm``, ``.xml`` as well as ``.xhtml``
|
||||||
|
- a template has the ability to opt in/out autoescaping with the
|
||||||
|
``{% autoescape %}`` tag.
|
||||||
|
- Flask inserts a couple of global functions and helpers into the
|
||||||
|
Jinja2 context, additionally to the values that are present by
|
||||||
|
default.
|
||||||
|
|
||||||
|
Standard Context
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The following global variables are available within Jinja2 templates
|
||||||
|
by default:
|
||||||
|
|
||||||
|
.. data:: config
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
The current configuration object (:data:`flask.config`)
|
||||||
|
|
||||||
|
.. versionadded:: 0.6
|
||||||
|
|
||||||
|
.. data:: request
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
The current request object (:class:`flask.request`)
|
||||||
|
|
||||||
|
.. data:: session
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
The current session object (:class:`flask.session`)
|
||||||
|
|
||||||
|
.. data:: g
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
The request-bound object for global variables (:data:`flask.g`)
|
||||||
|
|
||||||
|
.. function:: url_for
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
The :func:`flask.url_for` function.
|
||||||
|
|
||||||
|
.. function:: get_flashed_messages
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
The :func:`flask.get_flashed_messages` function.
|
||||||
|
|
||||||
|
.. admonition:: The Jinja Context Behaviour
|
||||||
|
|
||||||
|
These variables are added to the context of variables, they are not
|
||||||
|
global variables. The difference is that by default these will not
|
||||||
|
show up in the context of imported templates. This is partially caused
|
||||||
|
by performance considerations, partially to keep things explicit.
|
||||||
|
|
||||||
|
What does this mean for you? If you have a macro you want to import,
|
||||||
|
that needs to access the request object you have two possibilities:
|
||||||
|
|
||||||
|
1. you explicitly pass the request to the macro as parameter, or
|
||||||
|
the attribute of the request object you are interested in.
|
||||||
|
2. you import the macro "with context".
|
||||||
|
|
||||||
|
Importing with context looks like this:
|
||||||
|
|
||||||
|
.. sourcecode:: jinja
|
||||||
|
|
||||||
|
{% from '_helpers.html' import my_macro with context %}
|
||||||
|
|
||||||
|
Standard Filters
|
||||||
|
----------------
|
||||||
|
|
||||||
|
These filters are available in Jinja2 additionally to the filters provided
|
||||||
|
by Jinja2 itself:
|
||||||
|
|
||||||
|
.. function:: tojson
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
This function converts the given object into JSON representation. This
|
||||||
|
is for example very helpful if you try to generate JavaScript on the
|
||||||
|
fly.
|
||||||
|
|
||||||
|
Note that inside `script` tags no escaping must take place, so make
|
||||||
|
sure to disable escaping with ``|safe`` if you intend to use it inside
|
||||||
|
`script` tags:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
<script type=text/javascript>
|
||||||
|
doSomethingWith({{ user.username|tojson|safe }});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
That the ``|tojson`` filter escapes forward slashes properly for you.
|
||||||
|
|
||||||
|
Controlling Autoescaping
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Autoescaping is the concept of automatically escaping special characters
|
||||||
|
of you. Special characters in the sense of HTML (or XML, and thus XHTML)
|
||||||
|
are ``&``, ``>``, ``<``, ``"`` as well as ``'``. Because these characters
|
||||||
|
carry specific meanings in documents on their own you have to replace them
|
||||||
|
by so called "entities" if you want to use them for text. Not doing so
|
||||||
|
would not only cause user frustration by the inability to use these
|
||||||
|
characters in text, but can also lead to security problems. (see
|
||||||
|
:ref:`xss`)
|
||||||
|
|
||||||
|
Sometimes however you will need to disable autoescaping in templates.
|
||||||
|
This can be the case if you want to explicitly inject HTML into pages, for
|
||||||
|
example if they come from a system that generate secure HTML like a
|
||||||
|
markdown to HTML converter.
|
||||||
|
|
||||||
|
There are three ways to accomplish that:
|
||||||
|
|
||||||
|
- In the Python code, wrap the HTML string in a :class:`~flask.Markup`
|
||||||
|
object before passing it to the template. This is in general the
|
||||||
|
recommended way.
|
||||||
|
- Inside the template, use the ``|safe`` filter to explicitly mark a
|
||||||
|
string as safe HTML (``{{ myvariable|safe }}``)
|
||||||
|
- Temporarily disable the autoescape system altogether.
|
||||||
|
|
||||||
|
To disable the autoescape system in templates, you can use the ``{%
|
||||||
|
autoescape %}`` block:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
{% autoescape false %}
|
||||||
|
<p>autoescaping is disabled here
|
||||||
|
<p>{{ will_not_be_escaped }}
|
||||||
|
{% endautoescape %}
|
||||||
|
|
||||||
|
Whenever you do this, please be very cautious about the variables you are
|
||||||
|
using in this block.
|
||||||
|
|
||||||
|
Registering Filters
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
If you want to register your own filters in Jinja2 you have two ways to do
|
||||||
|
that. You can either put them by hand into the
|
||||||
|
:attr:`~flask.Flask.jinja_env` of the application or use the
|
||||||
|
:meth:`~flask.Flask.template_filter` decorator.
|
||||||
|
|
||||||
|
The two following examples work the same and both reverse an object::
|
||||||
|
|
||||||
|
@app.template_filter('reverse')
|
||||||
|
def reverse_filter(s):
|
||||||
|
return s[::-1]
|
||||||
|
|
||||||
|
def reverse_filter(s):
|
||||||
|
return s[::-1]
|
||||||
|
app.jinja_env.filters['reverse'] = reverse_filter
|
||||||
|
|
||||||
|
In case of the decorator the argument is optional if you want to use the
|
||||||
|
function name as name of the filter.
|
||||||
|
|
||||||
|
Context Processors
|
||||||
|
------------------
|
||||||
|
|
||||||
|
To inject new variables automatically into the context of a template
|
||||||
|
context processors exist in Flask. Context processors run before the
|
||||||
|
template is rendered and have the ability to inject new values into the
|
||||||
|
template context. A context processor is a function that returns a
|
||||||
|
dictionary. The keys and values of this dictionary are then merged with
|
||||||
|
the template context::
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_user():
|
||||||
|
return dict(user=g.user)
|
||||||
|
|
||||||
|
The context processor above makes a variable called `user` available in
|
||||||
|
the template with the value of `g.user`. This example is not very
|
||||||
|
interesting because `g` is available in templates anyways, but it gives an
|
||||||
|
idea how this works.
|
||||||
|
|
@ -8,8 +8,8 @@ Testing Flask Applications
|
||||||
Not sure where that is coming from, and it's not entirely correct, but
|
Not sure where that is coming from, and it's not entirely correct, but
|
||||||
also not that far from the truth. Untested applications make it hard to
|
also not that far from the truth. Untested applications make it hard to
|
||||||
improve existing code and developers of untested applications tend to
|
improve existing code and developers of untested applications tend to
|
||||||
become pretty paranoid. If an application however has automated tests, you
|
become pretty paranoid. If an application has automated tests, you can
|
||||||
can safely change things and you will instantly know if your change broke
|
safely change things, and you will instantly know if your change broke
|
||||||
something.
|
something.
|
||||||
|
|
||||||
Flask gives you a couple of ways to test applications. It mainly does
|
Flask gives you a couple of ways to test applications. It mainly does
|
||||||
|
|
@ -43,13 +43,13 @@ In order to test that, we add a second module (
|
||||||
class FlaskrTestCase(unittest.TestCase):
|
class FlaskrTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.db_fd, flaskr.DATABASE = tempfile.mkstemp()
|
self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
|
||||||
self.app = flaskr.app.test_client()
|
self.app = flaskr.app.test_client()
|
||||||
flaskr.init_db()
|
flaskr.init_db()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
os.close(self.db_fd)
|
os.close(self.db_fd)
|
||||||
os.unlink(flaskr.DATABASE)
|
os.unlink(flaskr.app.config['DATABASE'])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
@ -60,7 +60,7 @@ each individual test function. To delete the database after the test, we
|
||||||
close the file and remove it from the filesystem in the
|
close the file and remove it from the filesystem in the
|
||||||
:meth:`~unittest.TestCase.tearDown` method. What the test client does is
|
:meth:`~unittest.TestCase.tearDown` method. What the test client does is
|
||||||
give us a simple interface to the application. We can trigger test
|
give us a simple interface to the application. We can trigger test
|
||||||
requests to the application and the client will also keep track of cookies
|
requests to the application, and the client will also keep track of cookies
|
||||||
for us.
|
for us.
|
||||||
|
|
||||||
Because SQLite3 is filesystem-based we can easily use the tempfile module
|
Because SQLite3 is filesystem-based we can easily use the tempfile module
|
||||||
|
|
@ -130,7 +130,7 @@ Logging In and Out
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
The majority of the functionality of our application is only available for
|
The majority of the functionality of our application is only available for
|
||||||
the administration user. So we need a way to log our test client in to the
|
the administrative user, so we need a way to log our test client in to the
|
||||||
application and out of it again. For that we fire some requests to the
|
application and out of it again. For that we fire some requests to the
|
||||||
login and logout pages with the required form data (username and
|
login and logout pages with the required form data (username and
|
||||||
password). Because the login and logout pages redirect, we tell the
|
password). Because the login and logout pages redirect, we tell the
|
||||||
|
|
@ -200,12 +200,12 @@ suite.
|
||||||
Other Testing Tricks
|
Other Testing Tricks
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
Besides using the test client we used above there is also the
|
Besides using the test client we used above, there is also the
|
||||||
:meth:`~flask.Flask.test_request_context` method that in combination with
|
:meth:`~flask.Flask.test_request_context` method that in combination with
|
||||||
the `with` statement can be used to activate a request context
|
the `with` statement can be used to activate a request context
|
||||||
temporarily. With that you can access the :class:`~flask.request`,
|
temporarily. With that you can access the :class:`~flask.request`,
|
||||||
:class:`~flask.g` and :class:`~flask.session` objects like in view
|
:class:`~flask.g` and :class:`~flask.session` objects like in view
|
||||||
functions. Here a full example that showcases this::
|
functions. Here's a full example that showcases this::
|
||||||
|
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ The `secret_key` is needed to keep the client-side sessions secure.
|
||||||
Choose that key wisely and as hard to guess and complex as possible. The
|
Choose that key wisely and as hard to guess and complex as possible. The
|
||||||
debug flag enables or disables the interactive debugger. Never leave
|
debug flag enables or disables the interactive debugger. Never leave
|
||||||
debug mode activated in a production system because it will allow users to
|
debug mode activated in a production system because it will allow users to
|
||||||
executed code on the server!
|
execute code on the server!
|
||||||
|
|
||||||
We also add a method to easily connect to the database specified. That
|
We also add a method to easily connect to the database specified. That
|
||||||
can be used to open a connection on request and also from the interactive
|
can be used to open a connection on request and also from the interactive
|
||||||
|
|
@ -64,7 +64,7 @@ Python shell or a script. This will come in handy later
|
||||||
return sqlite3.connect(app.config['DATABASE'])
|
return sqlite3.connect(app.config['DATABASE'])
|
||||||
|
|
||||||
Finally we just add a line to the bottom of the file that fires up the
|
Finally we just add a line to the bottom of the file that fires up the
|
||||||
server if we run that file as standalone application::
|
server if we want to run that file as a standalone application::
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run()
|
app.run()
|
||||||
|
|
@ -76,7 +76,7 @@ focus on that a little later. First we should get the database working.
|
||||||
|
|
||||||
.. admonition:: Externally Visible Server
|
.. admonition:: Externally Visible Server
|
||||||
|
|
||||||
Want your server to be publically available? Check out the
|
Want your server to be publicly available? Check out the
|
||||||
:ref:`externally visible server <public-server>` section for more
|
:ref:`externally visible server <public-server>` section for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Bonus: Testing the Application
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
Now that you have finished the application and everything works as
|
Now that you have finished the application and everything works as
|
||||||
expected, it's probably not a good idea to add automated tests to simplify
|
expected, it's probably not a bad idea to add automated tests to simplify
|
||||||
modifications in the future. The application above is used as a basic
|
modifications in the future. The application above is used as a basic
|
||||||
example of how to perform unittesting in the :ref:`testing` section of the
|
example of how to perform unittesting in the :ref:`testing` section of the
|
||||||
documentation. Go there to see how easy it is to test Flask applications.
|
documentation. Go there to see how easy it is to test Flask applications.
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@ Show Entries
|
||||||
|
|
||||||
This view shows all the entries stored in the database. It listens on the
|
This view shows all the entries stored in the database. It listens on the
|
||||||
root of the application and will select title and text from the database.
|
root of the application and will select title and text from the database.
|
||||||
The one with the highest id (the newest entry) on top. The rows returned
|
The one with the highest id (the newest entry) will be on top. The rows
|
||||||
from the cursor are tuples with the columns ordered like specified in the
|
returned from the cursor are tuples with the columns ordered like specified
|
||||||
select statement. This is good enough for small applications like here,
|
in the select statement. This is good enough for small applications like
|
||||||
but you might want to convert them into a dict. If you are interested how
|
here, but you might want to convert them into a dict. If you are
|
||||||
to do that, check out the :ref:`easy-querying` example.
|
interested in how to do that, check out the :ref:`easy-querying` example.
|
||||||
|
|
||||||
The view function will pass the entries as dicts to the
|
The view function will pass the entries as dicts to the
|
||||||
`show_entries.html` template and return the rendered one::
|
`show_entries.html` template and return the rendered one::
|
||||||
|
|
@ -48,16 +48,23 @@ redirect back to the `show_entries` page::
|
||||||
Note that we check that the user is logged in here (the `logged_in` key is
|
Note that we check that the user is logged in here (the `logged_in` key is
|
||||||
present in the session and `True`).
|
present in the session and `True`).
|
||||||
|
|
||||||
|
.. admonition:: Security Note
|
||||||
|
|
||||||
|
Be sure to use question marks when building SQL statements, as done in the
|
||||||
|
example above. Otherwise, your app will be vulnerable to SQL injection when
|
||||||
|
you use string formatting to build SQL statements.
|
||||||
|
See :ref:`sqlite3` for more.
|
||||||
|
|
||||||
Login and Logout
|
Login and Logout
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
These functions are used to sign the user in and out. Login checks the
|
These functions are used to sign the user in and out. Login checks the
|
||||||
username and password against the ones from the configuration and sets the
|
username and password against the ones from the configuration and sets the
|
||||||
`logged_in` key in the session. If the user logged in successfully that
|
`logged_in` key in the session. If the user logged in successfully, that
|
||||||
key is set to `True` and the user is redirected back to the `show_entries`
|
key is set to `True`, and the user is redirected back to the `show_entries`
|
||||||
page. In that case also a message is flashed that informs the user he or
|
page. In addition, a message is flashed that informs the user that he or
|
||||||
she was logged in successfully. If an error occoured the template is
|
she was logged in successfully. If an error occurred, the template is
|
||||||
notified about that and the user asked again::
|
notified about that, and the user is asked again::
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
|
|
@ -73,12 +80,12 @@ notified about that and the user asked again::
|
||||||
return redirect(url_for('show_entries'))
|
return redirect(url_for('show_entries'))
|
||||||
return render_template('login.html', error=error)
|
return render_template('login.html', error=error)
|
||||||
|
|
||||||
The logout function on the other hand removes that key from the session
|
The logout function, on the other hand, removes that key from the session
|
||||||
again. We use a neat trick here: if you use the :meth:`~dict.pop` method
|
again. We use a neat trick here: if you use the :meth:`~dict.pop` method
|
||||||
of the dict and pass a second parameter to it (the default) the method
|
of the dict and pass a second parameter to it (the default), the method
|
||||||
will delete the key from the dictionary if present or do nothing when that
|
will delete the key from the dictionary if present or do nothing when that
|
||||||
key was not in there. This is helpful because we don't have to check in
|
key is not in there. This is helpful because now we don't have to check
|
||||||
that case if the user was logged in.
|
if the user was logged in.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ should probably read `The Absolute Minimum Every Software Developer
|
||||||
Absolutely, Positively Must Know About Unicode and Character Sets
|
Absolutely, Positively Must Know About Unicode and Character Sets
|
||||||
<http://www.joelonsoftware.com/articles/Unicode.html>`_. This part of the
|
<http://www.joelonsoftware.com/articles/Unicode.html>`_. This part of the
|
||||||
documentation just tries to cover the very basics so that you have a
|
documentation just tries to cover the very basics so that you have a
|
||||||
pleasent experience with unicode related things.
|
pleasant experience with unicode related things.
|
||||||
|
|
||||||
Automatic Conversion
|
Automatic Conversion
|
||||||
--------------------
|
--------------------
|
||||||
|
|
@ -54,6 +54,8 @@ unicode. What does working with unicode in Python 2.x mean?
|
||||||
UTF-8 for this purpose. To tell the interpreter your encoding you can
|
UTF-8 for this purpose. To tell the interpreter your encoding you can
|
||||||
put the ``# -*- coding: utf-8 -*-`` into the first or second line of
|
put the ``# -*- coding: utf-8 -*-`` into the first or second line of
|
||||||
your Python source file.
|
your Python source file.
|
||||||
|
- Jinja is configured to decode the template files from UTF-8. So make
|
||||||
|
sure to tell your editor to save the file as UTF-8 there as well.
|
||||||
|
|
||||||
Encoding and Decoding Yourself
|
Encoding and Decoding Yourself
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
@ -61,12 +63,12 @@ Encoding and Decoding Yourself
|
||||||
If you are talking with a filesystem or something that is not really based
|
If you are talking with a filesystem or something that is not really based
|
||||||
on unicode you will have to ensure that you decode properly when working
|
on unicode you will have to ensure that you decode properly when working
|
||||||
with unicode interface. So for example if you want to load a file on the
|
with unicode interface. So for example if you want to load a file on the
|
||||||
filesystem and embedd it into a Jinja2 template you will have to decode it
|
filesystem and embed it into a Jinja2 template you will have to decode it
|
||||||
form the encoding of that file. Here the old problem that textfiles do
|
from the encoding of that file. Here the old problem that text files do
|
||||||
not specify their encoding comes into play. So do yourself a favour and
|
not specify their encoding comes into play. So do yourself a favour and
|
||||||
limit yourself to UTF-8 for textfiles as well.
|
limit yourself to UTF-8 for text files as well.
|
||||||
|
|
||||||
Anyways. To load such a file with unicode you can use the builtin
|
Anyways. To load such a file with unicode you can use the built-in
|
||||||
:meth:`str.decode` method::
|
:meth:`str.decode` method::
|
||||||
|
|
||||||
def read_file(filename, charset='utf-8'):
|
def read_file(filename, charset='utf-8'):
|
||||||
|
|
@ -79,3 +81,27 @@ To go from unicode into a specific charset such as UTF-8 you can use the
|
||||||
def write_file(filename, contents, charset='utf-8'):
|
def write_file(filename, contents, charset='utf-8'):
|
||||||
with open(filename, 'w') as f:
|
with open(filename, 'w') as f:
|
||||||
f.write(contents.encode(charset))
|
f.write(contents.encode(charset))
|
||||||
|
|
||||||
|
Configuring Editors
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Most editors save as UTF-8 by default nowadays but in case your editor is
|
||||||
|
not configured to do this you have to change it. Here some common ways to
|
||||||
|
set your editor to store as UTF-8:
|
||||||
|
|
||||||
|
- Vim: put ``set enc=utf-8`` to your ``.vimrc`` file.
|
||||||
|
|
||||||
|
- Emacs: either use an encoding cookie or put this into your ``.emacs``
|
||||||
|
file::
|
||||||
|
|
||||||
|
(prefer-coding-system 'utf-8)
|
||||||
|
(setq default-buffer-file-coding-system 'utf-8)
|
||||||
|
|
||||||
|
- Notepad++:
|
||||||
|
|
||||||
|
1. Go to *Settings -> Preferences ...*
|
||||||
|
2. Select the "New Document/Default Directory" tab
|
||||||
|
3. Select "UTF-8 without BOM" as encoding
|
||||||
|
|
||||||
|
It is also recommended to use the Unix newline format, you can select
|
||||||
|
it in the same panel but this is not a requirement.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ Upgrading to Newer Releases
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
Flask itself is changing like any software is changing over time. Most of
|
Flask itself is changing like any software is changing over time. Most of
|
||||||
the changes are the nice kind, the kind where you don't have th change
|
the changes are the nice kind, the kind where you don't have to change
|
||||||
anything in your code to profit from a new release.
|
anything in your code to profit from a new release.
|
||||||
|
|
||||||
However every once in a while there are changes that do require some
|
However every once in a while there are changes that do require some
|
||||||
|
|
@ -19,6 +19,74 @@ installation, make sure to pass it the ``-U`` parameter::
|
||||||
|
|
||||||
$ easy_install -U Flask
|
$ easy_install -U Flask
|
||||||
|
|
||||||
|
Version 0.7
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Due to a bug in earlier implementations the request local proxies now
|
||||||
|
raise a :exc:`RuntimeError` instead of an :exc:`AttributeError` when they
|
||||||
|
are unbound. If you caught these exceptions with :exc:`AttributeError`
|
||||||
|
before, you should catch them with :exc:`RuntimeError` now.
|
||||||
|
|
||||||
|
Additionally the :func:`~flask.send_file` function is now issuing
|
||||||
|
deprecation warnings if you depend on functionality that will be removed
|
||||||
|
in Flask 1.0. Previously it was possible to use etags and mimetypes
|
||||||
|
when file objects were passed. This was unreliable and caused issues
|
||||||
|
for a few setups. If you get a deprecation warning, make sure to
|
||||||
|
update your application to work with either filenames there or disable
|
||||||
|
etag attaching and attach them yourself.
|
||||||
|
|
||||||
|
Old code::
|
||||||
|
|
||||||
|
return send_file(my_file_object)
|
||||||
|
return send_file(my_file_object)
|
||||||
|
|
||||||
|
New code::
|
||||||
|
|
||||||
|
return send_file(my_file_object, add_etags=False)
|
||||||
|
|
||||||
|
Version 0.6
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Flask 0.6 comes with a backwards incompatible change which affects the
|
||||||
|
order of after-request handlers. Previously they were called in the order
|
||||||
|
of the registration, now they are called in reverse order. This change
|
||||||
|
was made so that Flask behaves more like people expected it to work and
|
||||||
|
how other systems handle request pre- and postprocessing. If you
|
||||||
|
depend on the order of execution of post-request functions, be sure to
|
||||||
|
change the order.
|
||||||
|
|
||||||
|
Another change that breaks backwards compatibility is that context
|
||||||
|
processors will no longer override values passed directly to the template
|
||||||
|
rendering function. If for example `request` is as variable passed
|
||||||
|
directly to the template, the default context processor will not override
|
||||||
|
it with the current request object. This makes it easier to extend
|
||||||
|
context processors later to inject additional variables without breaking
|
||||||
|
existing template not expecting them.
|
||||||
|
|
||||||
|
Version 0.5
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Flask 0.5 is the first release that comes as a Python package instead of a
|
||||||
|
single module. There were a couple of internal refactoring so if you
|
||||||
|
depend on undocumented internal details you probably have to adapt the
|
||||||
|
imports.
|
||||||
|
|
||||||
|
The following changes may be relevant to your application:
|
||||||
|
|
||||||
|
- autoescaping no longer happens for all templates. Instead it is
|
||||||
|
configured to only happen on files ending with ``.html``, ``.htm``,
|
||||||
|
``.xml`` and ``.xhtml``. If you have templates with different
|
||||||
|
extensions you should override the
|
||||||
|
:meth:`~flask.Flask.select_jinja_autoescape` method.
|
||||||
|
- Flask no longer supports zipped applications in this release. This
|
||||||
|
functionality might come back in future releases if there is demand
|
||||||
|
for this feature. Removing support for this makes the Flask internal
|
||||||
|
code easier to understand and fixes a couple of small issues that make
|
||||||
|
debugging harder than necessary.
|
||||||
|
- The `create_jinja_loader` function is gone. If you want to customize
|
||||||
|
the Jinja loader now, use the
|
||||||
|
:meth:`~flask.Flask.create_jinja_environment` method instead.
|
||||||
|
|
||||||
Version 0.4
|
Version 0.4
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
<title>jQuery Example</title>
|
<title>jQuery Example</title>
|
||||||
<script type=text/javascript
|
<script type=text/javascript
|
||||||
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||||
<script type=text/javascript src="{{ url_for('static', filename='app.js')
|
|
||||||
}}"></script>
|
|
||||||
<script type=text/javascript>
|
<script type=text/javascript>
|
||||||
var $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
|
var $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
146
extreview/approved.rst
Normal file
146
extreview/approved.rst
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
Approved Extensions
|
||||||
|
===================
|
||||||
|
|
||||||
|
This document contains a list of all extensions that were approved and the
|
||||||
|
date of approval as well as notes. This should make it possible to better
|
||||||
|
track the extension approval process.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Babel
|
||||||
|
-----------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-23
|
||||||
|
:Last Review: 2010-07-23
|
||||||
|
:Approved Version: 0.6
|
||||||
|
:Approved License: BSD
|
||||||
|
|
||||||
|
Notes: Developed by the Flask development head
|
||||||
|
|
||||||
|
How to improve: add a better long description to the next release
|
||||||
|
|
||||||
|
|
||||||
|
Flask-SQLAlchemy
|
||||||
|
----------------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-25
|
||||||
|
:Last Review: 2010-07-25
|
||||||
|
:Approved Version: 0.9.1
|
||||||
|
:Approved License: BSD
|
||||||
|
|
||||||
|
Notes: Developed by the Flask development head
|
||||||
|
|
||||||
|
How to improve: add a better long description to the next release
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Creole
|
||||||
|
------------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-25
|
||||||
|
:Last Review: 2010-07-25
|
||||||
|
:Approved Version: 0.4.4
|
||||||
|
:Approved License: BSD
|
||||||
|
|
||||||
|
Notes: Flask-Markdown and this should share API, consider that when
|
||||||
|
approving Flask-Markdown
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Genshi
|
||||||
|
------------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-26
|
||||||
|
:Last Review: 2010-07-26
|
||||||
|
:Approved Version: 0.3.1
|
||||||
|
:Approved License: BSD
|
||||||
|
|
||||||
|
Notes: This is the first template engine extension. When others come
|
||||||
|
around it would be a good idea to decide on a common interface.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Script
|
||||||
|
------------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-26
|
||||||
|
:Last Review: 2010-07-26
|
||||||
|
:Approved Version: 0.3
|
||||||
|
:Approved License: BSD
|
||||||
|
|
||||||
|
Notes: Flask-Actions has some overlap. Consider that when approving
|
||||||
|
Flask-Actions or similar packages.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-CouchDB
|
||||||
|
-------------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-26
|
||||||
|
:Last Review: 2010-07-26
|
||||||
|
:Approved Version: 0.2.1
|
||||||
|
:Approved License: MIT
|
||||||
|
|
||||||
|
There is also Flask-CouchDBKit. Both are fine because they are doing
|
||||||
|
different things, but the latter is not yet approved.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Testing
|
||||||
|
-------------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-27
|
||||||
|
:Last Review: 2010-07-27
|
||||||
|
:Approved Version: 0.2.3
|
||||||
|
:Approved License: BSD
|
||||||
|
|
||||||
|
All fine.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-WTF
|
||||||
|
---------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-27
|
||||||
|
:Last Review: 2010-07-27
|
||||||
|
:Approved Version: 0.2.3
|
||||||
|
:Approved License: BSD
|
||||||
|
|
||||||
|
All fine.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Themes
|
||||||
|
------------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-27
|
||||||
|
:Last Review: 2010-07-27
|
||||||
|
:Approved Version: 0.1.2
|
||||||
|
:Approved License: MIT
|
||||||
|
|
||||||
|
All fine.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Uploads
|
||||||
|
-------------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-27
|
||||||
|
:Last Review: 2010-07-27
|
||||||
|
:Approved Version: 0.1.2
|
||||||
|
:Approved License: MIT
|
||||||
|
|
||||||
|
All fine.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Mail
|
||||||
|
----------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-29
|
||||||
|
:Last Review: 2010-07-29
|
||||||
|
:Approved Version: 0.3.4
|
||||||
|
:Approved License: BSD
|
||||||
|
|
||||||
|
All fine.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-XML-RPC
|
||||||
|
-------------
|
||||||
|
|
||||||
|
:First Approval: 2010-07-30
|
||||||
|
:Last Review: 2010-07-30
|
||||||
|
:Approved Version: 0.1.2
|
||||||
|
:Approved License: MIT
|
||||||
|
|
||||||
|
All fine.
|
||||||
64
extreview/listed.rst
Normal file
64
extreview/listed.rst
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
Listed Extensions
|
||||||
|
=================
|
||||||
|
|
||||||
|
This list contains extensions that passed listing. This means the
|
||||||
|
extension is on the list of extensions on the website. It does not
|
||||||
|
contain extensions that are approved.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-CouchDBKit
|
||||||
|
----------------
|
||||||
|
|
||||||
|
:Last-Review: 2010-07-25
|
||||||
|
:Reviewed Version: 0.2
|
||||||
|
|
||||||
|
Would be fine for approval, but the test suite is not part of the sdist
|
||||||
|
package (missing entry in MANIFEST.in) and the test suite does not respond
|
||||||
|
to either "make test" or "python setup.py test".
|
||||||
|
|
||||||
|
|
||||||
|
flask-csrf
|
||||||
|
----------
|
||||||
|
|
||||||
|
:Last-Review: 2010-07-25
|
||||||
|
:Reviewed Version: 0.2
|
||||||
|
|
||||||
|
Will not be approved because this is functionality that should be handled
|
||||||
|
in the form handling systems which is for Flask-WTF already the case.
|
||||||
|
Also, this implementation only supports one open tab with forms.
|
||||||
|
|
||||||
|
Name is not following Flask extension naming rules.
|
||||||
|
|
||||||
|
Considered for unlisting.
|
||||||
|
|
||||||
|
|
||||||
|
flask-lesscss
|
||||||
|
-------------
|
||||||
|
|
||||||
|
:Last-Review: 2010-07-25
|
||||||
|
:Reviewed Version: 0.9.1
|
||||||
|
|
||||||
|
Broken package description, nonconforming package name, does not follow
|
||||||
|
standard API rules (init_lesscss instead of lesscss).
|
||||||
|
|
||||||
|
Considered for unlisting, improved version should release as
|
||||||
|
"Flask-LessCSS" with a conforming API and fixed packages indices, as well
|
||||||
|
as a testsuite.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-OAuth
|
||||||
|
-----------
|
||||||
|
|
||||||
|
:Last-Review: 2010-07-25
|
||||||
|
:Reviewed Version: 0.9
|
||||||
|
|
||||||
|
Short long description, missing tests.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-OpenID
|
||||||
|
------------
|
||||||
|
|
||||||
|
:Last-Review: 2010-07-25
|
||||||
|
:Reviewed Version: 1.0.1
|
||||||
|
|
||||||
|
Short long description, missing tests.
|
||||||
55
extreview/unlisted.rst
Normal file
55
extreview/unlisted.rst
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
Unlisted Extensions
|
||||||
|
===================
|
||||||
|
|
||||||
|
This is a list of extensions that is currently rejected from listing and
|
||||||
|
with that also not approved. If an extension ends up here it should
|
||||||
|
improved to be listed.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Actions
|
||||||
|
-------------
|
||||||
|
|
||||||
|
:Last Review: 2010-07-25
|
||||||
|
:Reviewed Version: 0.2
|
||||||
|
|
||||||
|
Rejected because of missing description in PyPI, formatting issues with
|
||||||
|
the documentation (missing headlines, scrollbars etc.) and a general clash
|
||||||
|
of functionality with the Flask-Script package. Latter should not be a
|
||||||
|
problem, but the documentation should improve. For listing, the extension
|
||||||
|
developer should probably discuss the extension on the mailinglist with
|
||||||
|
others.
|
||||||
|
|
||||||
|
Futhermore it also has an egg registered with an invalid filename.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Jinja2Extender
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
:Last Review: 2010-07-25
|
||||||
|
:Reviewed Version: 0.1
|
||||||
|
|
||||||
|
Usecase not obvious, hacky implementation, does not solve a problem that
|
||||||
|
could not be solved with Flask itself. I suppose it is to aid other
|
||||||
|
extensions, but that should be discussed on the mailinglist.
|
||||||
|
|
||||||
|
|
||||||
|
Flask-Markdown
|
||||||
|
--------------
|
||||||
|
|
||||||
|
:Last Review: 2010-07-25
|
||||||
|
:Reviewed Version: 0.2
|
||||||
|
|
||||||
|
Would be great for enlisting but it should follow the API of Flask-Creole.
|
||||||
|
Besides that, the docstrings are not valid rst (run through rst2html to
|
||||||
|
see the issue) and it is missing tests. Otherwise fine :)
|
||||||
|
|
||||||
|
|
||||||
|
flask-urls
|
||||||
|
----------
|
||||||
|
|
||||||
|
:Last Review: 2010-07-25
|
||||||
|
:Reviewed Version: 0.9.2
|
||||||
|
|
||||||
|
Broken PyPI index and non-conforming extension name. Due to the small
|
||||||
|
featureset this was also delisted from the list. It was there previously
|
||||||
|
before the approval process was introduced.
|
||||||
34
flask/__init__.py
Normal file
34
flask/__init__.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
A microframework based on Werkzeug. It's extensively documented
|
||||||
|
and follows best practice patterns.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# utilities we import from Werkzeug and Jinja2 that are unused
|
||||||
|
# in the module but are exported as public interface.
|
||||||
|
from werkzeug import abort, redirect
|
||||||
|
from jinja2 import Markup, escape
|
||||||
|
|
||||||
|
from .app import Flask, Request, Response
|
||||||
|
from .config import Config
|
||||||
|
from .helpers import url_for, jsonify, json_available, flash, \
|
||||||
|
send_file, send_from_directory, get_flashed_messages, \
|
||||||
|
get_template_attribute, make_response
|
||||||
|
from .globals import current_app, g, request, session, _request_ctx_stack
|
||||||
|
from .module import Module
|
||||||
|
from .templating import render_template, render_template_string
|
||||||
|
from .session import Session
|
||||||
|
|
||||||
|
# the signals
|
||||||
|
from .signals import signals_available, template_rendered, request_started, \
|
||||||
|
request_finished, got_request_exception
|
||||||
|
|
||||||
|
# only import json if it's available
|
||||||
|
if json_available:
|
||||||
|
from .helpers import json
|
||||||
911
flask/app.py
Normal file
911
flask/app.py
Normal file
|
|
@ -0,0 +1,911 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.app
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
This module implements the central WSGI application object.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
from threading import Lock
|
||||||
|
from datetime import timedelta, datetime
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from jinja2 import Environment
|
||||||
|
|
||||||
|
from werkzeug import ImmutableDict
|
||||||
|
from werkzeug.routing import Map, Rule
|
||||||
|
from werkzeug.exceptions import HTTPException, InternalServerError, \
|
||||||
|
MethodNotAllowed
|
||||||
|
|
||||||
|
from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
|
||||||
|
_tojson_filter, _endpoint_from_view_func
|
||||||
|
from .wrappers import Request, Response
|
||||||
|
from .config import ConfigAttribute, Config
|
||||||
|
from .ctx import _RequestContext
|
||||||
|
from .globals import _request_ctx_stack, request
|
||||||
|
from .session import Session, _NullSession
|
||||||
|
from .module import _ModuleSetupState
|
||||||
|
from .templating import _DispatchingJinjaLoader, \
|
||||||
|
_default_template_ctx_processor
|
||||||
|
from .signals import request_started, request_finished, got_request_exception
|
||||||
|
|
||||||
|
# a lock used for logger initialization
|
||||||
|
_logger_lock = Lock()
|
||||||
|
|
||||||
|
|
||||||
|
class Flask(_PackageBoundObject):
|
||||||
|
"""The flask object implements a WSGI application and acts as the central
|
||||||
|
object. It is passed the name of the module or package of the
|
||||||
|
application. Once it is created it will act as a central registry for
|
||||||
|
the view functions, the URL rules, template configuration and much more.
|
||||||
|
|
||||||
|
The name of the package is used to resolve resources from inside the
|
||||||
|
package or the folder the module is contained in depending on if the
|
||||||
|
package parameter resolves to an actual python package (a folder with
|
||||||
|
an `__init__.py` file inside) or a standard module (just a `.py` file).
|
||||||
|
|
||||||
|
For more information about resource loading, see :func:`open_resource`.
|
||||||
|
|
||||||
|
Usually you create a :class:`Flask` instance in your main module or
|
||||||
|
in the `__init__.py` file of your package like this::
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
.. admonition:: About the First Parameter
|
||||||
|
|
||||||
|
The idea of the first parameter is to give Flask an idea what
|
||||||
|
belongs to your application. This name is used to find resources
|
||||||
|
on the file system, can be used by extensions to improve debugging
|
||||||
|
information and a lot more.
|
||||||
|
|
||||||
|
So it's important what you provide there. If you are using a single
|
||||||
|
module, `__name__` is always the correct value. If you however are
|
||||||
|
using a package, it's usually recommended to hardcode the name of
|
||||||
|
your package there.
|
||||||
|
|
||||||
|
For example if your application is defined in `yourapplication/app.py`
|
||||||
|
you should create it with one of the two versions below::
|
||||||
|
|
||||||
|
app = Flask('yourapplication')
|
||||||
|
app = Flask(__name__.split('.')[0])
|
||||||
|
|
||||||
|
Why is that? The application will work even with `__name__`, thanks
|
||||||
|
to how resources are looked up. However it will make debugging more
|
||||||
|
painful. Certain extensions can make assumptions based on the
|
||||||
|
import name of your application. For example the Flask-SQLAlchemy
|
||||||
|
extension will look for the code in your application that triggered
|
||||||
|
an SQL query in debug mode. If the import name is not properly set
|
||||||
|
up, that debugging information is lost. (For example it would only
|
||||||
|
pick up SQL queries in `yourapplicaiton.app` and not
|
||||||
|
`yourapplication.views.frontend`)
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
The `static_path` parameter was added.
|
||||||
|
|
||||||
|
:param import_name: the name of the application package
|
||||||
|
:param static_path: can be used to specify a different path for the
|
||||||
|
static files on the web. Defaults to ``/static``.
|
||||||
|
This does not affect the folder the files are served
|
||||||
|
*from*.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: The class that is used for request objects. See :class:`~flask.Request`
|
||||||
|
#: for more information.
|
||||||
|
request_class = Request
|
||||||
|
|
||||||
|
#: The class that is used for response objects. See
|
||||||
|
#: :class:`~flask.Response` for more information.
|
||||||
|
response_class = Response
|
||||||
|
|
||||||
|
#: Path for the static files. If you don't want to use static files
|
||||||
|
#: you can set this value to `None` in which case no URL rule is added
|
||||||
|
#: and the development server will no longer serve any static files.
|
||||||
|
#:
|
||||||
|
#: This is the default used for application and modules unless a
|
||||||
|
#: different value is passed to the constructor.
|
||||||
|
static_path = '/static'
|
||||||
|
|
||||||
|
#: The debug flag. Set this to `True` to enable debugging of the
|
||||||
|
#: application. In debug mode the debugger will kick in when an unhandled
|
||||||
|
#: exception ocurrs and the integrated server will automatically reload
|
||||||
|
#: the application if changes in the code are detected.
|
||||||
|
#:
|
||||||
|
#: This attribute can also be configured from the config with the `DEBUG`
|
||||||
|
#: configuration key. Defaults to `False`.
|
||||||
|
debug = ConfigAttribute('DEBUG')
|
||||||
|
|
||||||
|
#: The testing flask. Set this to `True` to enable the test mode of
|
||||||
|
#: Flask extensions (and in the future probably also Flask itself).
|
||||||
|
#: For example this might activate unittest helpers that have an
|
||||||
|
#: additional runtime cost which should not be enabled by default.
|
||||||
|
#:
|
||||||
|
#: This attribute can also be configured from the config with the
|
||||||
|
#: `TESTING` configuration key. Defaults to `False`.
|
||||||
|
testing = ConfigAttribute('TESTING')
|
||||||
|
|
||||||
|
#: If a secret key is set, cryptographic components can use this to
|
||||||
|
#: sign cookies and other things. Set this to a complex random value
|
||||||
|
#: when you want to use the secure cookie for instance.
|
||||||
|
#:
|
||||||
|
#: This attribute can also be configured from the config with the
|
||||||
|
#: `SECRET_KEY` configuration key. Defaults to `None`.
|
||||||
|
secret_key = ConfigAttribute('SECRET_KEY')
|
||||||
|
|
||||||
|
#: The secure cookie uses this for the name of the session cookie.
|
||||||
|
#:
|
||||||
|
#: This attribute can also be configured from the config with the
|
||||||
|
#: `SESSION_COOKIE_NAME` configuration key. Defaults to ``'session'``
|
||||||
|
session_cookie_name = ConfigAttribute('SESSION_COOKIE_NAME')
|
||||||
|
|
||||||
|
#: A :class:`~datetime.timedelta` which is used to set the expiration
|
||||||
|
#: date of a permanent session. The default is 31 days which makes a
|
||||||
|
#: permanent session survive for roughly one month.
|
||||||
|
#:
|
||||||
|
#: This attribute can also be configured from the config with the
|
||||||
|
#: `PERMANENT_SESSION_LIFETIME` configuration key. Defaults to
|
||||||
|
#: ``timedelta(days=31)``
|
||||||
|
permanent_session_lifetime = ConfigAttribute('PERMANENT_SESSION_LIFETIME')
|
||||||
|
|
||||||
|
#: Enable this if you want to use the X-Sendfile feature. Keep in
|
||||||
|
#: mind that the server has to support this. This only affects files
|
||||||
|
#: sent with the :func:`send_file` method.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.2
|
||||||
|
#:
|
||||||
|
#: This attribute can also be configured from the config with the
|
||||||
|
#: `USE_X_SENDFILE` configuration key. Defaults to `False`.
|
||||||
|
use_x_sendfile = ConfigAttribute('USE_X_SENDFILE')
|
||||||
|
|
||||||
|
#: The name of the logger to use. By default the logger name is the
|
||||||
|
#: package name passed to the constructor.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.4
|
||||||
|
logger_name = ConfigAttribute('LOGGER_NAME')
|
||||||
|
|
||||||
|
#: The logging format used for the debug logger. This is only used when
|
||||||
|
#: the application is in debug mode, otherwise the attached logging
|
||||||
|
#: handler does the formatting.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.3
|
||||||
|
debug_log_format = (
|
||||||
|
'-' * 80 + '\n' +
|
||||||
|
'%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n' +
|
||||||
|
'%(message)s\n' +
|
||||||
|
'-' * 80
|
||||||
|
)
|
||||||
|
|
||||||
|
#: Options that are passed directly to the Jinja2 environment.
|
||||||
|
jinja_options = ImmutableDict(
|
||||||
|
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
|
||||||
|
)
|
||||||
|
|
||||||
|
#: Default configuration parameters.
|
||||||
|
default_config = ImmutableDict({
|
||||||
|
'DEBUG': False,
|
||||||
|
'TESTING': False,
|
||||||
|
'SECRET_KEY': None,
|
||||||
|
'SESSION_COOKIE_NAME': 'session',
|
||||||
|
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
|
||||||
|
'USE_X_SENDFILE': False,
|
||||||
|
'LOGGER_NAME': None,
|
||||||
|
'SERVER_NAME': None,
|
||||||
|
'MAX_CONTENT_LENGTH': None
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, import_name, static_path=None):
|
||||||
|
_PackageBoundObject.__init__(self, import_name)
|
||||||
|
if static_path is not None:
|
||||||
|
self.static_path = static_path
|
||||||
|
|
||||||
|
#: The configuration dictionary as :class:`Config`. This behaves
|
||||||
|
#: exactly like a regular dictionary but supports additional methods
|
||||||
|
#: to load a config from files.
|
||||||
|
self.config = Config(self.root_path, self.default_config)
|
||||||
|
|
||||||
|
#: Prepare the deferred setup of the logger.
|
||||||
|
self._logger = None
|
||||||
|
self.logger_name = self.import_name
|
||||||
|
|
||||||
|
#: A dictionary of all view functions registered. The keys will
|
||||||
|
#: be function names which are also used to generate URLs and
|
||||||
|
#: the values are the function objects themselves.
|
||||||
|
#: To register a view function, use the :meth:`route` decorator.
|
||||||
|
self.view_functions = {}
|
||||||
|
|
||||||
|
#: A dictionary of all registered error handlers. The key is
|
||||||
|
#: be the error code as integer, the value the function that
|
||||||
|
#: should handle that error.
|
||||||
|
#: To register a error handler, use the :meth:`errorhandler`
|
||||||
|
#: decorator.
|
||||||
|
self.error_handlers = {}
|
||||||
|
|
||||||
|
#: A dictionary with lists of functions that should be called at the
|
||||||
|
#: beginning of the request. The key of the dictionary is the name of
|
||||||
|
#: the module this function is active for, `None` for all requests.
|
||||||
|
#: This can for example be used to open database connections or
|
||||||
|
#: getting hold of the currently logged in user. To register a
|
||||||
|
#: function here, use the :meth:`before_request` decorator.
|
||||||
|
self.before_request_funcs = {}
|
||||||
|
|
||||||
|
#: A dictionary with lists of functions that should be called after
|
||||||
|
#: each request. The key of the dictionary is the name of the module
|
||||||
|
#: this function is active for, `None` for all requests. This can for
|
||||||
|
#: example be used to open database connections or getting hold of the
|
||||||
|
#: currently logged in user. To register a function here, use the
|
||||||
|
#: :meth:`after_request` decorator.
|
||||||
|
self.after_request_funcs = {}
|
||||||
|
|
||||||
|
#: A dictionary with list of functions that are called without argument
|
||||||
|
#: to populate the template context. The key of the dictionary is the
|
||||||
|
#: name of the module this function is active for, `None` for all
|
||||||
|
#: requests. Each returns a dictionary that the template context is
|
||||||
|
#: updated with. To register a function here, use the
|
||||||
|
#: :meth:`context_processor` decorator.
|
||||||
|
self.template_context_processors = {
|
||||||
|
None: [_default_template_ctx_processor]
|
||||||
|
}
|
||||||
|
|
||||||
|
#: all the loaded modules in a dictionary by name.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.5
|
||||||
|
self.modules = {}
|
||||||
|
|
||||||
|
#: a place where extensions can store application specific state. For
|
||||||
|
#: example this is where an extension could store database engines and
|
||||||
|
#: similar things. For backwards compatibility extensions should register
|
||||||
|
#: themselves like this::
|
||||||
|
#:
|
||||||
|
#: if not hasattr(app, 'extensions'):
|
||||||
|
#: app.extensions = {}
|
||||||
|
#: app.extensions['extensionname'] = SomeObject()
|
||||||
|
#:
|
||||||
|
#: The key must match the name of the `flaskext` module. For example in
|
||||||
|
#: case of a "Flask-Foo" extension in `flaskext.foo`, the key would be
|
||||||
|
#: ``'foo'``.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.7
|
||||||
|
self.extensions = {}
|
||||||
|
|
||||||
|
#: The :class:`~werkzeug.routing.Map` for this instance. You can use
|
||||||
|
#: this to change the routing converters after the class was created
|
||||||
|
#: but before any routes are connected. Example::
|
||||||
|
#:
|
||||||
|
#: from werkzeug import BaseConverter
|
||||||
|
#:
|
||||||
|
#: class ListConverter(BaseConverter):
|
||||||
|
#: def to_python(self, value):
|
||||||
|
#: return value.split(',')
|
||||||
|
#: def to_url(self, values):
|
||||||
|
#: return ','.join(BaseConverter.to_url(value)
|
||||||
|
#: for value in values)
|
||||||
|
#:
|
||||||
|
#: app = Flask(__name__)
|
||||||
|
#: app.url_map.converters['list'] = ListConverter
|
||||||
|
self.url_map = Map()
|
||||||
|
|
||||||
|
# register the static folder for the application. Do that even
|
||||||
|
# if the folder does not exist. First of all it might be created
|
||||||
|
# while the server is running (usually happens during development)
|
||||||
|
# but also because google appengine stores static files somewhere
|
||||||
|
# else when mapped with the .yml file.
|
||||||
|
self.add_url_rule(self.static_path + '/<path:filename>',
|
||||||
|
endpoint='static',
|
||||||
|
view_func=self.send_static_file)
|
||||||
|
|
||||||
|
#: The Jinja2 environment. It is created from the
|
||||||
|
#: :attr:`jinja_options`.
|
||||||
|
self.jinja_env = self.create_jinja_environment()
|
||||||
|
self.init_jinja_globals()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logger(self):
|
||||||
|
"""A :class:`logging.Logger` object for this application. The
|
||||||
|
default configuration is to log to stderr if the application is
|
||||||
|
in debug mode. This logger can be used to (surprise) log messages.
|
||||||
|
Here some examples::
|
||||||
|
|
||||||
|
app.logger.debug('A value for debugging')
|
||||||
|
app.logger.warning('A warning ocurred (%d apples)', 42)
|
||||||
|
app.logger.error('An error occoured')
|
||||||
|
|
||||||
|
.. versionadded:: 0.3
|
||||||
|
"""
|
||||||
|
if self._logger and self._logger.name == self.logger_name:
|
||||||
|
return self._logger
|
||||||
|
with _logger_lock:
|
||||||
|
if self._logger and self._logger.name == self.logger_name:
|
||||||
|
return self._logger
|
||||||
|
from flask.logging import create_logger
|
||||||
|
self._logger = rv = create_logger(self)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def create_jinja_environment(self):
|
||||||
|
"""Creates the Jinja2 environment based on :attr:`jinja_options`
|
||||||
|
and :meth:`select_jinja_autoescape`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
"""
|
||||||
|
options = dict(self.jinja_options)
|
||||||
|
if 'autoescape' not in options:
|
||||||
|
options['autoescape'] = self.select_jinja_autoescape
|
||||||
|
return Environment(loader=_DispatchingJinjaLoader(self), **options)
|
||||||
|
|
||||||
|
def init_jinja_globals(self):
|
||||||
|
"""Called directly after the environment was created to inject
|
||||||
|
some defaults (like `url_for`, `get_flashed_messages` and the
|
||||||
|
`tojson` filter.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
"""
|
||||||
|
self.jinja_env.globals.update(
|
||||||
|
url_for=url_for,
|
||||||
|
get_flashed_messages=get_flashed_messages
|
||||||
|
)
|
||||||
|
self.jinja_env.filters['tojson'] = _tojson_filter
|
||||||
|
|
||||||
|
def select_jinja_autoescape(self, filename):
|
||||||
|
"""Returns `True` if autoescaping should be active for the given
|
||||||
|
template name.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
"""
|
||||||
|
if filename is None:
|
||||||
|
return False
|
||||||
|
return filename.endswith(('.html', '.htm', '.xml', '.xhtml'))
|
||||||
|
|
||||||
|
def update_template_context(self, context):
|
||||||
|
"""Update the template context with some commonly used variables.
|
||||||
|
This injects request, session, config and g into the template
|
||||||
|
context as well as everything template context processors want
|
||||||
|
to inject. Note that the as of Flask 0.6, the original values
|
||||||
|
in the context will not be overriden if a context processor
|
||||||
|
decides to return a value with the same key.
|
||||||
|
|
||||||
|
:param context: the context as a dictionary that is updated in place
|
||||||
|
to add extra variables.
|
||||||
|
"""
|
||||||
|
funcs = self.template_context_processors[None]
|
||||||
|
mod = _request_ctx_stack.top.request.module
|
||||||
|
if mod is not None and mod in self.template_context_processors:
|
||||||
|
funcs = chain(funcs, self.template_context_processors[mod])
|
||||||
|
orig_ctx = context.copy()
|
||||||
|
for func in funcs:
|
||||||
|
context.update(func())
|
||||||
|
# make sure the original values win. This makes it possible to
|
||||||
|
# easier add new variables in context processors without breaking
|
||||||
|
# existing views.
|
||||||
|
context.update(orig_ctx)
|
||||||
|
|
||||||
|
def run(self, host='127.0.0.1', port=5000, **options):
|
||||||
|
"""Runs the application on a local development server. If the
|
||||||
|
:attr:`debug` flag is set the server will automatically reload
|
||||||
|
for code changes and show a debugger in case an exception happened.
|
||||||
|
|
||||||
|
If you want to run the application in debug mode, but disable the
|
||||||
|
code execution on the interactive debugger, you can pass
|
||||||
|
``use_evalex=False`` as parameter. This will keep the debugger's
|
||||||
|
traceback screen active, but disable code execution.
|
||||||
|
|
||||||
|
.. admonition:: Keep in Mind
|
||||||
|
|
||||||
|
Flask will suppress any server error with a generic error page
|
||||||
|
unless it is in debug mode. As such to enable just the
|
||||||
|
interactive debugger without the code reloading, you have to
|
||||||
|
invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``.
|
||||||
|
Setting ``use_debugger`` to `True` without being in debug mode
|
||||||
|
won't catch any exceptions because there won't be any to
|
||||||
|
catch.
|
||||||
|
|
||||||
|
:param host: the hostname to listen on. set this to ``'0.0.0.0'``
|
||||||
|
to have the server available externally as well.
|
||||||
|
:param port: the port of the webserver
|
||||||
|
:param options: the options to be forwarded to the underlying
|
||||||
|
Werkzeug server. See :func:`werkzeug.run_simple`
|
||||||
|
for more information.
|
||||||
|
"""
|
||||||
|
from werkzeug import run_simple
|
||||||
|
if 'debug' in options:
|
||||||
|
self.debug = options.pop('debug')
|
||||||
|
options.setdefault('use_reloader', self.debug)
|
||||||
|
options.setdefault('use_debugger', self.debug)
|
||||||
|
return run_simple(host, port, self, **options)
|
||||||
|
|
||||||
|
def test_client(self):
|
||||||
|
"""Creates a test client for this application. For information
|
||||||
|
about unit testing head over to :ref:`testing`.
|
||||||
|
|
||||||
|
The test client can be used in a `with` block to defer the closing down
|
||||||
|
of the context until the end of the `with` block. This is useful if
|
||||||
|
you want to access the context locals for testing::
|
||||||
|
|
||||||
|
with app.test_client() as c:
|
||||||
|
rv = c.get('/?vodka=42')
|
||||||
|
assert request.args['vodka'] == '42'
|
||||||
|
|
||||||
|
.. versionchanged:: 0.4
|
||||||
|
added support for `with` block usage for the client.
|
||||||
|
"""
|
||||||
|
from flask.testing import FlaskClient
|
||||||
|
return FlaskClient(self, self.response_class, use_cookies=True)
|
||||||
|
|
||||||
|
def open_session(self, request):
|
||||||
|
"""Creates or opens a new session. Default implementation stores all
|
||||||
|
session data in a signed cookie. This requires that the
|
||||||
|
:attr:`secret_key` is set.
|
||||||
|
|
||||||
|
:param request: an instance of :attr:`request_class`.
|
||||||
|
"""
|
||||||
|
key = self.secret_key
|
||||||
|
if key is not None:
|
||||||
|
return Session.load_cookie(request, self.session_cookie_name,
|
||||||
|
secret_key=key)
|
||||||
|
|
||||||
|
def save_session(self, session, response):
|
||||||
|
"""Saves the session if it needs updates. For the default
|
||||||
|
implementation, check :meth:`open_session`.
|
||||||
|
|
||||||
|
:param session: the session to be saved (a
|
||||||
|
:class:`~werkzeug.contrib.securecookie.SecureCookie`
|
||||||
|
object)
|
||||||
|
:param response: an instance of :attr:`response_class`
|
||||||
|
"""
|
||||||
|
expires = domain = None
|
||||||
|
if session.permanent:
|
||||||
|
expires = datetime.utcnow() + self.permanent_session_lifetime
|
||||||
|
if self.config['SERVER_NAME'] is not None:
|
||||||
|
domain = '.' + self.config['SERVER_NAME']
|
||||||
|
session.save_cookie(response, self.session_cookie_name,
|
||||||
|
expires=expires, httponly=True, domain=domain)
|
||||||
|
|
||||||
|
def register_module(self, module, **options):
|
||||||
|
"""Registers a module with this application. The keyword argument
|
||||||
|
of this function are the same as the ones for the constructor of the
|
||||||
|
:class:`Module` class and will override the values of the module if
|
||||||
|
provided.
|
||||||
|
"""
|
||||||
|
options.setdefault('url_prefix', module.url_prefix)
|
||||||
|
options.setdefault('subdomain', module.subdomain)
|
||||||
|
state = _ModuleSetupState(self, **options)
|
||||||
|
for func in module._register_events:
|
||||||
|
func(state)
|
||||||
|
|
||||||
|
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||||
|
"""Connects a URL rule. Works exactly like the :meth:`route`
|
||||||
|
decorator. If a view_func is provided it will be registered with the
|
||||||
|
endpoint.
|
||||||
|
|
||||||
|
Basically this example::
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
pass
|
||||||
|
|
||||||
|
Is equivalent to the following::
|
||||||
|
|
||||||
|
def index():
|
||||||
|
pass
|
||||||
|
app.add_url_rule('/', 'index', index)
|
||||||
|
|
||||||
|
If the view_func is not provided you will need to connect the endpoint
|
||||||
|
to a view function like so::
|
||||||
|
|
||||||
|
app.view_functions['index'] = index
|
||||||
|
|
||||||
|
.. versionchanged:: 0.2
|
||||||
|
`view_func` parameter added.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.6
|
||||||
|
`OPTIONS` is added automatically as method.
|
||||||
|
|
||||||
|
:param rule: the URL rule as string
|
||||||
|
:param endpoint: the endpoint for the registered URL rule. Flask
|
||||||
|
itself assumes the name of the view function as
|
||||||
|
endpoint
|
||||||
|
:param view_func: the function to call when serving a request to the
|
||||||
|
provided endpoint
|
||||||
|
:param options: the options to be forwarded to the underlying
|
||||||
|
:class:`~werkzeug.routing.Rule` object. A change
|
||||||
|
to Werkzeug is handling of method options. methods
|
||||||
|
is a list of methods this rule should be limited
|
||||||
|
to (`GET`, `POST` etc.). By default a rule
|
||||||
|
just listens for `GET` (and implicitly `HEAD`).
|
||||||
|
Starting with Flask 0.6, `OPTIONS` is implicitly
|
||||||
|
added and handled by the standard request handling.
|
||||||
|
"""
|
||||||
|
if endpoint is None:
|
||||||
|
endpoint = _endpoint_from_view_func(view_func)
|
||||||
|
options['endpoint'] = endpoint
|
||||||
|
methods = options.pop('methods', ('GET',))
|
||||||
|
provide_automatic_options = False
|
||||||
|
if 'OPTIONS' not in methods:
|
||||||
|
methods = tuple(methods) + ('OPTIONS',)
|
||||||
|
provide_automatic_options = True
|
||||||
|
rule = Rule(rule, methods=methods, **options)
|
||||||
|
rule.provide_automatic_options = provide_automatic_options
|
||||||
|
self.url_map.add(rule)
|
||||||
|
if view_func is not None:
|
||||||
|
self.view_functions[endpoint] = view_func
|
||||||
|
|
||||||
|
def route(self, rule, **options):
|
||||||
|
"""A decorator that is used to register a view function for a
|
||||||
|
given URL rule. Example::
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return 'Hello World'
|
||||||
|
|
||||||
|
Variables parts in the route can be specified with angular
|
||||||
|
brackets (``/user/<username>``). By default a variable part
|
||||||
|
in the URL accepts any string without a slash however a different
|
||||||
|
converter can be specified as well by using ``<converter:name>``.
|
||||||
|
|
||||||
|
Variable parts are passed to the view function as keyword
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
The following converters are possible:
|
||||||
|
|
||||||
|
=========== ===========================================
|
||||||
|
`int` accepts integers
|
||||||
|
`float` like `int` but for floating point values
|
||||||
|
`path` like the default but also accepts slashes
|
||||||
|
=========== ===========================================
|
||||||
|
|
||||||
|
Here some examples::
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@app.route('/<username>')
|
||||||
|
def show_user(username):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@app.route('/post/<int:post_id>')
|
||||||
|
def show_post(post_id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
An important detail to keep in mind is how Flask deals with trailing
|
||||||
|
slashes. The idea is to keep each URL unique so the following rules
|
||||||
|
apply:
|
||||||
|
|
||||||
|
1. If a rule ends with a slash and is requested without a slash
|
||||||
|
by the user, the user is automatically redirected to the same
|
||||||
|
page with a trailing slash attached.
|
||||||
|
2. If a rule does not end with a trailing slash and the user request
|
||||||
|
the page with a trailing slash, a 404 not found is raised.
|
||||||
|
|
||||||
|
This is consistent with how web servers deal with static files. This
|
||||||
|
also makes it possible to use relative link targets safely.
|
||||||
|
|
||||||
|
The :meth:`route` decorator accepts a couple of other arguments
|
||||||
|
as well:
|
||||||
|
|
||||||
|
:param rule: the URL rule as string
|
||||||
|
:param methods: a list of methods this rule should be limited
|
||||||
|
to (`GET`, `POST` etc.). By default a rule
|
||||||
|
just listens for `GET` (and implicitly `HEAD`).
|
||||||
|
Starting with Flask 0.6, `OPTIONS` is implicitly
|
||||||
|
added and handled by the standard request handling.
|
||||||
|
:param subdomain: specifies the rule for the subdomain in case
|
||||||
|
subdomain matching is in use.
|
||||||
|
:param strict_slashes: can be used to disable the strict slashes
|
||||||
|
setting for this rule. See above.
|
||||||
|
:param options: other options to be forwarded to the underlying
|
||||||
|
:class:`~werkzeug.routing.Rule` object.
|
||||||
|
"""
|
||||||
|
def decorator(f):
|
||||||
|
self.add_url_rule(rule, None, f, **options)
|
||||||
|
return f
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def errorhandler(self, code):
|
||||||
|
"""A decorator that is used to register a function give a given
|
||||||
|
error code. Example::
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(error):
|
||||||
|
return 'This page does not exist', 404
|
||||||
|
|
||||||
|
You can also register a function as error handler without using
|
||||||
|
the :meth:`errorhandler` decorator. The following example is
|
||||||
|
equivalent to the one above::
|
||||||
|
|
||||||
|
def page_not_found(error):
|
||||||
|
return 'This page does not exist', 404
|
||||||
|
app.error_handlers[404] = page_not_found
|
||||||
|
|
||||||
|
:param code: the code as integer for the handler
|
||||||
|
"""
|
||||||
|
def decorator(f):
|
||||||
|
self.error_handlers[code] = f
|
||||||
|
return f
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def template_filter(self, name=None):
|
||||||
|
"""A decorator that is used to register custom template filter.
|
||||||
|
You can specify a name for the filter, otherwise the function
|
||||||
|
name will be used. Example::
|
||||||
|
|
||||||
|
@app.template_filter()
|
||||||
|
def reverse(s):
|
||||||
|
return s[::-1]
|
||||||
|
|
||||||
|
:param name: the optional name of the filter, otherwise the
|
||||||
|
function name will be used.
|
||||||
|
"""
|
||||||
|
def decorator(f):
|
||||||
|
self.jinja_env.filters[name or f.__name__] = f
|
||||||
|
return f
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def before_request(self, f):
|
||||||
|
"""Registers a function to run before each request."""
|
||||||
|
self.before_request_funcs.setdefault(None, []).append(f)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def after_request(self, f):
|
||||||
|
"""Register a function to be run after each request."""
|
||||||
|
self.after_request_funcs.setdefault(None, []).append(f)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def context_processor(self, f):
|
||||||
|
"""Registers a template context processor function."""
|
||||||
|
self.template_context_processors[None].append(f)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def handle_http_exception(self, e):
|
||||||
|
"""Handles an HTTP exception. By default this will invoke the
|
||||||
|
registered error handlers and fall back to returning the
|
||||||
|
exception as response.
|
||||||
|
|
||||||
|
.. versionadded: 0.3
|
||||||
|
"""
|
||||||
|
handler = self.error_handlers.get(e.code)
|
||||||
|
if handler is None:
|
||||||
|
return e
|
||||||
|
return handler(e)
|
||||||
|
|
||||||
|
def handle_exception(self, e):
|
||||||
|
"""Default exception handling that kicks in when an exception
|
||||||
|
occours that is not catched. In debug mode the exception will
|
||||||
|
be re-raised immediately, otherwise it is logged and the handler
|
||||||
|
for a 500 internal server error is used. If no such handler
|
||||||
|
exists, a default 500 internal server error message is displayed.
|
||||||
|
|
||||||
|
.. versionadded: 0.3
|
||||||
|
"""
|
||||||
|
got_request_exception.send(self, exception=e)
|
||||||
|
handler = self.error_handlers.get(500)
|
||||||
|
if self.debug:
|
||||||
|
raise
|
||||||
|
self.logger.exception('Exception on %s [%s]' % (
|
||||||
|
request.path,
|
||||||
|
request.method
|
||||||
|
))
|
||||||
|
if handler is None:
|
||||||
|
return InternalServerError()
|
||||||
|
return handler(e)
|
||||||
|
|
||||||
|
def dispatch_request(self):
|
||||||
|
"""Does the request dispatching. Matches the URL and returns the
|
||||||
|
return value of the view or error handler. This does not have to
|
||||||
|
be a response object. In order to convert the return value to a
|
||||||
|
proper response object, call :func:`make_response`.
|
||||||
|
"""
|
||||||
|
req = _request_ctx_stack.top.request
|
||||||
|
try:
|
||||||
|
if req.routing_exception is not None:
|
||||||
|
raise req.routing_exception
|
||||||
|
rule = req.url_rule
|
||||||
|
# if we provide automatic options for this URL and the
|
||||||
|
# request came with the OPTIONS method, reply automatically
|
||||||
|
if rule.provide_automatic_options and req.method == 'OPTIONS':
|
||||||
|
return self.make_default_options_response()
|
||||||
|
# otherwise dispatch to the handler for that endpoint
|
||||||
|
return self.view_functions[rule.endpoint](**req.view_args)
|
||||||
|
except HTTPException, e:
|
||||||
|
return self.handle_http_exception(e)
|
||||||
|
|
||||||
|
def make_default_options_response(self):
|
||||||
|
"""This method is called to create the default `OPTIONS` response.
|
||||||
|
This can be changed through subclassing to change the default
|
||||||
|
behaviour of `OPTIONS` responses.
|
||||||
|
|
||||||
|
.. versionadded:: 0.7
|
||||||
|
"""
|
||||||
|
# This would be nicer in Werkzeug 0.7, which however currently
|
||||||
|
# is not released. Werkzeug 0.7 provides a method called
|
||||||
|
# allowed_methods() that returns all methods that are valid for
|
||||||
|
# a given path.
|
||||||
|
methods = []
|
||||||
|
try:
|
||||||
|
_request_ctx_stack.top.url_adapter.match(method='--')
|
||||||
|
except MethodNotAllowed, e:
|
||||||
|
methods = e.valid_methods
|
||||||
|
except HTTPException, e:
|
||||||
|
pass
|
||||||
|
rv = self.response_class()
|
||||||
|
rv.allow.update(methods)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def make_response(self, rv):
|
||||||
|
"""Converts the return value from a view function to a real
|
||||||
|
response object that is an instance of :attr:`response_class`.
|
||||||
|
|
||||||
|
The following types are allowed for `rv`:
|
||||||
|
|
||||||
|
.. tabularcolumns:: |p{3.5cm}|p{9.5cm}|
|
||||||
|
|
||||||
|
======================= ===========================================
|
||||||
|
:attr:`response_class` the object is returned unchanged
|
||||||
|
:class:`str` a response object is created with the
|
||||||
|
string as body
|
||||||
|
:class:`unicode` a response object is created with the
|
||||||
|
string encoded to utf-8 as body
|
||||||
|
:class:`tuple` the response object is created with the
|
||||||
|
contents of the tuple as arguments
|
||||||
|
a WSGI function the function is called as WSGI application
|
||||||
|
and buffered as response object
|
||||||
|
======================= ===========================================
|
||||||
|
|
||||||
|
:param rv: the return value from the view function
|
||||||
|
"""
|
||||||
|
if rv is None:
|
||||||
|
raise ValueError('View function did not return a response')
|
||||||
|
if isinstance(rv, self.response_class):
|
||||||
|
return rv
|
||||||
|
if isinstance(rv, basestring):
|
||||||
|
return self.response_class(rv)
|
||||||
|
if isinstance(rv, tuple):
|
||||||
|
return self.response_class(*rv)
|
||||||
|
return self.response_class.force_type(rv, request.environ)
|
||||||
|
|
||||||
|
def create_url_adapter(self, request):
|
||||||
|
"""Creates a URL adapter for the given request. The URL adapter
|
||||||
|
is created at a point where the request context is not yet set up
|
||||||
|
so the request is passed explicitly.
|
||||||
|
|
||||||
|
.. versionadded:: 0.6
|
||||||
|
"""
|
||||||
|
return self.url_map.bind_to_environ(request.environ,
|
||||||
|
server_name=self.config['SERVER_NAME'])
|
||||||
|
|
||||||
|
def preprocess_request(self):
|
||||||
|
"""Called before the actual request dispatching and will
|
||||||
|
call every as :meth:`before_request` decorated function.
|
||||||
|
If any of these function returns a value it's handled as
|
||||||
|
if it was the return value from the view and further
|
||||||
|
request handling is stopped.
|
||||||
|
"""
|
||||||
|
funcs = self.before_request_funcs.get(None, ())
|
||||||
|
mod = request.module
|
||||||
|
if mod and mod in self.before_request_funcs:
|
||||||
|
funcs = chain(funcs, self.before_request_funcs[mod])
|
||||||
|
for func in funcs:
|
||||||
|
rv = func()
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def process_response(self, response):
|
||||||
|
"""Can be overridden in order to modify the response object
|
||||||
|
before it's sent to the WSGI server. By default this will
|
||||||
|
call all the :meth:`after_request` decorated functions.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.5
|
||||||
|
As of Flask 0.5 the functions registered for after request
|
||||||
|
execution are called in reverse order of registration.
|
||||||
|
|
||||||
|
:param response: a :attr:`response_class` object.
|
||||||
|
:return: a new response object or the same, has to be an
|
||||||
|
instance of :attr:`response_class`.
|
||||||
|
"""
|
||||||
|
ctx = _request_ctx_stack.top
|
||||||
|
mod = ctx.request.module
|
||||||
|
if not isinstance(ctx.session, _NullSession):
|
||||||
|
self.save_session(ctx.session, response)
|
||||||
|
funcs = ()
|
||||||
|
if mod and mod in self.after_request_funcs:
|
||||||
|
funcs = reversed(self.after_request_funcs[mod])
|
||||||
|
if None in self.after_request_funcs:
|
||||||
|
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
|
||||||
|
for handler in funcs:
|
||||||
|
response = handler(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def request_context(self, environ):
|
||||||
|
"""Creates a request context from the given environment and binds
|
||||||
|
it to the current context. This must be used in combination with
|
||||||
|
the `with` statement because the request is only bound to the
|
||||||
|
current context for the duration of the `with` block.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
with app.request_context(environ):
|
||||||
|
do_something_with(request)
|
||||||
|
|
||||||
|
The object returned can also be used without the `with` statement
|
||||||
|
which is useful for working in the shell. The example above is
|
||||||
|
doing exactly the same as this code::
|
||||||
|
|
||||||
|
ctx = app.request_context(environ)
|
||||||
|
ctx.push()
|
||||||
|
try:
|
||||||
|
do_something_with(request)
|
||||||
|
finally:
|
||||||
|
ctx.pop()
|
||||||
|
|
||||||
|
The big advantage of this approach is that you can use it without
|
||||||
|
the try/finally statement in a shell for interactive testing:
|
||||||
|
|
||||||
|
>>> ctx = app.test_request_context()
|
||||||
|
>>> ctx.bind()
|
||||||
|
>>> request.path
|
||||||
|
u'/'
|
||||||
|
>>> ctx.unbind()
|
||||||
|
|
||||||
|
.. versionchanged:: 0.3
|
||||||
|
Added support for non-with statement usage and `with` statement
|
||||||
|
is now passed the ctx object.
|
||||||
|
|
||||||
|
:param environ: a WSGI environment
|
||||||
|
"""
|
||||||
|
return _RequestContext(self, environ)
|
||||||
|
|
||||||
|
def test_request_context(self, *args, **kwargs):
|
||||||
|
"""Creates a WSGI environment from the given values (see
|
||||||
|
:func:`werkzeug.create_environ` for more information, this
|
||||||
|
function accepts the same arguments).
|
||||||
|
"""
|
||||||
|
from werkzeug import create_environ
|
||||||
|
return self.request_context(create_environ(*args, **kwargs))
|
||||||
|
|
||||||
|
def wsgi_app(self, environ, start_response):
|
||||||
|
"""The actual WSGI application. This is not implemented in
|
||||||
|
`__call__` so that middlewares can be applied without losing a
|
||||||
|
reference to the class. So instead of doing this::
|
||||||
|
|
||||||
|
app = MyMiddleware(app)
|
||||||
|
|
||||||
|
It's a better idea to do this instead::
|
||||||
|
|
||||||
|
app.wsgi_app = MyMiddleware(app.wsgi_app)
|
||||||
|
|
||||||
|
Then you still have the original application object around and
|
||||||
|
can continue to call methods on it.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.4
|
||||||
|
The :meth:`after_request` functions are now called even if an
|
||||||
|
error handler took over request processing. This ensures that
|
||||||
|
even if an exception happens database have the chance to
|
||||||
|
properly close the connection.
|
||||||
|
|
||||||
|
:param environ: a WSGI environment
|
||||||
|
:param start_response: a callable accepting a status code,
|
||||||
|
a list of headers and an optional
|
||||||
|
exception context to start the response
|
||||||
|
"""
|
||||||
|
with self.request_context(environ):
|
||||||
|
try:
|
||||||
|
request_started.send(self)
|
||||||
|
rv = self.preprocess_request()
|
||||||
|
if rv is None:
|
||||||
|
rv = self.dispatch_request()
|
||||||
|
response = self.make_response(rv)
|
||||||
|
except Exception, e:
|
||||||
|
response = self.make_response(self.handle_exception(e))
|
||||||
|
try:
|
||||||
|
response = self.process_response(response)
|
||||||
|
except Exception, e:
|
||||||
|
response = self.make_response(self.handle_exception(e))
|
||||||
|
request_finished.send(self, response=response)
|
||||||
|
return response(environ, start_response)
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
"""Shortcut for :attr:`wsgi_app`."""
|
||||||
|
return self.wsgi_app(environ, start_response)
|
||||||
156
flask/config.py
Normal file
156
flask/config.py
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.config
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the configuration related objects.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from werkzeug import import_string
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigAttribute(object):
|
||||||
|
"""Makes an attribute forward to the config"""
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.__name__ = name
|
||||||
|
|
||||||
|
def __get__(self, obj, type=None):
|
||||||
|
if obj is None:
|
||||||
|
return self
|
||||||
|
return obj.config[self.__name__]
|
||||||
|
|
||||||
|
def __set__(self, obj, value):
|
||||||
|
obj.config[self.__name__] = value
|
||||||
|
|
||||||
|
|
||||||
|
class Config(dict):
|
||||||
|
"""Works exactly like a dict but provides ways to fill it from files
|
||||||
|
or special dictionaries. There are two common patterns to populate the
|
||||||
|
config.
|
||||||
|
|
||||||
|
Either you can fill the config from a config file::
|
||||||
|
|
||||||
|
app.config.from_pyfile('yourconfig.cfg')
|
||||||
|
|
||||||
|
Or alternatively you can define the configuration options in the
|
||||||
|
module that calls :meth:`from_object` or provide an import path to
|
||||||
|
a module that should be loaded. It is also possible to tell it to
|
||||||
|
use the same module and with that provide the configuration values
|
||||||
|
just before the call::
|
||||||
|
|
||||||
|
DEBUG = True
|
||||||
|
SECRET_KEY = 'development key'
|
||||||
|
app.config.from_object(__name__)
|
||||||
|
|
||||||
|
In both cases (loading from any Python file or loading from modules),
|
||||||
|
only uppercase keys are added to the config. This makes it possible to use
|
||||||
|
lowercase values in the config file for temporary values that are not added
|
||||||
|
to the config or to define the config keys in the same file that implements
|
||||||
|
the application.
|
||||||
|
|
||||||
|
Probably the most interesting way to load configurations is from an
|
||||||
|
environment variable pointing to a file::
|
||||||
|
|
||||||
|
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
||||||
|
|
||||||
|
In this case before launching the application you have to set this
|
||||||
|
environment variable to the file you want to use. On Linux and OS X
|
||||||
|
use the export statement::
|
||||||
|
|
||||||
|
export YOURAPPLICATION_SETTINGS='/path/to/config/file'
|
||||||
|
|
||||||
|
On windows use `set` instead.
|
||||||
|
|
||||||
|
:param root_path: path to which files are read relative from. When the
|
||||||
|
config object is created by the application, this is
|
||||||
|
the application's :attr:`~flask.Flask.root_path`.
|
||||||
|
:param defaults: an optional dictionary of default values
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, root_path, defaults=None):
|
||||||
|
dict.__init__(self, defaults or {})
|
||||||
|
self.root_path = root_path
|
||||||
|
|
||||||
|
def from_envvar(self, variable_name, silent=False):
|
||||||
|
"""Loads a configuration from an environment variable pointing to
|
||||||
|
a configuration file. This basically is just a shortcut with nicer
|
||||||
|
error messages for this line of code::
|
||||||
|
|
||||||
|
app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
|
||||||
|
|
||||||
|
:param variable_name: name of the environment variable
|
||||||
|
:param silent: set to `True` if you want silent failing for missing
|
||||||
|
files.
|
||||||
|
:return: bool. `True` if able to load config, `False` otherwise.
|
||||||
|
"""
|
||||||
|
rv = os.environ.get(variable_name)
|
||||||
|
if not rv:
|
||||||
|
if silent:
|
||||||
|
return False
|
||||||
|
raise RuntimeError('The environment variable %r is not set '
|
||||||
|
'and as such configuration could not be '
|
||||||
|
'loaded. Set this variable and make it '
|
||||||
|
'point to a configuration file' %
|
||||||
|
variable_name)
|
||||||
|
self.from_pyfile(rv)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def from_pyfile(self, filename):
|
||||||
|
"""Updates the values in the config from a Python file. This function
|
||||||
|
behaves as if the file was imported as module with the
|
||||||
|
:meth:`from_object` function.
|
||||||
|
|
||||||
|
:param filename: the filename of the config. This can either be an
|
||||||
|
absolute filename or a filename relative to the
|
||||||
|
root path.
|
||||||
|
"""
|
||||||
|
filename = os.path.join(self.root_path, filename)
|
||||||
|
d = type(sys)('config')
|
||||||
|
d.__file__ = filename
|
||||||
|
try:
|
||||||
|
execfile(filename, d.__dict__)
|
||||||
|
except IOError, e:
|
||||||
|
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
|
||||||
|
raise
|
||||||
|
self.from_object(d)
|
||||||
|
|
||||||
|
def from_object(self, obj):
|
||||||
|
"""Updates the values from the given object. An object can be of one
|
||||||
|
of the following two types:
|
||||||
|
|
||||||
|
- a string: in this case the object with that name will be imported
|
||||||
|
- an actual object reference: that object is used directly
|
||||||
|
|
||||||
|
Objects are usually either modules or classes.
|
||||||
|
|
||||||
|
Just the uppercase variables in that object are stored in the config
|
||||||
|
after lowercasing. Example usage::
|
||||||
|
|
||||||
|
app.config.from_object('yourapplication.default_config')
|
||||||
|
from yourapplication import default_config
|
||||||
|
app.config.from_object(default_config)
|
||||||
|
|
||||||
|
You should not use this function to load the actual configuration but
|
||||||
|
rather configuration defaults. The actual config should be loaded
|
||||||
|
with :meth:`from_pyfile` and ideally from a location not within the
|
||||||
|
package because the package might be installed system wide.
|
||||||
|
|
||||||
|
:param obj: an import name or object
|
||||||
|
"""
|
||||||
|
if isinstance(obj, basestring):
|
||||||
|
obj = import_string(obj)
|
||||||
|
for key in dir(obj):
|
||||||
|
if key.isupper():
|
||||||
|
self[key] = getattr(obj, key)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
|
||||||
66
flask/ctx.py
Normal file
66
flask/ctx.py
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.ctx
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the objects required to keep the context.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
|
from .globals import _request_ctx_stack
|
||||||
|
from .session import _NullSession
|
||||||
|
|
||||||
|
|
||||||
|
class _RequestGlobals(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class _RequestContext(object):
|
||||||
|
"""The request context contains all request relevant information. It is
|
||||||
|
created at the beginning of the request and pushed to the
|
||||||
|
`_request_ctx_stack` and removed at the end of it. It will create the
|
||||||
|
URL adapter and request object for the WSGI environment provided.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app, environ):
|
||||||
|
self.app = app
|
||||||
|
self.request = app.request_class(environ)
|
||||||
|
self.url_adapter = app.create_url_adapter(self.request)
|
||||||
|
self.session = app.open_session(self.request)
|
||||||
|
if self.session is None:
|
||||||
|
self.session = _NullSession()
|
||||||
|
self.g = _RequestGlobals()
|
||||||
|
self.flashes = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
url_rule, self.request.view_args = \
|
||||||
|
self.url_adapter.match(return_rule=True)
|
||||||
|
self.request.url_rule = url_rule
|
||||||
|
except HTTPException, e:
|
||||||
|
self.request.routing_exception = e
|
||||||
|
|
||||||
|
def push(self):
|
||||||
|
"""Binds the request context."""
|
||||||
|
_request_ctx_stack.push(self)
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
"""Pops the request context."""
|
||||||
|
_request_ctx_stack.pop()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.push()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
|
# do not pop the request stack if we are in debug mode and an
|
||||||
|
# exception happened. This will allow the debugger to still
|
||||||
|
# access the request object in the interactive shell. Furthermore
|
||||||
|
# the context can be force kept alive for the test client.
|
||||||
|
# See flask.testing for how this works.
|
||||||
|
if not self.request.environ.get('flask._preserve_context') and \
|
||||||
|
(tb is None or not self.app.debug):
|
||||||
|
self.pop()
|
||||||
27
flask/globals.py
Normal file
27
flask/globals.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.globals
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Defines all the global objects that are proxies to the current
|
||||||
|
active context.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
from werkzeug import LocalStack, LocalProxy
|
||||||
|
|
||||||
|
def _lookup_object(name):
|
||||||
|
top = _request_ctx_stack.top
|
||||||
|
if top is None:
|
||||||
|
raise RuntimeError('working outside of request context')
|
||||||
|
return getattr(top, name)
|
||||||
|
|
||||||
|
# context locals
|
||||||
|
_request_ctx_stack = LocalStack()
|
||||||
|
current_app = LocalProxy(partial(_lookup_object, 'app'))
|
||||||
|
request = LocalProxy(partial(_lookup_object, 'request'))
|
||||||
|
session = LocalProxy(partial(_lookup_object, 'session'))
|
||||||
|
g = LocalProxy(partial(_lookup_object, 'g'))
|
||||||
488
flask/helpers.py
Normal file
488
flask/helpers.py
Normal file
|
|
@ -0,0 +1,488 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.helpers
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements various helpers.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import posixpath
|
||||||
|
import mimetypes
|
||||||
|
from time import time
|
||||||
|
from zlib import adler32
|
||||||
|
|
||||||
|
# try to load the best simplejson implementation available. If JSON
|
||||||
|
# is not installed, we add a failing class.
|
||||||
|
json_available = True
|
||||||
|
json = None
|
||||||
|
try:
|
||||||
|
import simplejson as json
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
# Google Appengine offers simplejson via django
|
||||||
|
from django.utils import simplejson as json
|
||||||
|
except ImportError:
|
||||||
|
json_available = False
|
||||||
|
|
||||||
|
|
||||||
|
from werkzeug import Headers, wrap_file, cached_property
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
from jinja2 import FileSystemLoader
|
||||||
|
|
||||||
|
from .globals import session, _request_ctx_stack, current_app, request
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_have_json():
|
||||||
|
"""Helper function that fails if JSON is unavailable."""
|
||||||
|
if not json_available:
|
||||||
|
raise RuntimeError('simplejson not installed')
|
||||||
|
|
||||||
|
# figure out if simplejson escapes slashes. This behaviour was changed
|
||||||
|
# from one version to another without reason.
|
||||||
|
if not json_available or '\\/' not in json.dumps('/'):
|
||||||
|
|
||||||
|
def _tojson_filter(*args, **kwargs):
|
||||||
|
if __debug__:
|
||||||
|
_assert_have_json()
|
||||||
|
return json.dumps(*args, **kwargs).replace('/', '\\/')
|
||||||
|
else:
|
||||||
|
_tojson_filter = json.dumps
|
||||||
|
|
||||||
|
|
||||||
|
def _endpoint_from_view_func(view_func):
|
||||||
|
"""Internal helper that returns the default endpoint for a given
|
||||||
|
function. This always is the function name.
|
||||||
|
"""
|
||||||
|
assert view_func is not None, 'expected view func if endpoint ' \
|
||||||
|
'is not provided.'
|
||||||
|
return view_func.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def jsonify(*args, **kwargs):
|
||||||
|
"""Creates a :class:`~flask.Response` with the JSON representation of
|
||||||
|
the given arguments with an `application/json` mimetype. The arguments
|
||||||
|
to this function are the same as to the :class:`dict` constructor.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
@app.route('/_get_current_user')
|
||||||
|
def get_current_user():
|
||||||
|
return jsonify(username=g.user.username,
|
||||||
|
email=g.user.email,
|
||||||
|
id=g.user.id)
|
||||||
|
|
||||||
|
This will send a JSON response like this to the browser::
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"email": "admin@localhost",
|
||||||
|
"id": 42
|
||||||
|
}
|
||||||
|
|
||||||
|
This requires Python 2.6 or an installed version of simplejson. For
|
||||||
|
security reasons only objects are supported toplevel. For more
|
||||||
|
information about this, have a look at :ref:`json-security`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.2
|
||||||
|
"""
|
||||||
|
if __debug__:
|
||||||
|
_assert_have_json()
|
||||||
|
return current_app.response_class(json.dumps(dict(*args, **kwargs),
|
||||||
|
indent=None if request.is_xhr else 2), mimetype='application/json')
|
||||||
|
|
||||||
|
|
||||||
|
def make_response(*args):
|
||||||
|
"""Sometimes it is necessary to set additional headers in a view. Because
|
||||||
|
views do not have to return response objects but can return a value that
|
||||||
|
is converted into a response object by Flask itself, it becomes tricky to
|
||||||
|
add headers to it. This function can be called instead of using a return
|
||||||
|
and you will get a response object which you can use to attach headers.
|
||||||
|
|
||||||
|
If view looked like this and you want to add a new header::
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return render_template('index.html', foo=42)
|
||||||
|
|
||||||
|
You can now do something like this::
|
||||||
|
|
||||||
|
def index():
|
||||||
|
response = make_response(render_template('index.html', foo=42))
|
||||||
|
response.headers['X-Parachutes'] = 'parachutes are cool'
|
||||||
|
return response
|
||||||
|
|
||||||
|
This function accepts the very same arguments you can return from a
|
||||||
|
view function. This for example creates a response with a 404 error
|
||||||
|
code::
|
||||||
|
|
||||||
|
response = make_response(render_template('not_found.html'), 404)
|
||||||
|
|
||||||
|
Internally this function does the following things:
|
||||||
|
|
||||||
|
- if no arguments are passed, it creates a new response argument
|
||||||
|
- if one argument is passed, :meth:`flask.Flask.make_response`
|
||||||
|
is invoked with it.
|
||||||
|
- if more than one argument is passed, the arguments are passed
|
||||||
|
to the :meth:`flask.Flask.make_response` function as tuple.
|
||||||
|
|
||||||
|
.. versionadded:: 0.6
|
||||||
|
"""
|
||||||
|
if not args:
|
||||||
|
return current_app.response_class()
|
||||||
|
if len(args) == 1:
|
||||||
|
args = args[0]
|
||||||
|
return current_app.make_response(args)
|
||||||
|
|
||||||
|
|
||||||
|
def url_for(endpoint, **values):
|
||||||
|
"""Generates a URL to the given endpoint with the method provided.
|
||||||
|
The endpoint is relative to the active module if modules are in use.
|
||||||
|
|
||||||
|
Here are some examples:
|
||||||
|
|
||||||
|
==================== ======================= =============================
|
||||||
|
Active Module Target Endpoint Target Function
|
||||||
|
==================== ======================= =============================
|
||||||
|
`None` ``'index'`` `index` of the application
|
||||||
|
`None` ``'.index'`` `index` of the application
|
||||||
|
``'admin'`` ``'index'`` `index` of the `admin` module
|
||||||
|
any ``'.index'`` `index` of the application
|
||||||
|
any ``'admin.index'`` `index` of the `admin` module
|
||||||
|
==================== ======================= =============================
|
||||||
|
|
||||||
|
Variable arguments that are unknown to the target endpoint are appended
|
||||||
|
to the generated URL as query arguments.
|
||||||
|
|
||||||
|
For more information, head over to the :ref:`Quickstart <url-building>`.
|
||||||
|
|
||||||
|
:param endpoint: the endpoint of the URL (name of the function)
|
||||||
|
:param values: the variable arguments of the URL rule
|
||||||
|
:param _external: if set to `True`, an absolute URL is generated.
|
||||||
|
"""
|
||||||
|
ctx = _request_ctx_stack.top
|
||||||
|
if '.' not in endpoint:
|
||||||
|
mod = ctx.request.module
|
||||||
|
if mod is not None:
|
||||||
|
endpoint = mod + '.' + endpoint
|
||||||
|
elif endpoint.startswith('.'):
|
||||||
|
endpoint = endpoint[1:]
|
||||||
|
external = values.pop('_external', False)
|
||||||
|
return ctx.url_adapter.build(endpoint, values, force_external=external)
|
||||||
|
|
||||||
|
|
||||||
|
def get_template_attribute(template_name, attribute):
|
||||||
|
"""Loads a macro (or variable) a template exports. This can be used to
|
||||||
|
invoke a macro from within Python code. If you for example have a
|
||||||
|
template named `_cider.html` with the following contents:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
{% macro hello(name) %}Hello {{ name }}!{% endmacro %}
|
||||||
|
|
||||||
|
You can access this from Python code like this::
|
||||||
|
|
||||||
|
hello = get_template_attribute('_cider.html', 'hello')
|
||||||
|
return hello('World')
|
||||||
|
|
||||||
|
.. versionadded:: 0.2
|
||||||
|
|
||||||
|
:param template_name: the name of the template
|
||||||
|
:param attribute: the name of the variable of macro to acccess
|
||||||
|
"""
|
||||||
|
return getattr(current_app.jinja_env.get_template(template_name).module,
|
||||||
|
attribute)
|
||||||
|
|
||||||
|
|
||||||
|
def flash(message, category='message'):
|
||||||
|
"""Flashes a message to the next request. In order to remove the
|
||||||
|
flashed message from the session and to display it to the user,
|
||||||
|
the template has to call :func:`get_flashed_messages`.
|
||||||
|
|
||||||
|
.. versionchanged: 0.3
|
||||||
|
`category` parameter added.
|
||||||
|
|
||||||
|
:param message: the message to be flashed.
|
||||||
|
:param category: the category for the message. The following values
|
||||||
|
are recommended: ``'message'`` for any kind of message,
|
||||||
|
``'error'`` for errors, ``'info'`` for information
|
||||||
|
messages and ``'warning'`` for warnings. However any
|
||||||
|
kind of string can be used as category.
|
||||||
|
"""
|
||||||
|
session.setdefault('_flashes', []).append((category, message))
|
||||||
|
|
||||||
|
|
||||||
|
def get_flashed_messages(with_categories=False):
|
||||||
|
"""Pulls all flashed messages from the session and returns them.
|
||||||
|
Further calls in the same request to the function will return
|
||||||
|
the same messages. By default just the messages are returned,
|
||||||
|
but when `with_categories` is set to `True`, the return value will
|
||||||
|
be a list of tuples in the form ``(category, message)`` instead.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
.. sourcecode:: html+jinja
|
||||||
|
|
||||||
|
{% for category, msg in get_flashed_messages(with_categories=true) %}
|
||||||
|
<p class=flash-{{ category }}>{{ msg }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
.. versionchanged:: 0.3
|
||||||
|
`with_categories` parameter added.
|
||||||
|
|
||||||
|
:param with_categories: set to `True` to also receive categories.
|
||||||
|
"""
|
||||||
|
flashes = _request_ctx_stack.top.flashes
|
||||||
|
if flashes is None:
|
||||||
|
_request_ctx_stack.top.flashes = flashes = session.pop('_flashes', [])
|
||||||
|
if not with_categories:
|
||||||
|
return [x[1] for x in flashes]
|
||||||
|
return flashes
|
||||||
|
|
||||||
|
|
||||||
|
def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
|
attachment_filename=None, add_etags=True,
|
||||||
|
cache_timeout=60 * 60 * 12, conditional=False):
|
||||||
|
"""Sends the contents of a file to the client. This will use the
|
||||||
|
most efficient method available and configured. By default it will
|
||||||
|
try to use the WSGI server's file_wrapper support. Alternatively
|
||||||
|
you can set the application's :attr:`~Flask.use_x_sendfile` attribute
|
||||||
|
to ``True`` to directly emit an `X-Sendfile` header. This however
|
||||||
|
requires support of the underlying webserver for `X-Sendfile`.
|
||||||
|
|
||||||
|
By default it will try to guess the mimetype for you, but you can
|
||||||
|
also explicitly provide one. For extra security you probably want
|
||||||
|
to sent certain files as attachment (HTML for instance). The mimetype
|
||||||
|
guessing requires a `filename` or an `attachment_filename` to be
|
||||||
|
provided.
|
||||||
|
|
||||||
|
Please never pass filenames to this function from user sources without
|
||||||
|
checking them first. Something like this is usually sufficient to
|
||||||
|
avoid security problems::
|
||||||
|
|
||||||
|
if '..' in filename or filename.startswith('/'):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
.. versionadded:: 0.2
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
The `add_etags`, `cache_timeout` and `conditional` parameters were
|
||||||
|
added. The default behaviour is now to attach etags.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.7
|
||||||
|
mimetype guessing and etag support for file objects was
|
||||||
|
deprecated because it was unreliable. Pass a filename if you are
|
||||||
|
able to, otherwise attach an etag yourself. This functionality
|
||||||
|
will be removed in Flask 1.0
|
||||||
|
|
||||||
|
:param filename_or_fp: the filename of the file to send. This is
|
||||||
|
relative to the :attr:`~Flask.root_path` if a
|
||||||
|
relative path is specified.
|
||||||
|
Alternatively a file object might be provided
|
||||||
|
in which case `X-Sendfile` might not work and
|
||||||
|
fall back to the traditional method.
|
||||||
|
:param mimetype: the mimetype of the file if provided, otherwise
|
||||||
|
auto detection happens.
|
||||||
|
:param as_attachment: set to `True` if you want to send this file with
|
||||||
|
a ``Content-Disposition: attachment`` header.
|
||||||
|
:param attachment_filename: the filename for the attachment if it
|
||||||
|
differs from the file's filename.
|
||||||
|
:param add_etags: set to `False` to disable attaching of etags.
|
||||||
|
:param conditional: set to `True` to enable conditional responses.
|
||||||
|
:param cache_timeout: the timeout in seconds for the headers.
|
||||||
|
"""
|
||||||
|
mtime = None
|
||||||
|
if isinstance(filename_or_fp, basestring):
|
||||||
|
filename = filename_or_fp
|
||||||
|
file = None
|
||||||
|
else:
|
||||||
|
from warnings import warn
|
||||||
|
file = filename_or_fp
|
||||||
|
filename = getattr(file, 'name', None)
|
||||||
|
|
||||||
|
# XXX: this behaviour is now deprecated because it was unreliable.
|
||||||
|
# removed in Flask 1.0
|
||||||
|
if not attachment_filename and not mimetype \
|
||||||
|
and isinstance(filename, basestring):
|
||||||
|
warn(DeprecationWarning('The filename support for file objects '
|
||||||
|
'passed to send_file is not deprecated. Pass an '
|
||||||
|
'attach_filename if you want mimetypes to be guessed.'),
|
||||||
|
stacklevel=2)
|
||||||
|
if add_etags:
|
||||||
|
warn(DeprecationWarning('In future flask releases etags will no '
|
||||||
|
'longer be generated for file objects passed to the send_file '
|
||||||
|
'function because this behaviour was unreliable. Pass '
|
||||||
|
'filenames instead if possible, otherwise attach an etag '
|
||||||
|
'yourself based on another value'), stacklevel=2)
|
||||||
|
|
||||||
|
if filename is not None:
|
||||||
|
if not os.path.isabs(filename):
|
||||||
|
filename = os.path.join(current_app.root_path, filename)
|
||||||
|
if mimetype is None and (filename or attachment_filename):
|
||||||
|
mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
|
||||||
|
if mimetype is None:
|
||||||
|
mimetype = 'application/octet-stream'
|
||||||
|
|
||||||
|
headers = Headers()
|
||||||
|
if as_attachment:
|
||||||
|
if attachment_filename is None:
|
||||||
|
if filename is None:
|
||||||
|
raise TypeError('filename unavailable, required for '
|
||||||
|
'sending as attachment')
|
||||||
|
attachment_filename = os.path.basename(filename)
|
||||||
|
headers.add('Content-Disposition', 'attachment',
|
||||||
|
filename=attachment_filename)
|
||||||
|
|
||||||
|
if current_app.use_x_sendfile and filename:
|
||||||
|
if file is not None:
|
||||||
|
file.close()
|
||||||
|
headers['X-Sendfile'] = filename
|
||||||
|
data = None
|
||||||
|
else:
|
||||||
|
if file is None:
|
||||||
|
file = open(filename, 'rb')
|
||||||
|
mtime = os.path.getmtime(filename)
|
||||||
|
data = wrap_file(request.environ, file)
|
||||||
|
|
||||||
|
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
|
||||||
|
direct_passthrough=True)
|
||||||
|
|
||||||
|
# if we know the file modification date, we can store it as the
|
||||||
|
# current time to better support conditional requests. Werkzeug
|
||||||
|
# as of 0.6.1 will override this value however in the conditional
|
||||||
|
# response with the current time. This will be fixed in Werkzeug
|
||||||
|
# with a new release, however many WSGI servers will still emit
|
||||||
|
# a separate date header.
|
||||||
|
if mtime is not None:
|
||||||
|
rv.date = int(mtime)
|
||||||
|
|
||||||
|
rv.cache_control.public = True
|
||||||
|
if cache_timeout:
|
||||||
|
rv.cache_control.max_age = cache_timeout
|
||||||
|
rv.expires = int(time() + cache_timeout)
|
||||||
|
|
||||||
|
if add_etags and filename is not None:
|
||||||
|
rv.set_etag('flask-%s-%s-%s' % (
|
||||||
|
os.path.getmtime(filename),
|
||||||
|
os.path.getsize(filename),
|
||||||
|
adler32(filename) & 0xffffffff
|
||||||
|
))
|
||||||
|
if conditional:
|
||||||
|
rv = rv.make_conditional(request)
|
||||||
|
# make sure we don't send x-sendfile for servers that
|
||||||
|
# ignore the 304 status code for x-sendfile.
|
||||||
|
if rv.status_code == 304:
|
||||||
|
rv.headers.pop('x-sendfile', None)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def send_from_directory(directory, filename, **options):
|
||||||
|
"""Send a file from a given directory with :func:`send_file`. This
|
||||||
|
is a secure way to quickly expose static files from an upload folder
|
||||||
|
or something similar.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
@app.route('/uploads/<path:filename>')
|
||||||
|
def download_file(filename):
|
||||||
|
return send_from_directory(app.config['UPLOAD_FOLDER'],
|
||||||
|
filename, as_attachment=True)
|
||||||
|
|
||||||
|
.. admonition:: Sending files and Performance
|
||||||
|
|
||||||
|
It is strongly recommended to activate either `X-Sendfile` support in
|
||||||
|
your webserver or (if no authentication happens) to tell the webserver
|
||||||
|
to serve files for the given path on its own without calling into the
|
||||||
|
web application for improved performance.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
|
||||||
|
:param directory: the directory where all the files are stored.
|
||||||
|
:param filename: the filename relative to that directory to
|
||||||
|
download.
|
||||||
|
:param options: optional keyword arguments that are directly
|
||||||
|
forwarded to :func:`send_file`.
|
||||||
|
"""
|
||||||
|
filename = posixpath.normpath(filename)
|
||||||
|
if filename.startswith(('/', '../')):
|
||||||
|
raise NotFound()
|
||||||
|
filename = os.path.join(directory, filename)
|
||||||
|
if not os.path.isfile(filename):
|
||||||
|
raise NotFound()
|
||||||
|
return send_file(filename, conditional=True, **options)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_package_path(name):
|
||||||
|
"""Returns the path to a package or cwd if that cannot be found."""
|
||||||
|
try:
|
||||||
|
return os.path.abspath(os.path.dirname(sys.modules[name].__file__))
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
return os.getcwd()
|
||||||
|
|
||||||
|
|
||||||
|
class _PackageBoundObject(object):
|
||||||
|
|
||||||
|
def __init__(self, import_name):
|
||||||
|
#: The name of the package or module. Do not change this once
|
||||||
|
#: it was set by the constructor.
|
||||||
|
self.import_name = import_name
|
||||||
|
|
||||||
|
#: Where is the app root located?
|
||||||
|
self.root_path = _get_package_path(self.import_name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_static_folder(self):
|
||||||
|
"""This is `True` if the package bound object's container has a
|
||||||
|
folder named ``'static'``.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
"""
|
||||||
|
return os.path.isdir(os.path.join(self.root_path, 'static'))
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def jinja_loader(self):
|
||||||
|
"""The Jinja loader for this package bound object.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
"""
|
||||||
|
return FileSystemLoader(os.path.join(self.root_path, 'templates'))
|
||||||
|
|
||||||
|
def send_static_file(self, filename):
|
||||||
|
"""Function used internally to send static files from the static
|
||||||
|
folder to the browser.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
"""
|
||||||
|
return send_from_directory(os.path.join(self.root_path, 'static'),
|
||||||
|
filename)
|
||||||
|
|
||||||
|
def open_resource(self, resource):
|
||||||
|
"""Opens a resource from the application's resource folder. To see
|
||||||
|
how this works, consider the following folder structure::
|
||||||
|
|
||||||
|
/myapplication.py
|
||||||
|
/schemal.sql
|
||||||
|
/static
|
||||||
|
/style.css
|
||||||
|
/templates
|
||||||
|
/layout.html
|
||||||
|
/index.html
|
||||||
|
|
||||||
|
If you want to open the `schema.sql` file you would do the
|
||||||
|
following::
|
||||||
|
|
||||||
|
with app.open_resource('schema.sql') as f:
|
||||||
|
contents = f.read()
|
||||||
|
do_something_with(contents)
|
||||||
|
|
||||||
|
:param resource: the name of the resource. To access resources within
|
||||||
|
subfolders use forward slashes as separator.
|
||||||
|
"""
|
||||||
|
return open(os.path.join(self.root_path, resource), 'rb')
|
||||||
42
flask/logging.py
Normal file
42
flask/logging.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.logging
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the logging support for Flask.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from logging import getLogger, StreamHandler, Formatter, Logger, DEBUG
|
||||||
|
|
||||||
|
|
||||||
|
def create_logger(app):
|
||||||
|
"""Creates a logger for the given application. This logger works
|
||||||
|
similar to a regular Python logger but changes the effective logging
|
||||||
|
level based on the application's debug flag. Furthermore this
|
||||||
|
function also removes all attached handlers in case there was a
|
||||||
|
logger with the log name before.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class DebugLogger(Logger):
|
||||||
|
def getEffectiveLevel(x):
|
||||||
|
return DEBUG if app.debug else Logger.getEffectiveLevel(x)
|
||||||
|
|
||||||
|
class DebugHandler(StreamHandler):
|
||||||
|
def emit(x, record):
|
||||||
|
StreamHandler.emit(x, record) if app.debug else None
|
||||||
|
|
||||||
|
handler = DebugHandler()
|
||||||
|
handler.setLevel(DEBUG)
|
||||||
|
handler.setFormatter(Formatter(app.debug_log_format))
|
||||||
|
logger = getLogger(app.logger_name)
|
||||||
|
# just in case that was not a new logger, get rid of all the handlers
|
||||||
|
# already attached to it.
|
||||||
|
del logger.handlers[:]
|
||||||
|
logger.__class__ = DebugLogger
|
||||||
|
logger.addHandler(handler)
|
||||||
|
return logger
|
||||||
222
flask/module.py
Normal file
222
flask/module.py
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.module
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements a class that represents module blueprints.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .helpers import _PackageBoundObject, _endpoint_from_view_func
|
||||||
|
|
||||||
|
|
||||||
|
def _register_module(module, static_path):
|
||||||
|
"""Internal helper function that returns a function for recording
|
||||||
|
that registers the `send_static_file` function for the module on
|
||||||
|
the application if necessary. It also registers the module on
|
||||||
|
the application.
|
||||||
|
"""
|
||||||
|
def _register(state):
|
||||||
|
state.app.modules[module.name] = module
|
||||||
|
# do not register the rule if the static folder of the
|
||||||
|
# module is the same as the one from the application.
|
||||||
|
if state.app.root_path == module.root_path:
|
||||||
|
return
|
||||||
|
path = static_path
|
||||||
|
if path is None:
|
||||||
|
path = state.app.static_path
|
||||||
|
if state.url_prefix:
|
||||||
|
path = state.url_prefix + path
|
||||||
|
state.app.add_url_rule(path + '/<path:filename>',
|
||||||
|
endpoint='%s.static' % module.name,
|
||||||
|
view_func=module.send_static_file,
|
||||||
|
subdomain=module.subdomain)
|
||||||
|
return _register
|
||||||
|
|
||||||
|
|
||||||
|
class _ModuleSetupState(object):
|
||||||
|
|
||||||
|
def __init__(self, app, url_prefix=None, subdomain=None):
|
||||||
|
self.app = app
|
||||||
|
self.url_prefix = url_prefix
|
||||||
|
self.subdomain = subdomain
|
||||||
|
|
||||||
|
|
||||||
|
class Module(_PackageBoundObject):
|
||||||
|
"""Container object that enables pluggable applications. A module can
|
||||||
|
be used to organize larger applications. They represent blueprints that,
|
||||||
|
in combination with a :class:`Flask` object are used to create a large
|
||||||
|
application.
|
||||||
|
|
||||||
|
A module is like an application bound to an `import_name`. Multiple
|
||||||
|
modules can share the same import names, but in that case a `name` has
|
||||||
|
to be provided to keep them apart. If different import names are used,
|
||||||
|
the rightmost part of the import name is used as name.
|
||||||
|
|
||||||
|
Here's an example structure for a larger application::
|
||||||
|
|
||||||
|
/myapplication
|
||||||
|
/__init__.py
|
||||||
|
/views
|
||||||
|
/__init__.py
|
||||||
|
/admin.py
|
||||||
|
/frontend.py
|
||||||
|
|
||||||
|
The `myapplication/__init__.py` can look like this::
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
from myapplication.views.admin import admin
|
||||||
|
from myapplication.views.frontend import frontend
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.register_module(admin, url_prefix='/admin')
|
||||||
|
app.register_module(frontend)
|
||||||
|
|
||||||
|
And here's an example view module (`myapplication/views/admin.py`)::
|
||||||
|
|
||||||
|
from flask import Module
|
||||||
|
|
||||||
|
admin = Module(__name__)
|
||||||
|
|
||||||
|
@admin.route('/')
|
||||||
|
def index():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@admin.route('/login')
|
||||||
|
def login():
|
||||||
|
pass
|
||||||
|
|
||||||
|
For a gentle introduction into modules, checkout the
|
||||||
|
:ref:`working-with-modules` section.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
The `static_path` parameter was added and it's now possible for
|
||||||
|
modules to refer to their own templates and static files. See
|
||||||
|
:ref:`modules-and-resources` for more information.
|
||||||
|
|
||||||
|
.. versionadded:: 0.6
|
||||||
|
The `subdomain` parameter was added.
|
||||||
|
|
||||||
|
:param import_name: the name of the Python package or module
|
||||||
|
implementing this :class:`Module`.
|
||||||
|
:param name: the internal short name for the module. Unless specified
|
||||||
|
the rightmost part of the import name
|
||||||
|
:param url_prefix: an optional string that is used to prefix all the
|
||||||
|
URL rules of this module. This can also be specified
|
||||||
|
when registering the module with the application.
|
||||||
|
:param subdomain: used to set the subdomain setting for URL rules that
|
||||||
|
do not have a subdomain setting set.
|
||||||
|
:param static_path: can be used to specify a different path for the
|
||||||
|
static files on the web. Defaults to ``/static``.
|
||||||
|
This does not affect the folder the files are served
|
||||||
|
*from*.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, import_name, name=None, url_prefix=None,
|
||||||
|
static_path=None, subdomain=None):
|
||||||
|
if name is None:
|
||||||
|
assert '.' in import_name, 'name required if package name ' \
|
||||||
|
'does not point to a submodule'
|
||||||
|
name = import_name.rsplit('.', 1)[1]
|
||||||
|
_PackageBoundObject.__init__(self, import_name)
|
||||||
|
self.name = name
|
||||||
|
self.url_prefix = url_prefix
|
||||||
|
self.subdomain = subdomain
|
||||||
|
self._register_events = [_register_module(self, static_path)]
|
||||||
|
|
||||||
|
def route(self, rule, **options):
|
||||||
|
"""Like :meth:`Flask.route` but for a module. The endpoint for the
|
||||||
|
:func:`url_for` function is prefixed with the name of the module.
|
||||||
|
"""
|
||||||
|
def decorator(f):
|
||||||
|
self.add_url_rule(rule, f.__name__, f, **options)
|
||||||
|
return f
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||||
|
"""Like :meth:`Flask.add_url_rule` but for a module. The endpoint for
|
||||||
|
the :func:`url_for` function is prefixed with the name of the module.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.6
|
||||||
|
The `endpoint` argument is now optional and will default to the
|
||||||
|
function name to consistent with the function of the same name
|
||||||
|
on the application object.
|
||||||
|
"""
|
||||||
|
def register_rule(state):
|
||||||
|
the_rule = rule
|
||||||
|
if state.url_prefix:
|
||||||
|
the_rule = state.url_prefix + rule
|
||||||
|
options.setdefault('subdomain', state.subdomain)
|
||||||
|
the_endpoint = endpoint
|
||||||
|
if the_endpoint is None:
|
||||||
|
the_endpoint = _endpoint_from_view_func(view_func)
|
||||||
|
state.app.add_url_rule(the_rule, '%s.%s' % (self.name,
|
||||||
|
the_endpoint),
|
||||||
|
view_func, **options)
|
||||||
|
self._record(register_rule)
|
||||||
|
|
||||||
|
def before_request(self, f):
|
||||||
|
"""Like :meth:`Flask.before_request` but for a module. This function
|
||||||
|
is only executed before each request that is handled by a function of
|
||||||
|
that module.
|
||||||
|
"""
|
||||||
|
self._record(lambda s: s.app.before_request_funcs
|
||||||
|
.setdefault(self.name, []).append(f))
|
||||||
|
return f
|
||||||
|
|
||||||
|
def before_app_request(self, f):
|
||||||
|
"""Like :meth:`Flask.before_request`. Such a function is executed
|
||||||
|
before each request, even if outside of a module.
|
||||||
|
"""
|
||||||
|
self._record(lambda s: s.app.before_request_funcs
|
||||||
|
.setdefault(None, []).append(f))
|
||||||
|
return f
|
||||||
|
|
||||||
|
def after_request(self, f):
|
||||||
|
"""Like :meth:`Flask.after_request` but for a module. This function
|
||||||
|
is only executed after each request that is handled by a function of
|
||||||
|
that module.
|
||||||
|
"""
|
||||||
|
self._record(lambda s: s.app.after_request_funcs
|
||||||
|
.setdefault(self.name, []).append(f))
|
||||||
|
return f
|
||||||
|
|
||||||
|
def after_app_request(self, f):
|
||||||
|
"""Like :meth:`Flask.after_request` but for a module. Such a function
|
||||||
|
is executed after each request, even if outside of the module.
|
||||||
|
"""
|
||||||
|
self._record(lambda s: s.app.after_request_funcs
|
||||||
|
.setdefault(None, []).append(f))
|
||||||
|
return f
|
||||||
|
|
||||||
|
def context_processor(self, f):
|
||||||
|
"""Like :meth:`Flask.context_processor` but for a module. This
|
||||||
|
function is only executed for requests handled by a module.
|
||||||
|
"""
|
||||||
|
self._record(lambda s: s.app.template_context_processors
|
||||||
|
.setdefault(self.name, []).append(f))
|
||||||
|
return f
|
||||||
|
|
||||||
|
def app_context_processor(self, f):
|
||||||
|
"""Like :meth:`Flask.context_processor` but for a module. Such a
|
||||||
|
function is executed each request, even if outside of the module.
|
||||||
|
"""
|
||||||
|
self._record(lambda s: s.app.template_context_processors
|
||||||
|
.setdefault(None, []).append(f))
|
||||||
|
return f
|
||||||
|
|
||||||
|
def app_errorhandler(self, code):
|
||||||
|
"""Like :meth:`Flask.errorhandler` but for a module. This
|
||||||
|
handler is used for all requests, even if outside of the module.
|
||||||
|
|
||||||
|
.. versionadded:: 0.4
|
||||||
|
"""
|
||||||
|
def decorator(f):
|
||||||
|
self._record(lambda s: s.app.errorhandler(code)(f))
|
||||||
|
return f
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def _record(self, func):
|
||||||
|
self._register_events.append(func)
|
||||||
43
flask/session.py
Normal file
43
flask/session.py
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.session
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements cookie based sessions based on Werkzeug's secure cookie
|
||||||
|
system.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from werkzeug.contrib.securecookie import SecureCookie
|
||||||
|
|
||||||
|
|
||||||
|
class Session(SecureCookie):
|
||||||
|
"""Expands the session with support for switching between permanent
|
||||||
|
and non-permanent sessions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_permanent(self):
|
||||||
|
return self.get('_permanent', False)
|
||||||
|
|
||||||
|
def _set_permanent(self, value):
|
||||||
|
self['_permanent'] = bool(value)
|
||||||
|
|
||||||
|
permanent = property(_get_permanent, _set_permanent)
|
||||||
|
del _get_permanent, _set_permanent
|
||||||
|
|
||||||
|
|
||||||
|
class _NullSession(Session):
|
||||||
|
"""Class used to generate nicer error messages if sessions are not
|
||||||
|
available. Will still allow read-only access to the empty session
|
||||||
|
but fail on setting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _fail(self, *args, **kwargs):
|
||||||
|
raise RuntimeError('the session is unavailable because no secret '
|
||||||
|
'key was set. Set the secret_key on the '
|
||||||
|
'application to something unique and secret.')
|
||||||
|
__setitem__ = __delitem__ = clear = pop = popitem = \
|
||||||
|
update = setdefault = _fail
|
||||||
|
del _fail
|
||||||
50
flask/signals.py
Normal file
50
flask/signals.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.signals
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements signals based on blinker if available, otherwise
|
||||||
|
falls silently back to a noop
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
signals_available = False
|
||||||
|
try:
|
||||||
|
from blinker import Namespace
|
||||||
|
signals_available = True
|
||||||
|
except ImportError:
|
||||||
|
class Namespace(object):
|
||||||
|
def signal(self, name, doc=None):
|
||||||
|
return _FakeSignal(name, doc)
|
||||||
|
|
||||||
|
class _FakeSignal(object):
|
||||||
|
"""If blinker is unavailable, create a fake class with the same
|
||||||
|
interface that allows sending of signals but will fail with an
|
||||||
|
error on anything else. Instead of doing anything on send, it
|
||||||
|
will just ignore the arguments and do nothing instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, doc=None):
|
||||||
|
self.name = name
|
||||||
|
self.__doc__ = doc
|
||||||
|
def _fail(self, *args, **kwargs):
|
||||||
|
raise RuntimeError('signalling support is unavailable '
|
||||||
|
'because the blinker library is '
|
||||||
|
'not installed.')
|
||||||
|
send = lambda *a, **kw: None
|
||||||
|
connect = disconnect = has_receivers_for = receivers_for = \
|
||||||
|
temporarily_connected_to = _fail
|
||||||
|
del _fail
|
||||||
|
|
||||||
|
# the namespace for code signals. If you are not flask code, do
|
||||||
|
# not put signals in here. Create your own namespace instead.
|
||||||
|
_signals = Namespace()
|
||||||
|
|
||||||
|
|
||||||
|
# core signals. For usage examples grep the sourcecode or consult
|
||||||
|
# the API documentation in docs/api.rst as well as docs/signals.rst
|
||||||
|
template_rendered = _signals.signal('template-rendered')
|
||||||
|
request_started = _signals.signal('request-started')
|
||||||
|
request_finished = _signals.signal('request-finished')
|
||||||
|
got_request_exception = _signals.signal('got-request-exception')
|
||||||
100
flask/templating.py
Normal file
100
flask/templating.py
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.templating
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the bridge to Jinja2.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
import posixpath
|
||||||
|
from jinja2 import BaseLoader, TemplateNotFound
|
||||||
|
|
||||||
|
from .globals import _request_ctx_stack
|
||||||
|
from .signals import template_rendered
|
||||||
|
|
||||||
|
|
||||||
|
def _default_template_ctx_processor():
|
||||||
|
"""Default template context processor. Injects `request`,
|
||||||
|
`session` and `g`.
|
||||||
|
"""
|
||||||
|
reqctx = _request_ctx_stack.top
|
||||||
|
return dict(
|
||||||
|
config=reqctx.app.config,
|
||||||
|
request=reqctx.request,
|
||||||
|
session=reqctx.session,
|
||||||
|
g=reqctx.g
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _DispatchingJinjaLoader(BaseLoader):
|
||||||
|
"""A loader that looks for templates in the application and all
|
||||||
|
the module folders.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
def get_source(self, environment, template):
|
||||||
|
template = posixpath.normpath(template)
|
||||||
|
if template.startswith('../'):
|
||||||
|
raise TemplateNotFound(template)
|
||||||
|
loader = None
|
||||||
|
try:
|
||||||
|
module, name = template.split('/', 1)
|
||||||
|
loader = self.app.modules[module].jinja_loader
|
||||||
|
except (ValueError, KeyError):
|
||||||
|
pass
|
||||||
|
# if there was a module and it has a loader, try this first
|
||||||
|
if loader is not None:
|
||||||
|
try:
|
||||||
|
return loader.get_source(environment, name)
|
||||||
|
except TemplateNotFound:
|
||||||
|
pass
|
||||||
|
# fall back to application loader if module failed
|
||||||
|
return self.app.jinja_loader.get_source(environment, template)
|
||||||
|
|
||||||
|
def list_templates(self):
|
||||||
|
result = self.app.jinja_loader.list_templates()
|
||||||
|
for name, module in self.app.modules.iteritems():
|
||||||
|
if module.jinja_loader is not None:
|
||||||
|
for template in module.jinja_loader.list_templates():
|
||||||
|
result.append('%s/%s' % (name, template))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _render(template, context, app):
|
||||||
|
"""Renders the template and fires the signal"""
|
||||||
|
rv = template.render(context)
|
||||||
|
template_rendered.send(app, template=template, context=context)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def render_template(template_name, **context):
|
||||||
|
"""Renders a template from the template folder with the given
|
||||||
|
context.
|
||||||
|
|
||||||
|
:param template_name: the name of the template to be rendered
|
||||||
|
:param context: the variables that should be available in the
|
||||||
|
context of the template.
|
||||||
|
"""
|
||||||
|
ctx = _request_ctx_stack.top
|
||||||
|
ctx.app.update_template_context(context)
|
||||||
|
return _render(ctx.app.jinja_env.get_template(template_name),
|
||||||
|
context, ctx.app)
|
||||||
|
|
||||||
|
|
||||||
|
def render_template_string(source, **context):
|
||||||
|
"""Renders a template from the given template source string
|
||||||
|
with the given context.
|
||||||
|
|
||||||
|
:param template_name: the sourcecode of the template to be
|
||||||
|
rendered
|
||||||
|
:param context: the variables that should be available in the
|
||||||
|
context of the template.
|
||||||
|
"""
|
||||||
|
ctx = _request_ctx_stack.top
|
||||||
|
ctx.app.update_template_context(context)
|
||||||
|
return _render(ctx.app.jinja_env.from_string(source),
|
||||||
|
context, ctx.app)
|
||||||
45
flask/testing.py
Normal file
45
flask/testing.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.testing
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements test support helpers. This module is lazily imported
|
||||||
|
and usually not used in production environments.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from werkzeug import Client
|
||||||
|
from flask import _request_ctx_stack
|
||||||
|
|
||||||
|
|
||||||
|
class FlaskClient(Client):
|
||||||
|
"""Works like a regular Werkzeug test client but has some
|
||||||
|
knowledge about how Flask works to defer the cleanup of the
|
||||||
|
request context stack to the end of a with body when used
|
||||||
|
in a with statement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
preserve_context = context_preserved = False
|
||||||
|
|
||||||
|
def open(self, *args, **kwargs):
|
||||||
|
if self.context_preserved:
|
||||||
|
_request_ctx_stack.pop()
|
||||||
|
self.context_preserved = False
|
||||||
|
kwargs.setdefault('environ_overrides', {}) \
|
||||||
|
['flask._preserve_context'] = self.preserve_context
|
||||||
|
old = _request_ctx_stack.top
|
||||||
|
try:
|
||||||
|
return Client.open(self, *args, **kwargs)
|
||||||
|
finally:
|
||||||
|
self.context_preserved = _request_ctx_stack.top is not old
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.preserve_context = True
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
|
self.preserve_context = False
|
||||||
|
if self.context_preserved:
|
||||||
|
_request_ctx_stack.pop()
|
||||||
88
flask/wrappers.py
Normal file
88
flask/wrappers.py
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
flask.wrappers
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Implements the WSGI wrappers (request and response).
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Armin Ronacher.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from werkzeug import Request as RequestBase, Response as ResponseBase, \
|
||||||
|
cached_property
|
||||||
|
|
||||||
|
from .helpers import json, _assert_have_json
|
||||||
|
from .globals import _request_ctx_stack
|
||||||
|
|
||||||
|
|
||||||
|
class Request(RequestBase):
|
||||||
|
"""The request object used by default in Flask. Remembers the
|
||||||
|
matched endpoint and view arguments.
|
||||||
|
|
||||||
|
It is what ends up as :class:`~flask.request`. If you want to replace
|
||||||
|
the request object used you can subclass this and set
|
||||||
|
:attr:`~flask.Flask.request_class` to your subclass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: the internal URL rule that matched the request. This can be
|
||||||
|
#: useful to inspect which methods are allowed for the URL from
|
||||||
|
#: a before/after handler (``request.url_rule.methods``) etc.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 0.6
|
||||||
|
url_rule = None
|
||||||
|
|
||||||
|
#: a dict of view arguments that matched the request. If an exception
|
||||||
|
#: happened when matching, this will be `None`.
|
||||||
|
view_args = None
|
||||||
|
|
||||||
|
#: if matching the URL failed, this is the exception that will be
|
||||||
|
#: raised / was raised as part of the request handling. This is
|
||||||
|
#: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
|
||||||
|
#: something similar.
|
||||||
|
routing_exception = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_content_length(self):
|
||||||
|
"""Read-only view of the `MAX_CONTENT_LENGTH` config key."""
|
||||||
|
ctx = _request_ctx_stack.top
|
||||||
|
if ctx is not None:
|
||||||
|
return ctx.app.config['MAX_CONTENT_LENGTH']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endpoint(self):
|
||||||
|
"""The endpoint that matched the request. This in combination with
|
||||||
|
:attr:`view_args` can be used to reconstruct the same or a
|
||||||
|
modified URL. If an exception happened when matching, this will
|
||||||
|
be `None`.
|
||||||
|
"""
|
||||||
|
if self.url_rule is not None:
|
||||||
|
return self.url_rule.endpoint
|
||||||
|
|
||||||
|
@property
|
||||||
|
def module(self):
|
||||||
|
"""The name of the current module"""
|
||||||
|
if self.url_rule and '.' in self.url_rule.endpoint:
|
||||||
|
return self.url_rule.endpoint.rsplit('.', 1)[0]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def json(self):
|
||||||
|
"""If the mimetype is `application/json` this will contain the
|
||||||
|
parsed JSON data.
|
||||||
|
"""
|
||||||
|
if __debug__:
|
||||||
|
_assert_have_json()
|
||||||
|
if self.mimetype == 'application/json':
|
||||||
|
return json.loads(self.data)
|
||||||
|
|
||||||
|
|
||||||
|
class Response(ResponseBase):
|
||||||
|
"""The response object that is used by default in Flask. Works like the
|
||||||
|
response object from Werkzeug but is set to have an HTML mimetype by
|
||||||
|
default. Quite often you don't have to create this object yourself because
|
||||||
|
:meth:`~flask.Flask.make_response` will take care of that for you.
|
||||||
|
|
||||||
|
If you want to replace the response object used you can subclass this and
|
||||||
|
set :attr:`~flask.Flask.response_class` to your subclass.
|
||||||
|
"""
|
||||||
|
default_mimetype = 'text/html'
|
||||||
43
setup.py
43
setup.py
|
|
@ -38,8 +38,44 @@ Links
|
||||||
<http://github.com/mitsuhiko/flask/zipball/master#egg=Flask-dev>`_
|
<http://github.com/mitsuhiko/flask/zipball/master#egg=Flask-dev>`_
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from setuptools import setup
|
from setuptools import Command, setup
|
||||||
|
|
||||||
|
class run_audit(Command):
|
||||||
|
"""Audits source code using PyFlakes for following issues:
|
||||||
|
- Names which are used but not defined or used before they are defined.
|
||||||
|
- Names which are redefined without having been used.
|
||||||
|
"""
|
||||||
|
description = "Audit source code with PyFlakes"
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
all = None
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
import os, sys
|
||||||
|
try:
|
||||||
|
import pyflakes.scripts.pyflakes as flakes
|
||||||
|
except ImportError:
|
||||||
|
print "Audit requires PyFlakes installed in your system."""
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
dirs = ['flask', 'tests']
|
||||||
|
# Add example directories
|
||||||
|
for dir in ['flaskr', 'jqueryexample', 'minitwit']:
|
||||||
|
dirs.append(os.path.join('examples', dir))
|
||||||
|
# TODO: Add test subdirectories
|
||||||
|
warns = 0
|
||||||
|
for dir in dirs:
|
||||||
|
for filename in os.listdir(dir):
|
||||||
|
if filename.endswith('.py') and filename != '__init__.py':
|
||||||
|
warns += flakes.checkPath(os.path.join(dir, filename))
|
||||||
|
if warns > 0:
|
||||||
|
print ("Audit finished with total %d warnings." % warns)
|
||||||
|
else:
|
||||||
|
print ("No problems found in sourcecode.")
|
||||||
|
|
||||||
def run_tests():
|
def run_tests():
|
||||||
import os, sys
|
import os, sys
|
||||||
|
|
@ -50,7 +86,7 @@ def run_tests():
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Flask',
|
name='Flask',
|
||||||
version='0.5',
|
version='0.7',
|
||||||
url='http://github.com/mitsuhiko/flask/',
|
url='http://github.com/mitsuhiko/flask/',
|
||||||
license='BSD',
|
license='BSD',
|
||||||
author='Armin Ronacher',
|
author='Armin Ronacher',
|
||||||
|
|
@ -58,7 +94,7 @@ 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__,
|
||||||
py_modules=['flask'],
|
packages=['flask'],
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
platforms='any',
|
platforms='any',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
|
|
@ -75,5 +111,6 @@ setup(
|
||||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||||
],
|
],
|
||||||
|
cmdclass={'audit': run_audit},
|
||||||
test_suite='__main__.run_tests'
|
test_suite='__main__.run_tests'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,14 @@ import re
|
||||||
import sys
|
import sys
|
||||||
import flask
|
import flask
|
||||||
import unittest
|
import unittest
|
||||||
import tempfile
|
|
||||||
from logging import StreamHandler
|
from logging import StreamHandler
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from werkzeug import parse_date, parse_options_header
|
from werkzeug import parse_date, parse_options_header
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
from jinja2 import TemplateNotFound
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
|
|
||||||
|
|
||||||
example_path = os.path.join(os.path.dirname(__file__), '..', 'examples')
|
example_path = os.path.join(os.path.dirname(__file__), '..', 'examples')
|
||||||
sys.path.append(os.path.join(example_path, 'flaskr'))
|
sys.path.append(os.path.join(example_path, 'flaskr'))
|
||||||
sys.path.append(os.path.join(example_path, 'minitwit'))
|
sys.path.append(os.path.join(example_path, 'minitwit'))
|
||||||
|
|
@ -33,8 +33,27 @@ TEST_KEY = 'foo'
|
||||||
SECRET_KEY = 'devkey'
|
SECRET_KEY = 'devkey'
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def catch_warnings():
|
||||||
|
"""Catch warnings in a with block in a list"""
|
||||||
|
import warnings
|
||||||
|
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
|
@contextmanager
|
||||||
def catch_stderr():
|
def catch_stderr():
|
||||||
|
"""Catch stderr in a StringIO"""
|
||||||
old_stderr = sys.stderr
|
old_stderr = sys.stderr
|
||||||
sys.stderr = rv = StringIO()
|
sys.stderr = rv = StringIO()
|
||||||
try:
|
try:
|
||||||
|
|
@ -72,7 +91,7 @@ class ContextTestCase(unittest.TestCase):
|
||||||
ctx.pop()
|
ctx.pop()
|
||||||
try:
|
try:
|
||||||
index()
|
index()
|
||||||
except AttributeError:
|
except RuntimeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
assert 0, 'expected runtime error'
|
assert 0, 'expected runtime error'
|
||||||
|
|
@ -110,6 +129,26 @@ class ContextTestCase(unittest.TestCase):
|
||||||
|
|
||||||
class BasicFunctionalityTestCase(unittest.TestCase):
|
class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_options_work(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
@app.route('/', methods=['GET', 'POST'])
|
||||||
|
def index():
|
||||||
|
return 'Hello World'
|
||||||
|
rv = app.test_client().open('/', method='OPTIONS')
|
||||||
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
|
||||||
|
assert rv.data == ''
|
||||||
|
|
||||||
|
def test_options_on_multiple_rules(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
@app.route('/', methods=['GET', 'POST'])
|
||||||
|
def index():
|
||||||
|
return 'Hello World'
|
||||||
|
@app.route('/', methods=['PUT'])
|
||||||
|
def index_put():
|
||||||
|
return 'Aha!'
|
||||||
|
rv = app.test_client().open('/', method='OPTIONS')
|
||||||
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
|
||||||
|
|
||||||
def test_request_dispatching(self):
|
def test_request_dispatching(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
|
@ -123,7 +162,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert c.get('/').data == 'GET'
|
assert c.get('/').data == 'GET'
|
||||||
rv = c.post('/')
|
rv = c.post('/')
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
|
||||||
rv = c.head('/')
|
rv = c.head('/')
|
||||||
assert rv.status_code == 200
|
assert rv.status_code == 200
|
||||||
assert not rv.data # head truncates
|
assert not rv.data # head truncates
|
||||||
|
|
@ -131,7 +170,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert c.get('/more').data == 'GET'
|
assert c.get('/more').data == 'GET'
|
||||||
rv = c.delete('/more')
|
rv = c.delete('/more')
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
|
||||||
|
|
||||||
def test_url_mapping(self):
|
def test_url_mapping(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
@ -147,7 +186,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert c.get('/').data == 'GET'
|
assert c.get('/').data == 'GET'
|
||||||
rv = c.post('/')
|
rv = c.post('/')
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
|
||||||
rv = c.head('/')
|
rv = c.head('/')
|
||||||
assert rv.status_code == 200
|
assert rv.status_code == 200
|
||||||
assert not rv.data # head truncates
|
assert not rv.data # head truncates
|
||||||
|
|
@ -155,7 +194,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert c.get('/more').data == 'GET'
|
assert c.get('/more').data == 'GET'
|
||||||
rv = c.delete('/more')
|
rv = c.delete('/more')
|
||||||
assert rv.status_code == 405
|
assert rv.status_code == 405
|
||||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
|
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
|
||||||
|
|
||||||
def test_session(self):
|
def test_session(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
@ -172,6 +211,20 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert c.post('/set', data={'value': '42'}).data == 'value set'
|
assert c.post('/set', data={'value': '42'}).data == 'value set'
|
||||||
assert c.get('/get').data == '42'
|
assert c.get('/get').data == '42'
|
||||||
|
|
||||||
|
def test_session_using_server_name(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config.update(
|
||||||
|
SECRET_KEY='foo',
|
||||||
|
SERVER_NAME='example.com'
|
||||||
|
)
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
flask.session['testing'] = 42
|
||||||
|
return 'Hello World'
|
||||||
|
rv = app.test_client().get('/', 'http://example.com/')
|
||||||
|
assert 'domain=.example.com' in rv.headers['set-cookie'].lower()
|
||||||
|
assert 'httponly' in rv.headers['set-cookie'].lower()
|
||||||
|
|
||||||
def test_missing_session(self):
|
def test_missing_session(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
def expect_exception(f, *args, **kwargs):
|
def expect_exception(f, *args, **kwargs):
|
||||||
|
|
@ -195,7 +248,13 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
flask.session['test'] = 42
|
flask.session['test'] = 42
|
||||||
flask.session.permanent = permanent
|
flask.session.permanent = permanent
|
||||||
return ''
|
return ''
|
||||||
rv = app.test_client().get('/')
|
|
||||||
|
@app.route('/test')
|
||||||
|
def test():
|
||||||
|
return unicode(flask.session.permanent)
|
||||||
|
|
||||||
|
client = app.test_client()
|
||||||
|
rv = client.get('/')
|
||||||
assert 'set-cookie' in rv.headers
|
assert 'set-cookie' in rv.headers
|
||||||
match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
|
match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
|
||||||
expires = parse_date(match.group())
|
expires = parse_date(match.group())
|
||||||
|
|
@ -204,6 +263,9 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert expires.month == expected.month
|
assert expires.month == expected.month
|
||||||
assert expires.day == expected.day
|
assert expires.day == expected.day
|
||||||
|
|
||||||
|
rv = client.get('/test')
|
||||||
|
assert rv.data == 'True'
|
||||||
|
|
||||||
permanent = False
|
permanent = False
|
||||||
rv = app.test_client().get('/')
|
rv = app.test_client().get('/')
|
||||||
assert 'set-cookie' in rv.headers
|
assert 'set-cookie' in rv.headers
|
||||||
|
|
@ -303,6 +365,30 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert 'Internal Server Error' in rv.data
|
assert 'Internal Server Error' in rv.data
|
||||||
assert len(called) == 1
|
assert len(called) == 1
|
||||||
|
|
||||||
|
def test_before_after_request_order(self):
|
||||||
|
called = []
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
@app.before_request
|
||||||
|
def before1():
|
||||||
|
called.append(1)
|
||||||
|
@app.before_request
|
||||||
|
def before2():
|
||||||
|
called.append(2)
|
||||||
|
@app.after_request
|
||||||
|
def after1(response):
|
||||||
|
called.append(4)
|
||||||
|
return response
|
||||||
|
@app.after_request
|
||||||
|
def after2(response):
|
||||||
|
called.append(3)
|
||||||
|
return response
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return '42'
|
||||||
|
rv = app.test_client().get('/')
|
||||||
|
assert rv.data == '42'
|
||||||
|
assert called == [1, 2, 3, 4]
|
||||||
|
|
||||||
def test_error_handling(self):
|
def test_error_handling(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
|
|
@ -345,6 +431,24 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
assert rv.status_code == 400
|
assert rv.status_code == 400
|
||||||
assert rv.mimetype == 'text/plain'
|
assert rv.mimetype == 'text/plain'
|
||||||
|
|
||||||
|
def test_make_response(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
with app.test_request_context():
|
||||||
|
rv = flask.make_response()
|
||||||
|
assert rv.status_code == 200
|
||||||
|
assert rv.data == ''
|
||||||
|
assert rv.mimetype == 'text/html'
|
||||||
|
|
||||||
|
rv = flask.make_response('Awesome')
|
||||||
|
assert rv.status_code == 200
|
||||||
|
assert rv.data == 'Awesome'
|
||||||
|
assert rv.mimetype == 'text/html'
|
||||||
|
|
||||||
|
rv = flask.make_response('W00t', 404)
|
||||||
|
assert rv.status_code == 404
|
||||||
|
assert rv.data == 'W00t'
|
||||||
|
assert rv.mimetype == 'text/html'
|
||||||
|
|
||||||
def test_url_generation(self):
|
def test_url_generation(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
@app.route('/hello/<name>', methods=['POST'])
|
@app.route('/hello/<name>', methods=['POST'])
|
||||||
|
|
@ -393,6 +497,10 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
assert "Expected ValueError"
|
assert "Expected ValueError"
|
||||||
|
|
||||||
|
def test_request_locals(self):
|
||||||
|
self.assertEqual(repr(flask.g), '<LocalProxy unbound>')
|
||||||
|
self.assertFalse(flask.g)
|
||||||
|
|
||||||
|
|
||||||
class JSONTestCase(unittest.TestCase):
|
class JSONTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
@ -430,6 +538,21 @@ class JSONTestCase(unittest.TestCase):
|
||||||
rv = render('{{ "<\0/script>"|tojson|safe }}')
|
rv = render('{{ "<\0/script>"|tojson|safe }}')
|
||||||
assert rv == '"<\\u0000\\/script>"'
|
assert 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'))
|
||||||
|
assert rv.status_code == 200
|
||||||
|
assert rv.data == u'정상처리'.encode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
class TemplatingTestCase(unittest.TestCase):
|
class TemplatingTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
@ -444,6 +567,30 @@ class TemplatingTestCase(unittest.TestCase):
|
||||||
rv = app.test_client().get('/')
|
rv = app.test_client().get('/')
|
||||||
assert rv.data == '<p>23|42'
|
assert 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('/')
|
||||||
|
assert 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')
|
||||||
|
assert rv.data.split() == ['42', '23', 'False', 'aha']
|
||||||
|
|
||||||
def test_escaping(self):
|
def test_escaping(self):
|
||||||
text = '<p>Hello World!'
|
text = '<p>Hello World!'
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
@ -461,6 +608,14 @@ class TemplatingTestCase(unittest.TestCase):
|
||||||
'<p>Hello World!'
|
'<p>Hello World!'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def test_no_escaping(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
with app.test_request_context():
|
||||||
|
assert flask.render_template_string('{{ foo }}',
|
||||||
|
foo='<test>') == '<test>'
|
||||||
|
assert flask.render_template('mail.txt', foo='<test>') \
|
||||||
|
== '<test> Mail'
|
||||||
|
|
||||||
def test_macros(self):
|
def test_macros(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
|
|
@ -514,13 +669,13 @@ class ModuleTestCase(unittest.TestCase):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
admin = flask.Module(__name__, 'admin', url_prefix='/admin')
|
admin = flask.Module(__name__, 'admin', url_prefix='/admin')
|
||||||
@admin.route('/')
|
@admin.route('/')
|
||||||
def index():
|
def admin_index():
|
||||||
return 'admin index'
|
return 'admin index'
|
||||||
@admin.route('/login')
|
@admin.route('/login')
|
||||||
def login():
|
def admin_login():
|
||||||
return 'admin login'
|
return 'admin login'
|
||||||
@admin.route('/logout')
|
@admin.route('/logout')
|
||||||
def logout():
|
def admin_logout():
|
||||||
return 'admin logout'
|
return 'admin logout'
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
|
|
@ -532,6 +687,18 @@ class ModuleTestCase(unittest.TestCase):
|
||||||
assert c.get('/admin/login').data == 'admin login'
|
assert c.get('/admin/login').data == 'admin login'
|
||||||
assert c.get('/admin/logout').data == 'admin logout'
|
assert c.get('/admin/logout').data == 'admin logout'
|
||||||
|
|
||||||
|
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('/')
|
||||||
|
assert rv.data == 'Awesome'
|
||||||
|
with app.test_request_context():
|
||||||
|
assert flask.url_for('frontend.index') == '/'
|
||||||
|
|
||||||
def test_request_processing(self):
|
def test_request_processing(self):
|
||||||
catched = []
|
catched = []
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
@ -544,7 +711,7 @@ class ModuleTestCase(unittest.TestCase):
|
||||||
catched.append('after-admin')
|
catched.append('after-admin')
|
||||||
return response
|
return response
|
||||||
@admin.route('/')
|
@admin.route('/')
|
||||||
def index():
|
def admin_index():
|
||||||
return 'the admin'
|
return 'the admin'
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def before_request():
|
def before_request():
|
||||||
|
|
@ -583,7 +750,7 @@ class ModuleTestCase(unittest.TestCase):
|
||||||
def index():
|
def index():
|
||||||
return flask.render_template_string('{{ a }}{{ b }}{{ c }}')
|
return flask.render_template_string('{{ a }}{{ b }}{{ c }}')
|
||||||
@admin.route('/')
|
@admin.route('/')
|
||||||
def index():
|
def admin_index():
|
||||||
return flask.render_template_string('{{ a }}{{ b }}{{ c }}')
|
return flask.render_template_string('{{ a }}{{ b }}{{ c }}')
|
||||||
app.register_module(admin)
|
app.register_module(admin)
|
||||||
c = app.test_client()
|
c = app.test_client()
|
||||||
|
|
@ -623,6 +790,55 @@ class ModuleTestCase(unittest.TestCase):
|
||||||
assert rv.status_code == 500
|
assert rv.status_code == 500
|
||||||
assert 'internal server error' == rv.data
|
assert 'internal server error' == rv.data
|
||||||
|
|
||||||
|
def test_templates_and_static(self):
|
||||||
|
from moduleapp import app
|
||||||
|
c = app.test_client()
|
||||||
|
|
||||||
|
rv = c.get('/')
|
||||||
|
assert rv.data == 'Hello from the Frontend'
|
||||||
|
rv = c.get('/admin/')
|
||||||
|
assert rv.data == 'Hello from the Admin'
|
||||||
|
rv = c.get('/admin/index2')
|
||||||
|
assert rv.data == 'Hello from the Admin'
|
||||||
|
rv = c.get('/admin/static/test.txt')
|
||||||
|
assert rv.data.strip() == 'Admin File'
|
||||||
|
rv = c.get('/admin/static/css/test.css')
|
||||||
|
assert rv.data.strip() == '/* nested file */'
|
||||||
|
|
||||||
|
with app.test_request_context():
|
||||||
|
assert 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:
|
||||||
|
assert e.name == 'missing.html'
|
||||||
|
else:
|
||||||
|
assert 0, 'expected exception'
|
||||||
|
|
||||||
|
with flask.Flask(__name__).test_request_context():
|
||||||
|
assert flask.render_template('nested/nested.txt') == 'I\'m nested'
|
||||||
|
|
||||||
|
def test_safe_access(self):
|
||||||
|
from moduleapp import app
|
||||||
|
|
||||||
|
with app.test_request_context():
|
||||||
|
f = app.view_functions['admin.static']
|
||||||
|
|
||||||
|
try:
|
||||||
|
f('/etc/passwd')
|
||||||
|
except NotFound:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
assert 0, 'expected exception'
|
||||||
|
try:
|
||||||
|
f('../__init__.py')
|
||||||
|
except NotFound:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
assert 0, 'expected exception'
|
||||||
|
|
||||||
|
|
||||||
class SendfileTestCase(unittest.TestCase):
|
class SendfileTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
@ -648,46 +864,64 @@ class SendfileTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_send_file_object(self):
|
def test_send_file_object(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
with app.test_request_context():
|
with catch_warnings() as captured:
|
||||||
f = open(os.path.join(app.root_path, 'static/index.html'))
|
with app.test_request_context():
|
||||||
rv = flask.send_file(f)
|
f = open(os.path.join(app.root_path, 'static/index.html'))
|
||||||
with app.open_resource('static/index.html') as f:
|
rv = flask.send_file(f)
|
||||||
assert rv.data == f.read()
|
with app.open_resource('static/index.html') as f:
|
||||||
assert rv.mimetype == 'text/html'
|
assert rv.data == f.read()
|
||||||
|
assert rv.mimetype == 'text/html'
|
||||||
|
# mimetypes + etag
|
||||||
|
assert len(captured) == 2
|
||||||
|
|
||||||
app.use_x_sendfile = True
|
app.use_x_sendfile = True
|
||||||
with app.test_request_context():
|
with catch_warnings() as captured:
|
||||||
f = open(os.path.join(app.root_path, 'static/index.html'))
|
with app.test_request_context():
|
||||||
rv = flask.send_file(f)
|
f = open(os.path.join(app.root_path, 'static/index.html'))
|
||||||
assert rv.mimetype == 'text/html'
|
rv = flask.send_file(f)
|
||||||
assert 'x-sendfile' in rv.headers
|
assert rv.mimetype == 'text/html'
|
||||||
assert rv.headers['x-sendfile'] == \
|
assert 'x-sendfile' in rv.headers
|
||||||
os.path.join(app.root_path, 'static/index.html')
|
assert rv.headers['x-sendfile'] == \
|
||||||
|
os.path.join(app.root_path, 'static/index.html')
|
||||||
|
# mimetypes + etag
|
||||||
|
assert len(captured) == 2
|
||||||
|
|
||||||
app.use_x_sendfile = False
|
app.use_x_sendfile = False
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
f = StringIO('Test')
|
with catch_warnings() as captured:
|
||||||
rv = flask.send_file(f)
|
f = StringIO('Test')
|
||||||
assert rv.data == 'Test'
|
rv = flask.send_file(f)
|
||||||
assert rv.mimetype == 'application/octet-stream'
|
assert rv.data == 'Test'
|
||||||
f = StringIO('Test')
|
assert rv.mimetype == 'application/octet-stream'
|
||||||
rv = flask.send_file(f, mimetype='text/plain')
|
# etags
|
||||||
assert rv.data == 'Test'
|
assert len(captured) == 1
|
||||||
assert rv.mimetype == 'text/plain'
|
with catch_warnings() as captured:
|
||||||
|
f = StringIO('Test')
|
||||||
|
rv = flask.send_file(f, mimetype='text/plain')
|
||||||
|
assert rv.data == 'Test'
|
||||||
|
assert rv.mimetype == 'text/plain'
|
||||||
|
# etags
|
||||||
|
assert len(captured) == 1
|
||||||
|
|
||||||
app.use_x_sendfile = True
|
app.use_x_sendfile = True
|
||||||
with app.test_request_context():
|
with catch_warnings() as captured:
|
||||||
f = StringIO('Test')
|
with app.test_request_context():
|
||||||
rv = flask.send_file(f)
|
f = StringIO('Test')
|
||||||
assert 'x-sendfile' not in rv.headers
|
rv = flask.send_file(f)
|
||||||
|
assert 'x-sendfile' not in rv.headers
|
||||||
|
# etags
|
||||||
|
assert len(captured) == 1
|
||||||
|
|
||||||
def test_attachment(self):
|
def test_attachment(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
with app.test_request_context():
|
with catch_warnings() as captured:
|
||||||
f = open(os.path.join(app.root_path, 'static/index.html'))
|
with app.test_request_context():
|
||||||
rv = flask.send_file(f, as_attachment=True)
|
f = open(os.path.join(app.root_path, 'static/index.html'))
|
||||||
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
rv = flask.send_file(f, as_attachment=True)
|
||||||
assert value == 'attachment'
|
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
||||||
|
assert value == 'attachment'
|
||||||
|
# mimetypes + etag
|
||||||
|
assert len(captured) == 2
|
||||||
|
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
assert options['filename'] == 'index.html'
|
assert options['filename'] == 'index.html'
|
||||||
|
|
@ -698,7 +932,8 @@ class SendfileTestCase(unittest.TestCase):
|
||||||
|
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
rv = flask.send_file(StringIO('Test'), as_attachment=True,
|
rv = flask.send_file(StringIO('Test'), as_attachment=True,
|
||||||
attachment_filename='index.txt')
|
attachment_filename='index.txt',
|
||||||
|
add_etags=False)
|
||||||
assert rv.mimetype == 'text/plain'
|
assert rv.mimetype == 'text/plain'
|
||||||
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
||||||
assert value == 'attachment'
|
assert value == 'attachment'
|
||||||
|
|
@ -731,7 +966,7 @@ class LoggingTestCase(unittest.TestCase):
|
||||||
c = app.test_client()
|
c = app.test_client()
|
||||||
|
|
||||||
with catch_stderr() as err:
|
with catch_stderr() as err:
|
||||||
rv = c.get('/')
|
c.get('/')
|
||||||
out = err.getvalue()
|
out = err.getvalue()
|
||||||
assert 'WARNING in flask_tests [' in out
|
assert 'WARNING in flask_tests [' in out
|
||||||
assert 'flask_tests.py' in out
|
assert 'flask_tests.py' in out
|
||||||
|
|
@ -835,6 +1070,162 @@ class ConfigTestCase(unittest.TestCase):
|
||||||
finally:
|
finally:
|
||||||
os.environ = env
|
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)
|
||||||
|
assert msg.startswith('[Errno 2] Unable to load configuration '
|
||||||
|
'file (No such file or directory):')
|
||||||
|
assert msg.endswith("missing.cfg'")
|
||||||
|
else:
|
||||||
|
assert 0, 'expected config'
|
||||||
|
|
||||||
|
|
||||||
|
class SubdomainTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_basic_support(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config['SERVER_NAME'] = 'localhost'
|
||||||
|
@app.route('/')
|
||||||
|
def normal_index():
|
||||||
|
return 'normal index'
|
||||||
|
@app.route('/', subdomain='test')
|
||||||
|
def test_index():
|
||||||
|
return 'test index'
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
rv = c.get('/', 'http://localhost/')
|
||||||
|
assert rv.data == 'normal index'
|
||||||
|
|
||||||
|
rv = c.get('/', 'http://test.localhost/')
|
||||||
|
assert rv.data == 'test index'
|
||||||
|
|
||||||
|
def test_module_static_path_subdomain(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config['SERVER_NAME'] = 'example.com'
|
||||||
|
from subdomaintestmodule import mod
|
||||||
|
app.register_module(mod)
|
||||||
|
c = app.test_client()
|
||||||
|
rv = c.get('/static/hello.txt', 'http://foo.example.com/')
|
||||||
|
assert rv.data.strip() == 'Hello Subdomain'
|
||||||
|
|
||||||
|
def test_subdomain_matching(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.config['SERVER_NAME'] = 'localhost'
|
||||||
|
@app.route('/', subdomain='<user>')
|
||||||
|
def index(user):
|
||||||
|
return 'index for %s' % user
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
rv = c.get('/', 'http://mitsuhiko.localhost/')
|
||||||
|
assert rv.data == 'index for mitsuhiko'
|
||||||
|
|
||||||
|
def test_module_subdomain_support(self):
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
mod = flask.Module(__name__, 'test', subdomain='testing')
|
||||||
|
app.config['SERVER_NAME'] = 'localhost'
|
||||||
|
|
||||||
|
@mod.route('/test')
|
||||||
|
def test():
|
||||||
|
return 'Test'
|
||||||
|
|
||||||
|
@mod.route('/outside', subdomain='xtesting')
|
||||||
|
def bar():
|
||||||
|
return 'Outside'
|
||||||
|
|
||||||
|
app.register_module(mod)
|
||||||
|
|
||||||
|
c = app.test_client()
|
||||||
|
rv = c.get('/test', 'http://testing.localhost/')
|
||||||
|
assert rv.data == 'Test'
|
||||||
|
rv = c.get('/outside', 'http://xtesting.localhost/')
|
||||||
|
assert rv.data == 'Outside'
|
||||||
|
|
||||||
|
|
||||||
|
class TestSignals(unittest.TestCase):
|
||||||
|
|
||||||
|
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('/')
|
||||||
|
assert len(recorded) == 1
|
||||||
|
template, context = recorded[0]
|
||||||
|
assert template.name == 'simple_template.html'
|
||||||
|
assert 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):
|
||||||
|
assert 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('/')
|
||||||
|
assert rv.data == 'stuff'
|
||||||
|
|
||||||
|
assert 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:
|
||||||
|
assert app.test_client().get('/').status_code == 500
|
||||||
|
assert len(recorded) == 1
|
||||||
|
assert isinstance(recorded[0], ZeroDivisionError)
|
||||||
|
finally:
|
||||||
|
flask.got_request_exception.disconnect(record, app)
|
||||||
|
|
||||||
|
|
||||||
def suite():
|
def suite():
|
||||||
from minitwit_tests import MiniTwitTestCase
|
from minitwit_tests import MiniTwitTestCase
|
||||||
|
|
@ -847,8 +1238,11 @@ def suite():
|
||||||
suite.addTest(unittest.makeSuite(SendfileTestCase))
|
suite.addTest(unittest.makeSuite(SendfileTestCase))
|
||||||
suite.addTest(unittest.makeSuite(LoggingTestCase))
|
suite.addTest(unittest.makeSuite(LoggingTestCase))
|
||||||
suite.addTest(unittest.makeSuite(ConfigTestCase))
|
suite.addTest(unittest.makeSuite(ConfigTestCase))
|
||||||
|
suite.addTest(unittest.makeSuite(SubdomainTestCase))
|
||||||
if flask.json_available:
|
if flask.json_available:
|
||||||
suite.addTest(unittest.makeSuite(JSONTestCase))
|
suite.addTest(unittest.makeSuite(JSONTestCase))
|
||||||
|
if flask.signals_available:
|
||||||
|
suite.addTest(unittest.makeSuite(TestSignals))
|
||||||
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
|
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
|
||||||
suite.addTest(unittest.makeSuite(FlaskrTestCase))
|
suite.addTest(unittest.makeSuite(FlaskrTestCase))
|
||||||
return suite
|
return suite
|
||||||
|
|
|
||||||
310
tests/flaskext_test.py
Normal file
310
tests/flaskext_test.py
Normal file
|
|
@ -0,0 +1,310 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Flask Extension Tests
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tests the Flask extensions.
|
||||||
|
|
||||||
|
:copyright: (c) 2010 by Ali Afshar.
|
||||||
|
:license: BSD, see LICENSE for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import urllib2
|
||||||
|
import tempfile
|
||||||
|
import subprocess
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from flask import json
|
||||||
|
|
||||||
|
from setuptools.package_index import PackageIndex
|
||||||
|
from setuptools.archive_util import unpack_archive
|
||||||
|
|
||||||
|
flask_svc_url = 'http://flask.pocoo.org/extensions/'
|
||||||
|
|
||||||
|
|
||||||
|
# OS X has awful paths when using mkstemp or gettempdir(). I don't
|
||||||
|
# care about security or clashes here, so pick something that is
|
||||||
|
# actually rememberable.
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
_tempdir = '/private/tmp'
|
||||||
|
else:
|
||||||
|
_tempdir = tempfile.gettempdir()
|
||||||
|
tdir = _tempdir + '/flaskext-test'
|
||||||
|
flaskdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
|
|
||||||
|
# virtualenv hack *cough*
|
||||||
|
os.environ['PYTHONDONTWRITEBYTECODE'] = ''
|
||||||
|
|
||||||
|
|
||||||
|
RESULT_TEMPATE = u'''\
|
||||||
|
<!doctype html>
|
||||||
|
<title>Flask-Extension Test Results</title>
|
||||||
|
<style type=text/css>
|
||||||
|
body { font-family: 'Georgia', serif; font-size: 17px; color: #000; }
|
||||||
|
a { color: #004B6B; }
|
||||||
|
a:hover { color: #6D4100; }
|
||||||
|
h1, h2, h3 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; }
|
||||||
|
h1 { font-size: 30px; margin: 15px 0 5px 0; }
|
||||||
|
h2 { font-size: 24px; margin: 15px 0 5px 0; }
|
||||||
|
h3 { font-size: 19px; margin: 15px 0 5px 0; }
|
||||||
|
textarea, code,
|
||||||
|
pre { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono',
|
||||||
|
'Bitstream Vera Sans Mono', monospace!important; font-size: 15px;
|
||||||
|
background: #eee; }
|
||||||
|
pre { padding: 7px 15px; line-height: 1.3; }
|
||||||
|
p { line-height: 1.4; }
|
||||||
|
table { border: 1px solid black; border-collapse: collapse;
|
||||||
|
margin: 15px 0; }
|
||||||
|
td, th { border: 1px solid black; padding: 4px 10px;
|
||||||
|
text-align: left; }
|
||||||
|
th { background: #eee; font-weight: normal; }
|
||||||
|
tr.success { background: #D3F5CC; }
|
||||||
|
tr.failed { background: #F5D2CB; }
|
||||||
|
</style>
|
||||||
|
<h1>Flask-Extension Test Results</h1>
|
||||||
|
<p>
|
||||||
|
This page contains the detailed test results for the test run of
|
||||||
|
all {{ 'approved' if approved }} Flask extensions.
|
||||||
|
<h2>Summary</h2>
|
||||||
|
<table class=results>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Extension
|
||||||
|
<th>Version
|
||||||
|
<th>Author
|
||||||
|
<th>License
|
||||||
|
<th>Outcome
|
||||||
|
{%- for iptr, _ in results[0].logs|dictsort %}
|
||||||
|
<th>{{ iptr }}
|
||||||
|
{%- endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for result in results %}
|
||||||
|
{% set outcome = 'success' if result.success else 'failed' %}
|
||||||
|
<tr class={{ outcome }}>
|
||||||
|
<th>{{ result.name }}
|
||||||
|
<td>{{ result.version }}
|
||||||
|
<td>{{ result.author }}
|
||||||
|
<td>{{ result.license }}
|
||||||
|
<td>{{ outcome }}
|
||||||
|
{%- for iptr, _ in result.logs|dictsort %}
|
||||||
|
<td><a href="#{{ result.name }}-{{ iptr }}">see log</a>
|
||||||
|
{%- endfor %}
|
||||||
|
</tr>
|
||||||
|
{%- endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h2>Test Logs</h2>
|
||||||
|
<p>Detailed test logs for all tests on all platforms:
|
||||||
|
{%- for result in results %}
|
||||||
|
{%- for iptr, log in result.logs|dictsort %}
|
||||||
|
<h3 id="{{ result.name }}-{{ iptr }}">
|
||||||
|
{{ result.name }} - {{ result.version }} [{{ iptr }}]</h3>
|
||||||
|
<pre>{{ log }}</pre>
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endfor %}
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg, *args):
|
||||||
|
print '[EXTTEST]', msg % args
|
||||||
|
|
||||||
|
|
||||||
|
class TestResult(object):
|
||||||
|
|
||||||
|
def __init__(self, name, folder, statuscode, interpreters):
|
||||||
|
intrptr = os.path.join(folder, '.tox/%s/bin/python'
|
||||||
|
% interpreters[0])
|
||||||
|
self.statuscode = statuscode
|
||||||
|
self.folder = folder
|
||||||
|
self.success = statuscode == 0
|
||||||
|
|
||||||
|
def fetch(field):
|
||||||
|
try:
|
||||||
|
c = subprocess.Popen([intrptr, 'setup.py',
|
||||||
|
'--' + field], cwd=folder,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
return c.communicate()[0].strip()
|
||||||
|
except OSError:
|
||||||
|
return '?'
|
||||||
|
self.name = name
|
||||||
|
self.license = fetch('license')
|
||||||
|
self.author = fetch('author')
|
||||||
|
self.version = fetch('version')
|
||||||
|
|
||||||
|
self.logs = {}
|
||||||
|
for interpreter in interpreters:
|
||||||
|
logfile = os.path.join(folder, '.tox/%s/log/test.log'
|
||||||
|
% interpreter)
|
||||||
|
if os.path.isfile(logfile):
|
||||||
|
self.logs[interpreter] = open(logfile).read()
|
||||||
|
else:
|
||||||
|
self.logs[interpreter] = ''
|
||||||
|
|
||||||
|
|
||||||
|
def create_tdir():
|
||||||
|
try:
|
||||||
|
shutil.rmtree(tdir)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
os.mkdir(tdir)
|
||||||
|
|
||||||
|
|
||||||
|
def package_flask():
|
||||||
|
distfolder = tdir + '/.flask-dist'
|
||||||
|
c = subprocess.Popen(['python', 'setup.py', 'sdist', '--formats=gztar',
|
||||||
|
'--dist', distfolder], cwd=flaskdir)
|
||||||
|
c.wait()
|
||||||
|
return os.path.join(distfolder, os.listdir(distfolder)[0])
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_command(checkout_dir):
|
||||||
|
if os.path.isfile(checkout_dir + '/Makefile'):
|
||||||
|
return 'make test'
|
||||||
|
return 'python setup.py test'
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_extensions_list():
|
||||||
|
req = urllib2.Request(flask_svc_url, headers={'accept':'application/json'})
|
||||||
|
d = urllib2.urlopen(req).read()
|
||||||
|
data = json.loads(d)
|
||||||
|
for ext in data['extensions']:
|
||||||
|
yield ext
|
||||||
|
|
||||||
|
|
||||||
|
def checkout_extension(name):
|
||||||
|
log('Downloading extension %s to temporary folder', name)
|
||||||
|
root = os.path.join(tdir, name)
|
||||||
|
os.mkdir(root)
|
||||||
|
checkout_path = PackageIndex().download(name, root)
|
||||||
|
|
||||||
|
unpack_archive(checkout_path, root)
|
||||||
|
path = None
|
||||||
|
for fn in os.listdir(root):
|
||||||
|
path = os.path.join(root, fn)
|
||||||
|
if os.path.isdir(path):
|
||||||
|
break
|
||||||
|
log('Downloaded to %s', path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
tox_template = """[tox]
|
||||||
|
envlist=%(env)s
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
deps=
|
||||||
|
%(deps)s
|
||||||
|
py
|
||||||
|
commands=bash flaskext-runtest.sh {envlogdir}/test.log
|
||||||
|
downloadcache=%(cache)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def create_tox_ini(checkout_path, interpreters, flask_dep):
|
||||||
|
tox_path = os.path.join(checkout_path, 'tox-flask-test.ini')
|
||||||
|
if not os.path.exists(tox_path):
|
||||||
|
with open(tox_path, 'w') as f:
|
||||||
|
f.write(tox_template % {
|
||||||
|
'env': ','.join(interpreters),
|
||||||
|
'cache': tdir,
|
||||||
|
'deps': flask_dep
|
||||||
|
})
|
||||||
|
return tox_path
|
||||||
|
|
||||||
|
|
||||||
|
def iter_extensions(only_approved=True):
|
||||||
|
for ext in fetch_extensions_list():
|
||||||
|
if ext['approved'] or not only_approved:
|
||||||
|
yield ext['name']
|
||||||
|
|
||||||
|
|
||||||
|
def test_extension(name, interpreters, flask_dep):
|
||||||
|
checkout_path = checkout_extension(name)
|
||||||
|
log('Running tests with tox in %s', checkout_path)
|
||||||
|
|
||||||
|
# figure out the test command and write a wrapper script. We
|
||||||
|
# can't write that directly into the tox ini because tox does
|
||||||
|
# not invoke the command from the shell so we have no chance
|
||||||
|
# to pipe the output into a logfile. The /dev/null hack is
|
||||||
|
# to trick py.test (if used) into not guessing widths from the
|
||||||
|
# invoking terminal.
|
||||||
|
test_command = get_test_command(checkout_path)
|
||||||
|
log('Test command: %s', test_command)
|
||||||
|
f = open(checkout_path + '/flaskext-runtest.sh', 'w')
|
||||||
|
f.write(test_command + ' &> "$1" < /dev/null\n')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
# if there is a tox.ini, remove it, it will cause troubles
|
||||||
|
# for us. Remove it if present, we are running tox ourselves
|
||||||
|
# afterall.
|
||||||
|
|
||||||
|
create_tox_ini(checkout_path, interpreters, flask_dep)
|
||||||
|
rv = subprocess.call(['tox', '-c', 'tox-flask-test.ini'], cwd=checkout_path)
|
||||||
|
return TestResult(name, checkout_path, rv, interpreters)
|
||||||
|
|
||||||
|
|
||||||
|
def run_tests(extensions, interpreters):
|
||||||
|
results = {}
|
||||||
|
create_tdir()
|
||||||
|
log('Packaging Flask')
|
||||||
|
flask_dep = package_flask()
|
||||||
|
log('Running extension tests')
|
||||||
|
log('Temporary Environment: %s', tdir)
|
||||||
|
for name in extensions:
|
||||||
|
log('Testing %s', name)
|
||||||
|
result = test_extension(name, interpreters, flask_dep)
|
||||||
|
if result.success:
|
||||||
|
log('Extension test succeeded')
|
||||||
|
else:
|
||||||
|
log('Extension test failed')
|
||||||
|
results[name] = result
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def render_results(results, approved):
|
||||||
|
from jinja2 import Template
|
||||||
|
items = results.values()
|
||||||
|
items.sort(key=lambda x: x.name.lower())
|
||||||
|
rv = Template(RESULT_TEMPATE, autoescape=True).render(results=items,
|
||||||
|
approved=approved)
|
||||||
|
fd, filename = tempfile.mkstemp(suffix='.html')
|
||||||
|
os.fdopen(fd, 'w').write(rv.encode('utf-8') + '\n')
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='Runs Flask extension tests')
|
||||||
|
parser.add_argument('--all', dest='all', action='store_true',
|
||||||
|
help='run against all extensions, not just approved')
|
||||||
|
parser.add_argument('--browse', dest='browse', action='store_true',
|
||||||
|
help='show browser with the result summary')
|
||||||
|
parser.add_argument('--env', dest='env', default='py25,py26,py27',
|
||||||
|
help='the tox environments to run against')
|
||||||
|
parser.add_argument('--extension=', dest='extension', default=None,
|
||||||
|
help='tests a single extension')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.extension is not None:
|
||||||
|
only_approved = False
|
||||||
|
extensions = [args.extension]
|
||||||
|
else:
|
||||||
|
only_approved = not args.all
|
||||||
|
extensions = iter_extensions(only_approved)
|
||||||
|
|
||||||
|
results = run_tests(extensions, [x.strip() for x in args.env.split(',')])
|
||||||
|
filename = render_results(results, only_approved)
|
||||||
|
if args.browse:
|
||||||
|
import webbrowser
|
||||||
|
webbrowser.open('file:///' + filename.lstrip('/'))
|
||||||
|
print 'Results written to', filename
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
7
tests/moduleapp/__init__.py
Normal file
7
tests/moduleapp/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
from moduleapp.apps.admin import admin
|
||||||
|
from moduleapp.apps.frontend import frontend
|
||||||
|
app.register_module(admin)
|
||||||
|
app.register_module(frontend)
|
||||||
0
tests/moduleapp/apps/__init__.py
Normal file
0
tests/moduleapp/apps/__init__.py
Normal file
14
tests/moduleapp/apps/admin/__init__.py
Normal file
14
tests/moduleapp/apps/admin/__init__.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
from flask import Module, render_template
|
||||||
|
|
||||||
|
|
||||||
|
admin = Module(__name__, url_prefix='/admin')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('admin/index.html')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.route('/index2')
|
||||||
|
def index2():
|
||||||
|
return render_template('./admin/index.html')
|
||||||
1
tests/moduleapp/apps/admin/static/css/test.css
Normal file
1
tests/moduleapp/apps/admin/static/css/test.css
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/* nested file */
|
||||||
1
tests/moduleapp/apps/admin/static/test.txt
Normal file
1
tests/moduleapp/apps/admin/static/test.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Admin File
|
||||||
1
tests/moduleapp/apps/admin/templates/index.html
Normal file
1
tests/moduleapp/apps/admin/templates/index.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Hello from the Admin
|
||||||
9
tests/moduleapp/apps/frontend/__init__.py
Normal file
9
tests/moduleapp/apps/frontend/__init__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from flask import Module, render_template
|
||||||
|
|
||||||
|
|
||||||
|
frontend = Module(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@frontend.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('frontend/index.html')
|
||||||
1
tests/moduleapp/apps/frontend/templates/index.html
Normal file
1
tests/moduleapp/apps/frontend/templates/index.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Hello from the Frontend
|
||||||
4
tests/subdomaintestmodule/__init__.py
Normal file
4
tests/subdomaintestmodule/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
from flask import Module
|
||||||
|
|
||||||
|
|
||||||
|
mod = Module(__name__, 'foo', subdomain='foo')
|
||||||
1
tests/subdomaintestmodule/static/hello.txt
Normal file
1
tests/subdomaintestmodule/static/hello.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Hello Subdomain
|
||||||
1
tests/templates/mail.txt
Normal file
1
tests/templates/mail.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{{ foo}} Mail
|
||||||
1
tests/templates/nested/nested.txt
Normal file
1
tests/templates/nested/nested.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
I'm nested
|
||||||
1
tests/templates/simple_template.html
Normal file
1
tests/templates/simple_template.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<h1>{{ whiskey }}</h1>
|
||||||
5
tox.ini
Normal file
5
tox.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
[tox]
|
||||||
|
envlist=py25,py26,py27
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
commands=make test
|
||||||
Loading…
Add table
Add a link
Reference in a new issue