Merge remote branch 'mitsuhiko/master'

This commit is contained in:
Nick Walker 2010-10-11 19:09:06 -07:00
commit 38262ccac5
84 changed files with 5584 additions and 2003 deletions

3
.gitignore vendored
View file

@ -2,6 +2,9 @@
*.pyc *.pyc
*.pyo *.pyo
env env
env*
dist dist
*.egg
*.egg-info *.egg-info
_mailinglist _mailinglist
.tox

View file

@ -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
View file

@ -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

View file

@ -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 *

View file

@ -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

View file

@ -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

@ -0,0 +1 @@
Subproject commit 3d964b660442e23faedf801caed6e3c7bd42d5c9

View file

@ -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)

View file

@ -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.

View file

@ -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 = {}

View file

@ -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/

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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
----------------------- -----------------------

View file

@ -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

View file

@ -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:

View file

@ -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.

View file

@ -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 ``"``).

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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
------------------ ------------------

View file

@ -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
View 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/

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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:

View 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
View 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/>`_.

View file

@ -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.

View file

@ -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:

View file

@ -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>`_.

View file

@ -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

View file

@ -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

View file

@ -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'&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags() >>> Markup('<em>Marked up</em> &raquo; 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)

View file

@ -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
View 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
View 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
View 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.

View file

@ -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__)

View file

@ -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.

View file

@ -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.

View file

@ -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.
:: ::

View file

@ -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.

View file

@ -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
----------- -----------

View file

@ -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
View 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
View 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
View 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.

1519
flask.py

File diff suppressed because it is too large Load diff

34
flask/__init__.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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'

View file

@ -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'
) )

View file

@ -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
View 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()

View 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)

View file

View 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')

View file

@ -0,0 +1 @@
/* nested file */

View file

@ -0,0 +1 @@
Admin File

View file

@ -0,0 +1 @@
Hello from the Admin

View file

@ -0,0 +1,9 @@
from flask import Module, render_template
frontend = Module(__name__)
@frontend.route('/')
def index():
return render_template('frontend/index.html')

View file

@ -0,0 +1 @@
Hello from the Frontend

View file

@ -0,0 +1,4 @@
from flask import Module
mod = Module(__name__, 'foo', subdomain='foo')

View file

@ -0,0 +1 @@
Hello Subdomain

1
tests/templates/mail.txt Normal file
View file

@ -0,0 +1 @@
{{ foo}} Mail

View file

@ -0,0 +1 @@
I'm nested

View file

@ -0,0 +1 @@
<h1>{{ whiskey }}</h1>

5
tox.ini Normal file
View file

@ -0,0 +1,5 @@
[tox]
envlist=py25,py26,py27
[testenv]
commands=make test