Merge branch 'master' of http://github.com/mitsuhiko/flask
This commit is contained in:
commit
95f162a4d8
77 changed files with 5657 additions and 1754 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,6 +2,8 @@
|
|||
*.pyc
|
||||
*.pyo
|
||||
env
|
||||
env*
|
||||
dist
|
||||
*.egg
|
||||
*.egg-info
|
||||
_mailinglist
|
||||
|
|
|
|||
15
AUTHORS
15
AUTHORS
|
|
@ -9,6 +9,21 @@ Development Lead
|
|||
Patches and Suggestions
|
||||
```````````````````````
|
||||
|
||||
- Adam Zapletal
|
||||
- Ali Afshar
|
||||
- Chris Edgemon
|
||||
- Chris Grindstaff
|
||||
- Christopher Grebs
|
||||
- Florent Xicluna
|
||||
- Georg Brandl
|
||||
- Justin Quick
|
||||
- Kenneth Reitz
|
||||
- Marian Sigler
|
||||
- Matt Campell
|
||||
- Matthew Frazier
|
||||
- Ron DuPlain
|
||||
- Sebastien Estienne
|
||||
- Simon Sapin
|
||||
- Stephane Wirtel
|
||||
- Thomas Schranz
|
||||
- Zhao Xiaohong
|
||||
|
|
|
|||
98
CHANGES
98
CHANGES
|
|
@ -3,15 +3,105 @@ Flask Changelog
|
|||
|
||||
Here you can see the full list of changes between each Flask release.
|
||||
|
||||
Version 0.6
|
||||
-----------
|
||||
|
||||
Release date to be announced, codename to be decided.
|
||||
|
||||
- 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
|
||||
-----------
|
||||
|
||||
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
|
||||
-----------
|
||||
|
||||
Release date to be announced, codename to be selected.
|
||||
Released on June 18th 2010, codename Rakia
|
||||
|
||||
- added the ability to register application wide error handlers
|
||||
from modules.
|
||||
- :meth:`~flask.Flask.after_request` handlers are now also invoked
|
||||
if the request dies with an exception and an error handling page
|
||||
kicks in.
|
||||
- test client has not the ability to preserve the request context
|
||||
for a little longer. This can also be used to trigger custom
|
||||
requests that do not pop the request stack for testing.
|
||||
- because the Python standard library caches loggers, the name of
|
||||
the logger is configurable now to better support unittests.
|
||||
- added `TESTING` switch that can activate unittesting helpers.
|
||||
- the logger switches to `DEBUG` mode now if debug is enabled.
|
||||
|
||||
Version 0.3.1
|
||||
-------------
|
||||
|
||||
Bugfix release, released May 28th
|
||||
Bugfix release, released on May 28th 2010
|
||||
|
||||
- fixed a error reporting bug with :meth:`flask.Config.from_envvar`
|
||||
- removed some unused code from flask
|
||||
|
|
@ -22,7 +112,7 @@ Bugfix release, released May 28th
|
|||
Version 0.3
|
||||
-----------
|
||||
|
||||
Released on May 28th, codename Schnaps
|
||||
Released on May 28th 2010, codename Schnaps
|
||||
|
||||
- added support for categories for flashed messages.
|
||||
- the application now configures a :class:`logging.Handler` and will
|
||||
|
|
@ -38,7 +128,7 @@ Released on May 28th, codename Schnaps
|
|||
Version 0.2
|
||||
-----------
|
||||
|
||||
Released on May 12th, codename Jägermeister
|
||||
Released on May 12th 2010, codename Jägermeister
|
||||
|
||||
- various bugfixes
|
||||
- integrated JSON support
|
||||
|
|
|
|||
29
LICENSE
29
LICENSE
|
|
@ -3,9 +3,9 @@ for more details.
|
|||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
Redistribution and use in source and binary forms of the software as well
|
||||
as documentation, with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
|
@ -19,14 +19,15 @@ met:
|
|||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE 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 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
THIS SOFTWARE AND 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
|
||||
SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ recursive-include examples *
|
|||
recursive-include docs *
|
||||
recursive-exclude docs *.pyc
|
||||
recursive-exclude docs *.pyo
|
||||
recursive-exclude tests *.pyc
|
||||
recursive-exclude tests *.pyo
|
||||
recursive-exclude examples *.pyc
|
||||
recursive-exclude examples *.pyo
|
||||
prune docs/_build
|
||||
|
|
|
|||
7
Makefile
7
Makefile
|
|
@ -1,9 +1,9 @@
|
|||
.PHONY: clean-pyc test upload-docs
|
||||
.PHONY: clean-pyc test upload-docs docs
|
||||
|
||||
all: clean-pyc test
|
||||
|
||||
test:
|
||||
python tests/flask_tests.py
|
||||
python setup.py test
|
||||
|
||||
release:
|
||||
python setup.py release sdist upload
|
||||
|
|
@ -20,3 +20,6 @@ upload-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/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/
|
||||
|
||||
docs:
|
||||
$(MAKE) -C docs html
|
||||
|
|
|
|||
20
artwork/LICENSE
Normal file
20
artwork/LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2010 by Armin Ronacher.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
This logo or a modified version may be used by anyone to refer to the
|
||||
Flask project, but does not indicate endorsement by the project.
|
||||
|
||||
Redistribution and use in source (the SVG file) and binary forms (rendered
|
||||
PNG files etc.) of the image, with or without modification, are permitted
|
||||
provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice and this list of conditions.
|
||||
|
||||
* The names of the contributors to the Flask software (see AUTHORS) may
|
||||
not be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
Note: we would appreciate that you make the image a link to
|
||||
http://flask.pocoo.org/ if you use it on a web page.
|
||||
BIN
docs/_static/no.png
vendored
Normal file
BIN
docs/_static/no.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 317 B |
BIN
docs/_static/yes.png
vendored
Normal file
BIN
docs/_static/yes.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 277 B |
|
|
@ -1 +1 @@
|
|||
Subproject commit 0d8f3d85558168647632c768bdea7d58cf6f8e42
|
||||
Subproject commit 3d964b660442e23faedf801caed6e3c7bd42d5c9
|
||||
149
docs/api.rst
149
docs/api.rst
|
|
@ -38,6 +38,8 @@ Incoming Request Data
|
|||
sure that you always get the correct data for the active thread if you
|
||||
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`
|
||||
subclass and provides all of the attributes Werkzeug defines. This
|
||||
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
|
||||
difference that it keeps track on modifications.
|
||||
|
||||
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||
|
||||
The following attributes are interesting:
|
||||
|
||||
.. 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
|
||||
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
|
||||
----------------------------
|
||||
|
|
@ -216,6 +222,8 @@ Useful Functions and Classes
|
|||
extensions that want to support multiple applications running side
|
||||
by side.
|
||||
|
||||
This is a proxy. See :ref:`notes-on-proxies` for more information.
|
||||
|
||||
.. autofunction:: url_for
|
||||
|
||||
.. function:: abort(code)
|
||||
|
|
@ -228,8 +236,12 @@ Useful Functions and Classes
|
|||
|
||||
.. autofunction:: redirect
|
||||
|
||||
.. autofunction:: make_response
|
||||
|
||||
.. autofunction:: send_file
|
||||
|
||||
.. autofunction:: send_from_directory
|
||||
|
||||
.. autofunction:: escape
|
||||
|
||||
.. autoclass:: Markup
|
||||
|
|
@ -290,3 +302,140 @@ Configuration
|
|||
|
||||
.. autoclass:: Config
|
||||
:members:
|
||||
|
||||
Useful Internals
|
||||
----------------
|
||||
|
||||
.. data:: _request_ctx_stack
|
||||
|
||||
The internal :class:`~werkzeug.LocalStack` that is used to implement
|
||||
all the context local objects used in Flask. This is a documented
|
||||
instance and can be used by extensions and application code but the
|
||||
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
|
||||
|
||||
The request context is automatically popped at the end of the request
|
||||
for you. In debug mode the request context is kept around if
|
||||
exceptions happen so that interactive debuggers have a chance to
|
||||
introspect the data. With 0.4 this can also be forced for requests
|
||||
that did not fail and outside of `DEBUG` mode. By setting
|
||||
``'flask._preserve_context'`` to `True` on the WSGI environment the
|
||||
context will not pop itself at the end of the request. This is used by
|
||||
the :meth:`~flask.Flask.test_client` for example to implement the
|
||||
deferred cleanup functionality.
|
||||
|
||||
You might find this helpful for unittests where you need the
|
||||
information from the context local around for a little longer. Make
|
||||
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
|
||||
that situation, otherwise your unittests will leak memory.
|
||||
|
||||
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
|
||||
- if the object reference is important (so for example for sending
|
||||
:ref:`signals`)
|
||||
|
||||
If you need to get access to the underlying object that is proxied, you
|
||||
can use the :meth:`~werkzeug.LocalProxy._get_current_object` method::
|
||||
|
||||
app = current_app._get_current_object()
|
||||
my_signal.send(app)
|
||||
|
|
|
|||
|
|
@ -3,54 +3,86 @@
|
|||
Becoming Big
|
||||
============
|
||||
|
||||
Your application is becoming more and more complex? Flask is really not
|
||||
designed for large scale applications and does not attempt to do so, but
|
||||
that does not mean you picked the wrong tool in the first place.
|
||||
Your application is becoming more and more complex? If you suddenly
|
||||
realize that Flask does things in a way that does not work out for your
|
||||
application there are ways to deal with that.
|
||||
|
||||
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
|
||||
two together. Being a microframework, Flask is literally a single file.
|
||||
What that means for large applications is that it's probably a good idea
|
||||
to take the code from Flask and put it into a new module within the
|
||||
applications and expand on that.
|
||||
two together. Being a microframework Flask does not do much more than
|
||||
combinding existing libraries - there is not a lot of code involved.
|
||||
What that means for large applications is that it's very easy to take the
|
||||
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
|
||||
names of the functions / URL rules) are handled to also take the module
|
||||
name into account. Right now the function name is the URL name, but
|
||||
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.
|
||||
- Flask extensions. For a lot of reusable functionality you can create
|
||||
extensions. For extensions a number of hooks exist throughout Flask
|
||||
with signals and callback functions.
|
||||
|
||||
Here are some suggestions for how Flask can be modified to better
|
||||
accommodate large-scale applications:
|
||||
- Subclassing. The majority of functionality can be changed by creating
|
||||
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
|
||||
of troubles for applications that have circular dependencies. It
|
||||
also requires that the whole application is imported when the system
|
||||
initializes or certain URLs will not be available right away. A
|
||||
better solution would be to have one module with all URLs in there and
|
||||
specifying the target functions explicitly or by name and importing
|
||||
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.
|
||||
- Forking. If nothing else works out you can just take the Flask
|
||||
codebase at a given point and copy/paste it into your application
|
||||
and change it. Flask is designed with that in mind and makes this
|
||||
incredible easy. You just have to take the package and copy it
|
||||
into your application's code and rename it (for example to
|
||||
`framework`). Then you can start modifying the code in there.
|
||||
|
||||
.. _Babel: http://babel.edgewall.org/
|
||||
.. _SQLAlchemy: http://www.sqlalchemy.org/
|
||||
Why consider Forking?
|
||||
---------------------
|
||||
|
||||
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
|
||||
handle a couple of requests per second and with an overall code complexity
|
||||
of less than 4000 lines of code and something of larger scale. At some
|
||||
point it becomes important to integrate external systems, different
|
||||
storage backends and more.
|
||||
This is not unique to Flask. Many people use patched and modified
|
||||
versions of their framework to counter shortcomings. This idea is also
|
||||
reflected in the license of Flask. You don't have to contribute any
|
||||
changes back if you decide to modify the framework.
|
||||
|
||||
If Flask was designed with all these contingencies in mind, it would be a
|
||||
much more complex framework and harder to get started with.
|
||||
The downside of forking is of course that Flask extensions will most
|
||||
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.
|
||||
|
|
|
|||
32
docs/conf.py
32
docs/conf.py
|
|
@ -43,12 +43,20 @@ master_doc = 'index'
|
|||
project = u'Flask'
|
||||
copyright = u'2010, Armin Ronacher'
|
||||
|
||||
import pkg_resources
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
release = __import__('pkg_resources').get_distribution('Flask').version
|
||||
import pkg_resources
|
||||
try:
|
||||
release = pkg_resources.get_distribution('Flask').version
|
||||
except pkg_resources.DistributionNotFound:
|
||||
print 'To build the documentation, The distribution information of Flask'
|
||||
print 'Has to be available. Either install the package into your'
|
||||
print 'development environment or run "setup.py develop" to setup the'
|
||||
print 'metadata. A virtualenv is recommended!'
|
||||
sys.exit(1)
|
||||
del pkg_resources
|
||||
|
||||
if 'dev' in release:
|
||||
release = release.split('dev')[0] + 'dev'
|
||||
version = '.'.join(release.split('.')[:2])
|
||||
|
|
@ -237,7 +245,23 @@ intersphinx_mapping = {
|
|||
'http://docs.python.org/dev': None,
|
||||
'http://werkzeug.pocoo.org/documentation/dev/': 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'
|
||||
|
||||
# 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 = {}
|
||||
|
|
|
|||
118
docs/config.rst
118
docs/config.rst
|
|
@ -6,12 +6,12 @@ Configuration Handling
|
|||
.. versionadded:: 0.3
|
||||
|
||||
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.
|
||||
|
||||
The way Flask is designed usually requires the configuration to be
|
||||
available when the application starts up. You can either hardcode the
|
||||
configuration in the code which for many small applications is not
|
||||
available when the application starts up. You can hardcode the
|
||||
configuration in the code, which for many small applications is not
|
||||
actually that bad, but there are better ways.
|
||||
|
||||
Independent of how you load your config, there is a config object
|
||||
|
|
@ -53,18 +53,54 @@ The following configuration values are used internally by Flask:
|
|||
|
||||
=============================== =========================================
|
||||
``DEBUG`` enable/disable debug mode
|
||||
``TESTING`` enable/disable testing mode
|
||||
``SECRET_KEY`` the secret key
|
||||
``SESSION_COOKIE_NAME`` the name of the session cookie
|
||||
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
|
||||
:class:`datetime.timedelta` object.
|
||||
``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 (eg: ``'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
|
||||
----------------------
|
||||
|
||||
Configuration becomes more useful if you can configure from a file. And
|
||||
ideally that file would be outside of the actual application package that
|
||||
Configuration becomes more useful if you can configure from a file, and
|
||||
ideally that file would be outside of the actual application package so that
|
||||
you can install the package with distribute (:ref:`distribute-deployment`)
|
||||
and still modify that file afterwards.
|
||||
|
||||
|
|
@ -74,7 +110,7 @@ So a common pattern is this::
|
|||
app.config.from_object('yourapplication.default_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
|
||||
with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS`
|
||||
environment variable points to. This environment variable can be set on
|
||||
|
|
@ -94,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
|
||||
sure to use uppercase letters for your config keys.
|
||||
|
||||
Here an example configuration file::
|
||||
Here is an example configuration file::
|
||||
|
||||
DEBUG = False
|
||||
SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'
|
||||
|
|
@ -122,3 +158,71 @@ experience:
|
|||
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
|
||||
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 sepearately to the production server(s). For some
|
||||
details about how to do that, head over to the :ref:`deploy` pattern.
|
||||
|
||||
.. _fabric: http://fabfile.org/
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
User's Guide
|
||||
------------
|
||||
|
||||
This part of the documentation is written text and should give you an idea
|
||||
how to work with Flask. It's a series of step-by-step instructions for
|
||||
web development.
|
||||
This part of the documentation, which is mostly prose, begins with some
|
||||
background information about Flask, then focuses on step-by-step
|
||||
instructions for web development with Flask.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
|
@ -12,9 +12,11 @@ web development.
|
|||
installation
|
||||
quickstart
|
||||
tutorial/index
|
||||
templating
|
||||
testing
|
||||
errorhandling
|
||||
config
|
||||
signals
|
||||
shell
|
||||
patterns/index
|
||||
deploying/index
|
||||
|
|
@ -40,6 +42,11 @@ Design notes, legal information and changelog are here for the interested.
|
|||
:maxdepth: 2
|
||||
|
||||
design
|
||||
license
|
||||
htmlfaq
|
||||
security
|
||||
unicode
|
||||
extensiondev
|
||||
styleguide
|
||||
upgrading
|
||||
changelog
|
||||
license
|
||||
|
|
|
|||
|
|
@ -73,26 +73,27 @@ root.
|
|||
Configuring nginx
|
||||
-----------------
|
||||
|
||||
Installing FastCGI applications on nginx is a bit tricky because by default
|
||||
some FastCGI parameters are not properly forwarded.
|
||||
Installing FastCGI applications on nginx is a bit different because by default
|
||||
no FastCGI parameters are forwarded.
|
||||
|
||||
A basic FastCGI configuration for nginx looks like this::
|
||||
A basic flask FastCGI configuration for nginx looks like this::
|
||||
|
||||
location /yourapplication/ {
|
||||
location = /yourapplication { rewrite ^ /yourapplication/ last; }
|
||||
location /yourapplication { try_files $uri @yourapplication; }
|
||||
location @yourapplication {
|
||||
include fastcgi_params;
|
||||
if ($uri ~ ^/yourapplication/(.*)?) {
|
||||
set $path_url $1;
|
||||
}
|
||||
fastcgi_param PATH_INFO $path_url;
|
||||
fastcgi_param SCRIPT_NAME /yourapplication;
|
||||
fastcgi_split_path_info ^(/yourapplication)(.*)$;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
|
||||
}
|
||||
|
||||
This configuration binds the application to `/yourapplication`. If you want
|
||||
to have it in the URL root it's a bit easier because you don't have to figure
|
||||
to have it in the URL root it's a bit simpler because you don't have to figure
|
||||
out how to calculate `PATH_INFO` and `SCRIPT_NAME`::
|
||||
|
||||
location /yourapplication/ {
|
||||
location / { try_files $uri @yourapplication; }
|
||||
location @yourapplication {
|
||||
include fastcgi_params;
|
||||
fastcgi_param PATH_INFO $fastcgi_script_name;
|
||||
fastcgi_param SCRIPT_NAME "";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _mod_wsgi-deployment:
|
||||
|
||||
mod_wsgi (Apache)
|
||||
=================
|
||||
|
||||
|
|
@ -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
|
||||
filename is used to locate the resources and for symlinks the wrong
|
||||
filename is picked up.
|
||||
|
||||
Support for Automatic Reloading
|
||||
-------------------------------
|
||||
|
||||
To help deployment tools you can activate support for automatic reloading.
|
||||
Whenever something changes the `.wsgi` file, `mod_wsgi` will reload all
|
||||
the daemon processes for us.
|
||||
|
||||
For that, just add the following directive to your `Directory` section:
|
||||
|
||||
.. sourcecode:: apache
|
||||
|
||||
WSGIScriptReloading On
|
||||
|
||||
Working with Virtual Environments
|
||||
---------------------------------
|
||||
|
||||
Virtual environments have the advantage that they never install the
|
||||
required dependencies system wide so you have a better control over what
|
||||
is used where. If you want to use a virtual environment with mod_wsgi you
|
||||
have to modify your `.wsgi` file slightly.
|
||||
|
||||
Add the following lines to the top of your `.wsgi` file::
|
||||
|
||||
activate_this = '/path/to/env/bin/activate_this.py'
|
||||
execfile(activate_this, dict(__file__=activate_this))
|
||||
|
||||
This sets up the load paths according to the settings of the virtual
|
||||
environment. Keep in mind that the path has to be absolute.
|
||||
|
|
|
|||
|
|
@ -61,3 +61,38 @@ and `greenlet`_. Running a Flask application on this server is quite simple::
|
|||
.. _eventlet: http://eventlet.net/
|
||||
.. _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 middlware for specific setups.
|
||||
|
||||
The most common setup invokes the host being set from `X-Forwarded-Host`
|
||||
and the remote address from `X-Forwared-For`::
|
||||
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||
|
||||
Please keep in mind that it is a security issue to use such a middleware
|
||||
in a non-proxy setup because it will blindly trust the incoming
|
||||
headers which might be forged by malicious clients.
|
||||
|
||||
If you want to rewrite the headers from another header, you might want to
|
||||
use a fixer like this::
|
||||
|
||||
class CustomProxyFix(object):
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
host = environ.get('HTTP_X_FHOST', '')
|
||||
if host:
|
||||
environ['HTTP_HOST'] = host
|
||||
return self.app(environ, start_response)
|
||||
|
||||
app.wsgi_app = CustomProxyFix(app.wsgi_app)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _design:
|
||||
|
||||
Design Decisions in Flask
|
||||
=========================
|
||||
|
||||
|
|
@ -73,6 +75,10 @@ want to apply a WSGI middleware, just wrap it and you're done (though
|
|||
there are better ways to do that so that you do not lose the reference
|
||||
to the application object :meth:`~flask.Flask.wsgi_app`).
|
||||
|
||||
Furthermore this design makes it possible to use a factory function to
|
||||
create the application which is very helpful for unittesting and similar
|
||||
things (:ref:`app-factories`).
|
||||
|
||||
One Template Engine
|
||||
-------------------
|
||||
|
||||
|
|
@ -105,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
|
||||
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
|
||||
-----------------------
|
||||
|
|
@ -145,3 +155,21 @@ quick and easy to write a traditional web application.
|
|||
|
||||
Also see the :ref:`becomingbig` section of the documentation for some
|
||||
inspiration for larger applications based on Flask.
|
||||
|
||||
|
||||
What Flask is, What Flask is Not
|
||||
--------------------------------
|
||||
|
||||
Flask will never have a database layer. It will not have a form library
|
||||
or anything else in that direction. Flask itself just bridges to Werkzeug
|
||||
to implement a proper WSGI application and to Jinja2 to handle templating.
|
||||
It also binds to a few common standard library packages such as logging.
|
||||
Everything else is up for extensions.
|
||||
|
||||
Why is this the case? Because people have different preferences and
|
||||
requirements and Flask could not meet those if it would force any of this
|
||||
into the core. The majority of web applications will need a template
|
||||
engine in some sort. However not every application needs a SQL database.
|
||||
|
||||
The idea of Flask is to build a good foundation for all applications.
|
||||
Everything else is up to you or extensions.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Handling Application Errors
|
|||
|
||||
.. 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
|
||||
exceptions from time to time. Why? Because everything else involved will
|
||||
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
|
||||
- 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
|
||||
production mode, Flask will display a very simple page for you and log the
|
||||
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
|
||||
server) you won't see any log messages by default. Why that? Flask tries
|
||||
to be a zero-configuration framework and where should it drop the logs for
|
||||
you if there is no configuration. Guessing is not a good idea because
|
||||
changes are, the place it guessed is not the place where the user has the
|
||||
server) you won't see any log messages by default. Why is that? Flask
|
||||
tries to be a zero-configuration framework. Where should it drop the logs
|
||||
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
|
||||
permission to create a logfile. Also, for most small applications nobody
|
||||
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
|
||||
something about it.
|
||||
|
||||
Flask is using the Python builtin logging system and that one can actually
|
||||
send you mails for errors which is probably what you want. Here is how
|
||||
you can configure the Flask logger to send you mails for exceptions::
|
||||
Flask uses the Python builtin logging system, and it can actually send
|
||||
you mails for errors which is probably what you want. Here is how you can
|
||||
configure the Flask logger to send you mails for exceptions::
|
||||
|
||||
ADMINS = ['yourname@example.com']
|
||||
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
|
||||
server listening on ``127.0.0.1`` to all the `ADMINS` from the address
|
||||
*server-error@example.com* with the subject "YourApplication Failed". If
|
||||
your mail server requires credentials these can also provided, for that
|
||||
check out the documentation for the :class:`~logging.handlers.SMTPHandler`.
|
||||
your mail server requires credentials, these can also be provided. For
|
||||
that check out the documentation for the
|
||||
:class:`~logging.handlers.SMTPHandler`.
|
||||
|
||||
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
|
||||
useless logs that might happen during request handling.
|
||||
|
||||
Before you run that in production, please also look at :ref:`log-format`
|
||||
to put more information into that error mail. That will save you from a
|
||||
lot of frustration.
|
||||
Before you run that in production, please also look at :ref:`logformat` to
|
||||
put more information into that error mail. That will save you from a lot
|
||||
of frustration.
|
||||
|
||||
|
||||
Logging to a File
|
||||
|
|
@ -109,18 +110,18 @@ above, just make sure to use a lower setting (I would recommend
|
|||
file_handler.setLevel(logging.WARNING)
|
||||
app.logger.addHandler(file_handler)
|
||||
|
||||
.. _log-format:
|
||||
.. _logformat:
|
||||
|
||||
Controlling the Log Format
|
||||
--------------------------
|
||||
|
||||
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
|
||||
information so that you have a better idea of why that error happened, and
|
||||
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
|
||||
do that in the log formatter format string.
|
||||
|
||||
|
|
@ -151,7 +152,7 @@ File logging
|
|||
|
||||
from logging import Formatter
|
||||
file_handler.setFormatter(Formatter(
|
||||
'%(astime)s %(levelname)s: %(message)s '
|
||||
'%(asctime)s %(levelname)s: %(message)s '
|
||||
'[in %(pathname)s:%(lineno)d]'
|
||||
))
|
||||
|
||||
|
|
@ -206,7 +207,7 @@ formatter. The formatter has three interesting methods:
|
|||
called for `asctime` formatting. If you want a different time format
|
||||
you can override this method.
|
||||
: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
|
||||
don't have to override it.
|
||||
|
||||
|
|
@ -217,8 +218,8 @@ Other Libraries
|
|||
---------------
|
||||
|
||||
So far we only configured the logger your application created itself.
|
||||
Other libraries might log themselves as well. For example, SQLAlchemy use
|
||||
logging heavily in the core. While there is a method to configure all
|
||||
Other libraries might log themselves as well. For example, SQLAlchemy uses
|
||||
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
|
||||
it. There might be a situation in which you want to have multiple
|
||||
separate applications running side by side in the same Python interpreter
|
||||
|
|
|
|||
323
docs/extensiondev.rst
Normal file
323
docs/extensiondev.rst
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
Flask Extension Development
|
||||
===========================
|
||||
|
||||
Flask, being a microframework, often requires some repetitive steps to get
|
||||
a third party library working. Because very often these steps could be
|
||||
abstracted to support multiple projects the `Flask Extension Registry`_
|
||||
was created.
|
||||
|
||||
If you want to create your own Flask extension for something that does not
|
||||
exist yet, this guide to extension development will help you get your
|
||||
extension running in no time and to feel like users would expect your
|
||||
extension to behave.
|
||||
|
||||
.. _Flask Extension Registry: http://flask.pocoo.org/extensions/
|
||||
|
||||
Anatomy of an Extension
|
||||
-----------------------
|
||||
|
||||
Extensions are all located in a package called ``flaskext.something``
|
||||
where "something" is the name of the library you want to bridge. So for
|
||||
example if you plan to add support for a library named `simplexml` to
|
||||
Flask, you would name your extension's package ``flaskext.simplexml``.
|
||||
|
||||
The name of the actual extension (the human readable name) however would
|
||||
be something like "Flask-SimpleXML". Make sure to include the name
|
||||
"Flask" somewhere in that name and that you check the capitalization.
|
||||
This is how users can then register dependencies to your extension in
|
||||
their `setup.py` files.
|
||||
|
||||
The magic that makes it possible to have your library in a package called
|
||||
``flaskext.something`` is called a "namespace package". Check out the
|
||||
guide below how to create something like that.
|
||||
|
||||
But how do extensions look like themselves? An extension has to ensure
|
||||
that it works with multiple Flask application instances at once. This is
|
||||
a requirement because many people will use patterns like the
|
||||
:ref:`app-factories` pattern to create their application as needed to aid
|
||||
unittests and to support multiple configurations. Because of that it is
|
||||
crucial that your application supports that kind of behaviour.
|
||||
|
||||
Most importantly the extension must be shipped with a `setup.py` file and
|
||||
registered on PyPI. Also the development checkout link should work so
|
||||
that people can easily install the development version into their
|
||||
virtualenv without having to download the library by hand.
|
||||
|
||||
Flask extensions must be licensed as BSD or MIT or a more liberal license
|
||||
to be enlisted on the Flask Extension Registry. Keep in mind that the
|
||||
Flask Extension Registry is a moderated place and libraries will be
|
||||
reviewed upfront if they behave as required.
|
||||
|
||||
"Hello Flaskext!"
|
||||
-----------------
|
||||
|
||||
So let's get started with creating such a Flask extension. The extension
|
||||
we want to create here will provide very basic support for SQLite3.
|
||||
|
||||
There is a script on github called `Flask Extension Wizard`_ which helps
|
||||
you create the initial folder structure. But for this very basic example
|
||||
we want to create all by hand to get a better feeling for it.
|
||||
|
||||
First we create the following folder structure::
|
||||
|
||||
flask-sqlite3/
|
||||
flaskext/
|
||||
__init__.py
|
||||
sqlite3.py
|
||||
setup.py
|
||||
LICENSE
|
||||
|
||||
Here the contents of the most important files:
|
||||
|
||||
flaskext/__init__.py
|
||||
````````````````````
|
||||
|
||||
The only purpose of this file is to mark the package as namespace package.
|
||||
This is required so that multiple modules from different PyPI packages can
|
||||
reside in the same Python package::
|
||||
|
||||
__import__('pkg_resources').declare_namespace(__name__)
|
||||
|
||||
If you want to know exactly what is happening there, checkout the
|
||||
distribute or setuptools docs which explain how this works.
|
||||
|
||||
Just make sure to not put anything else in there!
|
||||
|
||||
setup.py
|
||||
````````
|
||||
|
||||
The next file that is absolutely required is the `setup.py` file which is
|
||||
used to install your Flask extension. The following contents are
|
||||
something you can work with::
|
||||
|
||||
"""
|
||||
Flask-SQLite3
|
||||
-------------
|
||||
|
||||
This is the description for that library
|
||||
"""
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
setup(
|
||||
name='Flask-SQLite3',
|
||||
version='1.0',
|
||||
url='http://example.com/flask-sqlite3/',
|
||||
license='BSD',
|
||||
author='Your Name',
|
||||
author_email='your-email@example.com',
|
||||
description='Very short description',
|
||||
long_description=__doc__,
|
||||
packages=['flaskext'],
|
||||
namespace_packages=['flaskext'],
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
install_requires=[
|
||||
'Flask'
|
||||
],
|
||||
classifiers=[
|
||||
'Environment :: Web Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
]
|
||||
)
|
||||
|
||||
That's a lot of code but you can really just copy/paste that from existing
|
||||
extensions and adapt. This is also what the wizard creates for you if you
|
||||
use it.
|
||||
|
||||
flaskext/sqlite3.py
|
||||
```````````````````
|
||||
|
||||
Now this is where your extension code goes. But how exactly should such
|
||||
an extension look like? What are the best practices? Continue reading
|
||||
for some insight.
|
||||
|
||||
|
||||
Initializing Extensions
|
||||
-----------------------
|
||||
|
||||
Many extensions will need some kind of initialization step. For example,
|
||||
consider your application is currently connecting to SQLite like the
|
||||
documentation suggests (:ref:`sqlite3`) you will need to provide a few
|
||||
functions and before / after request handlers. So how does the extension
|
||||
know the name of the application object?
|
||||
|
||||
Quite simple: you pass it to it.
|
||||
|
||||
There are two recommended ways for an extension to initialize:
|
||||
|
||||
initialization functions:
|
||||
If your extension is called `helloworld` you might have a function
|
||||
called ``init_helloworld(app[, extra_args])`` that initalizes the
|
||||
extension for that application. It could attach before / after
|
||||
handlers etc.
|
||||
|
||||
classes:
|
||||
Classes work mostly like initialization functions but can later be
|
||||
used to further change the behaviour. For an example look at how the
|
||||
`OAuth extension`_ works: there is an `OAuth` object that provides
|
||||
some helper functions like `OAuth.remote_app` to create a reference to
|
||||
a remote application that uses OAuth.
|
||||
|
||||
What to use depends on what you have in mind. For the SQLite 3 extension
|
||||
we will need to use the class based approach because we have to use a
|
||||
controller object that can be used to connect to the database.
|
||||
|
||||
The Extension Code
|
||||
------------------
|
||||
|
||||
Here the contents of the `flaskext/sqlite3.py` for copy/paste::
|
||||
|
||||
from __future__ import absolute_import
|
||||
import sqlite3
|
||||
from flask import g
|
||||
|
||||
class SQLite3(object):
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
|
||||
|
||||
self.app.before_request(self.before_request)
|
||||
self.app.after_request(self.after_request)
|
||||
|
||||
def connect(self):
|
||||
return sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
|
||||
|
||||
def before_request(self):
|
||||
g.sqlite3_db = self.connect()
|
||||
|
||||
def after_request(self, response):
|
||||
g.sqlite3_db.close()
|
||||
return response
|
||||
|
||||
So here what the lines of code do:
|
||||
|
||||
1. the ``__future__`` import is necessary to activate absolute imports.
|
||||
This is needed because otherwise we could not call our module
|
||||
`sqlite3.py` and import the top-level `sqlite3` module which actually
|
||||
implements the connection to SQLite.
|
||||
2. We create a class for our extension that sets a default configuration
|
||||
for the SQLite 3 database if it's not there (:meth:`dict.setdefault`)
|
||||
and connects two functions as before and after request handlers.
|
||||
3. Then it implements a `connect` function that returns a new database
|
||||
connection and the two handlers.
|
||||
|
||||
So why did we decide on a class based approach here? Because using that
|
||||
extension looks something like this::
|
||||
|
||||
from flask import Flask, g
|
||||
from flaskext.sqlite3 import SQLite3
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile('the-config.cfg')
|
||||
db = SQLite(app)
|
||||
|
||||
Either way you can use the database from the views like this::
|
||||
|
||||
@app.route('/')
|
||||
def show_all():
|
||||
cur = g.sqlite3_db.cursor()
|
||||
cur.execute(...)
|
||||
|
||||
But how would you open a database connection from outside a view function?
|
||||
This is where the `db` object now comes into play:
|
||||
|
||||
>>> from yourapplication import db
|
||||
>>> con = db.connect()
|
||||
>>> cur = con.cursor()
|
||||
|
||||
If you don't need that, you can go with initialization functions.
|
||||
|
||||
Initialization Functions
|
||||
------------------------
|
||||
|
||||
Here how the module would look like with initialization functions::
|
||||
|
||||
from __future__ import absolute_import
|
||||
import sqlite3
|
||||
from flask import g
|
||||
|
||||
def init_sqlite3(app):
|
||||
app = app
|
||||
app.config.setdefault('SQLITE3_DATABASE', ':memory:')
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.sqlite3_db = sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
g.sqlite3_db.close()
|
||||
return response
|
||||
|
||||
Learn from Others
|
||||
-----------------
|
||||
|
||||
This documentation only touches the bare minimum for extension
|
||||
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
|
||||
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
|
||||
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, 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``).
|
||||
|
||||
|
||||
.. _Flask Extension Wizard:
|
||||
http://github.com/mitsuhiko/flask-extension-wizard
|
||||
.. _OAuth extension: http://packages.python.org/Flask-OAuth/
|
||||
.. _mailinglist: http://flask.pocoo.org/mailinglist/
|
||||
.. _IRC channel: http://flask.pocoo.org/community/irc/
|
||||
|
|
@ -2,90 +2,108 @@ Foreword
|
|||
========
|
||||
|
||||
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.
|
||||
|
||||
What does Micro Mean?
|
||||
---------------------
|
||||
What does "micro" mean?
|
||||
-----------------------
|
||||
|
||||
The micro in microframework for me means on the one hand being small in
|
||||
size and complexity but on the other hand also that the complexity of the
|
||||
applications that are written with these frameworks do not exceed a
|
||||
certain size. A microframework like Flask sacrifices a few things in
|
||||
order to be approachable and to be as concise as possible.
|
||||
To me, the "micro" in microframework refers not only to the simplicity and
|
||||
small size of the framework, but also to the typically limited complexity
|
||||
and size of applications that are written with the framework. Also the
|
||||
fact that you can have an entire application in a single Python file. To
|
||||
be approachable and concise, a microframework sacrifices a few features
|
||||
that may be necessary in larger or more complex applications.
|
||||
|
||||
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
|
||||
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.
|
||||
It's especially painful for more complex unittests and when you suddenly
|
||||
have to deal with code being executed outside of the context of a request
|
||||
(for example if you have cronjobs).
|
||||
you a lot of time, it might also cause some troubles for very large
|
||||
applications because changes on these thread-local objects can happen
|
||||
anywhere in the same thread.
|
||||
|
||||
Flask provides some tools to deal with the downsides of this approach but
|
||||
the core problem of this approach obviously stays. It is also based on
|
||||
convention over configuration which means that a lot of things are
|
||||
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.)
|
||||
it might be an issue for larger applications because in theory
|
||||
modifications on these objects might happen anywhere in the same thread.
|
||||
|
||||
But don't worry if your application suddenly grows larger than it was
|
||||
initially and you're afraid Flask might not grow with it. Even with
|
||||
larger frameworks you sooner or later will find out that you need
|
||||
something the framework just cannot do for you without modification.
|
||||
If you are ever in that situation, check out the :ref:`becomingbig`
|
||||
chapter.
|
||||
Flask is also based on convention over configuration, which means that
|
||||
many things are preconfigured. For example, by convention, templates and
|
||||
static files are in subdirectories within the Python source tree of the
|
||||
application.
|
||||
|
||||
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 built in a very solid foundation and
|
||||
with that 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
|
||||
framework. Flask itself is just one way to implement a framework on top
|
||||
of existing libraries. Unlike many other microframeworks Flask does not
|
||||
try to implement anything on its own, it reuses existing code.
|
||||
of existing libraries. Unlike many other microframeworks, Flask does not
|
||||
try to implement everything on its own; it reuses existing code.
|
||||
|
||||
Web Development is Dangerous
|
||||
----------------------------
|
||||
|
||||
I'm not even joking. Well, maybe a little. If you write a web
|
||||
application you are probably allowing users to register and leave their
|
||||
I'm not joking. Well, maybe a little. If you write a web
|
||||
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
|
||||
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
|
||||
problems of modern web applications: cross site scripting (XSS). Unless
|
||||
you deliberately mark insecure HTML as secure Flask (and the underlying
|
||||
Jinja2 template engine) have you covered. But there are many more ways to
|
||||
problems of modern web applications: cross-site scripting (XSS). Unless
|
||||
you deliberately mark insecure HTML as secure, Flask and the underlying
|
||||
Jinja2 template engine have you covered. But there are many more ways to
|
||||
cause security problems.
|
||||
|
||||
Whenever something is dangerous where you have to watch out, the
|
||||
documentation will tell you so. Some of the security concerns of web
|
||||
development are far more complex than one might think and often we all end
|
||||
up in situations where we think "well, this is just far fetched, how could
|
||||
that possibly be exploited" and then an intelligent guy comes along and
|
||||
figures a way out to exploit that application. And don't think, your
|
||||
application is not important enough for hackers to take notice. Depending
|
||||
on the kind of attack, chances are there are automated botnets out there
|
||||
trying to figure out how to fill your database with viagra advertisements.
|
||||
The documentation will warn you about aspects of web development that
|
||||
require attention to security. Some of these security concerns
|
||||
are far more complex than one might think, and we all sometimes underestimate
|
||||
the likelihood that a vulnerability will be exploited, until a clever
|
||||
attacker figures out a way to exploit our applications. And don't think
|
||||
that your application is not important enough to attract an attacker.
|
||||
Depending on the kind of attack, chances are that automated bots are
|
||||
probing for ways to fill your database with spam, links to malicious
|
||||
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
|
||||
too complex database structures, Flask is the Framework for you. It was
|
||||
designed from the ground up to be easy to use, based on established
|
||||
principles, good intentions and on top of two established libraries in
|
||||
widespread usage. Recent versions of Flask scale nicely within reasonable
|
||||
bounds and if you grow larger, you won't have any troubles adjusting Flask
|
||||
for your new application size.
|
||||
Currently the Python community is in the process of improving libraries to
|
||||
support the new iteration of the Python programming language.
|
||||
Unfortunately there are a few problems with Python 3, namely the missing
|
||||
consent on what WSGI for Python 3 should look like. These problems are
|
||||
partially caused by changes in the language that went unreviewed for too
|
||||
long, also partially the ambitions of everyone involved to drive the WSGI
|
||||
standard forward.
|
||||
|
||||
If you suddenly discover that your application grows larger than
|
||||
originally intended, head over to the :ref:`becomingbig` section to see
|
||||
some possible solutions for larger applications.
|
||||
Because of that we strongly recommend against using Python 3 for web
|
||||
development of any kind and wait until the WSGI situation is resolved.
|
||||
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.
|
||||
|
|
|
|||
207
docs/htmlfaq.rst
Normal file
207
docs/htmlfaq.rst
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
HTML/XHTML FAQ
|
||||
==============
|
||||
|
||||
The Flask documentation and example applications are using HTML5. You
|
||||
may notice that in many situations, when end tags are optional they are
|
||||
not used, so that the HTML is cleaner and faster to load. Because there
|
||||
is much confusion about HTML and XHTML among developers, this document tries
|
||||
to answer some of the major questions.
|
||||
|
||||
|
||||
History of XHTML
|
||||
----------------
|
||||
|
||||
For a while, it appeared that HTML was about to be replaced by XHTML.
|
||||
However, barely any websites on the Internet are actual XHTML (which is
|
||||
HTML processed using XML rules). There are a couple of major reasons
|
||||
why this is the case. One of them is Internet Explorer's lack of proper
|
||||
XHTML support. The XHTML spec states that XHTML must be served with the MIME
|
||||
type `application/xhtml+xml`, but Internet Explorer refuses to read files
|
||||
with that MIME type.
|
||||
While it is relatively easy to configure Web servers to serve XHTML properly,
|
||||
few people do. This is likely because properly using XHTML can be quite
|
||||
painful.
|
||||
|
||||
One of the most important causes of pain is XML's draconian (strict and
|
||||
ruthless) error handling. When an XML parsing error is encountered,
|
||||
the browser is supposed to show the user an ugly error message, instead
|
||||
of attempting to recover from the error and display what it can. Most of
|
||||
the (X)HTML generation on the web is based on non-XML template engines
|
||||
(such as Jinja, the one used in Flask) which do not protect you from
|
||||
accidentally creating invalid XHTML. There are XML based template engines,
|
||||
such as Kid and the popular Genshi, but they often come with a larger
|
||||
runtime overhead and, are not as straightforward to use because they have
|
||||
to obey XML rules.
|
||||
|
||||
The majority of users, however, assumed they were properly using XHTML.
|
||||
They wrote an XHTML doctype at the top of the document and self-closed all
|
||||
the necessary tags (``<br>`` becomes ``<br/>`` or ``<br></br>`` in XHTML).
|
||||
However, even if the document properly validates as XHTML, what really
|
||||
determines XHTML/HTML processing in browsers is the MIME type, which as
|
||||
said before is often not set properly. So the valid XHTML was being treated
|
||||
as invalid HTML.
|
||||
|
||||
XHTML also changed the way JavaScript is used. To properly work with XHTML,
|
||||
programmers have to use the namespaced DOM interface with the XHTML
|
||||
namespace to query for HTML elements.
|
||||
|
||||
History of HTML5
|
||||
----------------
|
||||
|
||||
Development of the HTML5 specification was started in 2004 under the name
|
||||
"Web Applications 1.0" by the Web Hypertext Application Technology Working
|
||||
Group, or WHATWG (which was formed by the major browser vendors Apple,
|
||||
Mozilla, and Opera) with the goal of writing a new and improved HTML
|
||||
specification, based on existing browser behaviour instead of unrealistic
|
||||
and backwards-incompatible specifications.
|
||||
|
||||
For example, in HTML4 ``<title/Hello/`` theoretically parses exactly the
|
||||
same as ``<title>Hello</title>``. However, since people were using
|
||||
XHTML-like tags along the lines of ``<link />``, browser vendors implemented
|
||||
the XHTML syntax over the syntax defined by the specification.
|
||||
|
||||
In 2007, the specification was adopted as the basis of a new HTML
|
||||
specification under the umbrella of the W3C, known as HTML5. Currently,
|
||||
it appears that XHTML is losing traction, as the XHTML 2 working group has
|
||||
been disbanded and HTML5 is being implemented by all major browser vendors.
|
||||
|
||||
HTML versus XHTML
|
||||
-----------------
|
||||
|
||||
The following table gives you a quick overview of features available in
|
||||
HTML 4.01, XHTML 1.1 and HTML5. (XHTML 1.0 is not included, as it was
|
||||
superseded by XHTML 1.1 and the barely-used XHTML5.)
|
||||
|
||||
.. tabularcolumns:: |p{9cm}|p{2cm}|p{2cm}|p{2cm}|
|
||||
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| | HTML4.01 | XHTML1.1 | HTML5 |
|
||||
+=========================================+==========+==========+==========+
|
||||
| ``<tag/value/`` == ``<tag>value</tag>`` | |Y| [1]_ | |N| | |N| |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| ``<br/>`` supported | |N| | |Y| | |Y| [2]_ |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| ``<script/>`` supported | |N| | |Y| | |N| |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| should be served as `text/html` | |Y| | |N| [3]_ | |Y| |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| should be served as | |N| | |Y| | |N| |
|
||||
| `application/xhtml+xml` | | | |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| strict error handling | |N| | |Y| | |N| |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| inline SVG | |N| | |Y| | |Y| |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| inline MathML | |N| | |Y| | |Y| |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| ``<video>`` tag | |N| | |N| | |Y| |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| ``<audio>`` tag | |N| | |N| | |Y| |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
| New semantic tags like ``<article>`` | |N| | |N| | |Y| |
|
||||
+-----------------------------------------+----------+----------+----------+
|
||||
|
||||
.. [1] This is an obscure feature inherited from SGML. It is usually not
|
||||
supported by browsers, for reasons detailed above.
|
||||
.. [2] This is for compatibility with server code that generates XHTML for
|
||||
tags such as ``<br>``. It should not be used in new code.
|
||||
.. [3] XHTML 1.0 is the last XHTML standard that allows to be served
|
||||
as `text/html` for backwards compatibility reasons.
|
||||
|
||||
.. |Y| image:: _static/yes.png
|
||||
:alt: Yes
|
||||
.. |N| image:: _static/no.png
|
||||
:alt: No
|
||||
|
||||
What does "strict" mean?
|
||||
------------------------
|
||||
|
||||
HTML5 has strictly defined parsing rules, but it also specifies exactly
|
||||
how a browser should react to parsing errors - unlike XHTML, which simply
|
||||
states parsing should abort. Some people are confused by apparently
|
||||
invalid syntax that still generates the expected results (for example,
|
||||
missing end tags or unquoted attribute values).
|
||||
|
||||
Some of these work because of the lenient error handling most browsers use
|
||||
when they encounter a markup error, others are actually specified. The
|
||||
following constructs are optional in HTML5 by standard, but have to be
|
||||
supported by browsers:
|
||||
|
||||
- Wrapping the document in an ``<html>`` tag
|
||||
- Wrapping header elements in ``<head>`` or the body elements in
|
||||
``<body>``
|
||||
- Closing the ``<p>``, ``<li>``, ``<dt>``, ``<dd>``, ``<tr>``,
|
||||
``<td>``, ``<th>``, ``<tbody>``, ``<thead>``, or ``<tfoot>`` tags.
|
||||
- Quoting attributes, so long as they contain no whitespace or
|
||||
special characters (like ``<``, ``>``, ``'``, or ``"``).
|
||||
- Requiring boolean attributes to have a value.
|
||||
|
||||
This means the following page in HTML5 is perfectly valid:
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
<!doctype html>
|
||||
<title>Hello HTML5</title>
|
||||
<div class=header>
|
||||
<h1>Hello HTML5</h1>
|
||||
<p class=tagline>HTML5 is awesome
|
||||
</div>
|
||||
<ul class=nav>
|
||||
<li><a href=/index>Index</a>
|
||||
<li><a href=/downloads>Downloads</a>
|
||||
<li><a href=/about>About</a>
|
||||
</ul>
|
||||
<div class=body>
|
||||
<h2>HTML5 is probably the future</h2>
|
||||
<p>
|
||||
There might be some other things around but in terms of
|
||||
browser vendor support, HTML5 is hard to beat.
|
||||
<dl>
|
||||
<dt>Key 1
|
||||
<dd>Value 1
|
||||
<dt>Key 2
|
||||
<dd>Value 2
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
|
||||
New technologies in HTML5
|
||||
-------------------------
|
||||
|
||||
HTML5 adds many new features that make Web applications easier to write
|
||||
and to use.
|
||||
|
||||
- The ``<audio>`` and ``<video>`` tags provide a way to embed audio and
|
||||
video without complicated add-ons like QuickTime or Flash.
|
||||
- Semantic elements like ``<article>``, ``<header>``, ``<nav>``, and
|
||||
``<time>`` that make content easier to understand.
|
||||
- The ``<canvas>`` tag, which supports a powerful drawing API, reducing
|
||||
the need for server-generated images to present data graphically.
|
||||
- New form control types like ``<input type="date">`` that allow user
|
||||
agents to make entering and validating values easier.
|
||||
- Advanced JavaScript APIs like Web Storage, Web Workers, Web Sockets,
|
||||
geolocation, and offline applications.
|
||||
|
||||
Many other features have been added, as well. A good guide to new features
|
||||
in HTML5 is Mark Pilgrim's soon-to-be-published book, `Dive Into HTML5`_.
|
||||
Not all of them are supported in browsers yet, however, so use caution.
|
||||
|
||||
.. _Dive Into HTML5: http://www.diveintohtml5.org/
|
||||
|
||||
What should be used?
|
||||
--------------------
|
||||
|
||||
Currently, the answer is HTML5. There are very few reasons to use XHTML
|
||||
considering the latest developments in Web browsers. To summarize the
|
||||
reasons given above:
|
||||
|
||||
- Internet Explorer (which, sadly, currently leads in market share)
|
||||
has poor support for XHTML.
|
||||
- Many JavaScript libraries also do not support XHTML, due to the more
|
||||
complicated namespacing API it requires.
|
||||
- HTML5 adds several new features, including semantic tags and the
|
||||
long-awaited ``<audio>`` and ``<video>`` tags.
|
||||
- It has the support of most browser vendors behind it.
|
||||
- It is much easier to write, and more compact.
|
||||
|
||||
For most applications, it is undoubtedly better to use HTML5 than XHTML.
|
||||
|
|
@ -4,20 +4,20 @@ Welcome to Flask
|
|||
================
|
||||
|
||||
.. image:: _static/logo-full.png
|
||||
:alt: The Flask Logo with Subtitle
|
||||
:alt: Flask: web development, one drop at a time
|
||||
:class: floatingflask
|
||||
|
||||
Welcome to Flask's documentation. This documentation is divided in
|
||||
different parts. I would suggest to get started with the
|
||||
:ref:`installation` and then heading over to the :ref:`quickstart`.
|
||||
Welcome to Flask's documentation. This documentation is divided into
|
||||
different parts. I recommend that you get started with
|
||||
:ref:`installation` and then head over to the :ref:`quickstart`.
|
||||
Besides the quickstart there is also a more detailed :ref:`tutorial` that
|
||||
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
|
||||
:ref:`patterns` section.
|
||||
|
||||
Flask also depends on two external libraries: the `Jinja2`_ template
|
||||
engine and the `Werkzeug`_ WSGI toolkit. both of which are not documented
|
||||
Flask depends on two external libraries: the `Jinja2`_ template
|
||||
engine and the `Werkzeug`_ WSGI toolkit. These libraries are not documented
|
||||
here. If you want to dive into their documentation check out the
|
||||
following links:
|
||||
|
||||
|
|
|
|||
|
|
@ -3,44 +3,46 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
Flask is a microframework and yet it depends on external libraries. There
|
||||
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
|
||||
Flask depends on two external libraries, `Werkzeug
|
||||
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/2/>`_.
|
||||
The first one is responsible for interfacing WSGI the latter for rendering
|
||||
templates. Now you are maybe asking, what is WSGI? WSGI is a standard
|
||||
in Python that is basically responsible for ensuring that your application
|
||||
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).
|
||||
Werkzeug is a toolkit for WSGI, the standard Python interface between web
|
||||
applications and a variety of servers for both development and deployment.
|
||||
Jinja2 renders templates.
|
||||
|
||||
So how do you get all that on your computer in no time? The most kick-ass
|
||||
method is virtualenv, so let's look at that first.
|
||||
So how do you get all that on your computer quickly? There are many ways
|
||||
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 is what you want to use during development and in production if
|
||||
you have shell access. So first: what does virtualenv do? If you are
|
||||
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!
|
||||
Virtualenv is probably what you want to use during development, and in
|
||||
production too if you have shell access there.
|
||||
|
||||
It basically makes it possible to have multiple side-by-side
|
||||
"installations" of Python, each for your own project. It's not actually
|
||||
an installation but a clever way to keep things separated.
|
||||
What problem does virtualenv solve? If you like Python as I do,
|
||||
chances are you want to use it for other projects besides Flask-based
|
||||
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::
|
||||
|
||||
$ sudo easy_install virtualenv
|
||||
|
|
@ -49,18 +51,19 @@ or even better::
|
|||
|
||||
$ sudo pip install virtualenv
|
||||
|
||||
Chances are you have virtualenv installed on your system then. Maybe it's
|
||||
even in your package manager (on ubuntu try ``sudo apt-get install
|
||||
python-virtualenv``).
|
||||
One of these will probably install virtualenv on your system. Maybe it's
|
||||
even in your package manager. If you use Ubuntu, try::
|
||||
|
||||
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
|
||||
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
|
||||
your own environment. I usually create a folder and a `env` folder
|
||||
within::
|
||||
Once you have virtualenv installed, just fire up a shell and create
|
||||
your own environment. I usually create a project folder and an `env`
|
||||
folder within::
|
||||
|
||||
$ mkdir myproject
|
||||
$ cd myproject
|
||||
|
|
@ -68,14 +71,14 @@ within::
|
|||
New python executable in env/bin/python
|
||||
Installing setuptools............done.
|
||||
|
||||
Now you only have to activate it, whenever you work with it. On OS X and
|
||||
Linux do the following::
|
||||
Now, whenever you want to work on a project, you only have to activate
|
||||
the corresponding environment. On OS X and Linux, do the following::
|
||||
|
||||
$ . env/bin/activate
|
||||
|
||||
(Note the whitespace between the dot and the script name. This means
|
||||
execute this file in context of the shell. If the dot does not work for
|
||||
whatever reason in your shell, try substituting it with ``source``)
|
||||
(Note the space between the dot and the script name. The dot means that
|
||||
this script should run in the context of the current shell. If this command
|
||||
does not work in your shell, try replacing the dot with ``source``)
|
||||
|
||||
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
|
||||
------------------------
|
||||
|
||||
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::
|
||||
|
||||
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
|
||||
------------------
|
||||
|
||||
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
|
||||
to operate on a git checkout. Either way it's recommended to do that in a
|
||||
virtualenv.
|
||||
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
|
||||
to operate on a git checkout. Either way, virtualenv is recommended.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
This will pull in the dependencies and activate the git head as current
|
||||
version. Then you just have to ``git pull origin`` to get the latest
|
||||
version.
|
||||
This will pull in the dependencies and activate the git head as the current
|
||||
version inside the virtualenv. Then you just have to ``git pull origin``
|
||||
to get the latest version.
|
||||
|
||||
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
|
||||
-------------------------
|
||||
|
||||
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
|
||||
easiest way to accomplish that is downloading the `ez_setup.py`_ file and
|
||||
running it. (Double clicking should do the trick)
|
||||
On Windows, installation of `easy_install` is a little bit tricker because
|
||||
slightly different rules apply on Windows than on Unix-like systems, but
|
||||
it's not difficult. The easiest way to do it is to download the
|
||||
`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
|
||||
and other Python scripts to the path. To do that you have to add the
|
||||
Python installation's Script folder to the `PATH` variable.
|
||||
|
||||
To do that, right-click on your "Computer" desktop icon and click
|
||||
"Properties". On Windows Vista and Windows 7 then click on "Advanced System
|
||||
settings", on Windows XP click on the "Advanced" tab instead. Then click
|
||||
Next, add the `easy_install` command and other Python scripts to the
|
||||
command search path, by adding your Python installation's Scripts folder
|
||||
to the `PATH` environment variable. To do that, right-click on the
|
||||
"Computer" icon on the Desktop or in the Start menu, and choose
|
||||
"Properties". Then, on Windows Vista and Windows 7 click on "Advanced System
|
||||
settings"; on Windows XP, click on the "Advanced" tab instead. Then click
|
||||
on the "Environment variables" button and double click on the "Path"
|
||||
variable in the "System variables" section.
|
||||
|
||||
There append the path of your Python interpreter's Script folder to the
|
||||
end of the last (make sure you delimit it from existing values with a
|
||||
semicolon). Assuming you are using Python 2.6 on the default path, add
|
||||
the following value::
|
||||
variable in the "System variables" section. There append the path of your
|
||||
Python interpreter's Scripts folder; make sure you delimit it from
|
||||
existing values with a semicolon. Assuming you are using Python 2.6 on
|
||||
the default path, add the following value::
|
||||
|
||||
;C:\Python26\Scripts
|
||||
|
||||
Then you are done. To check that it worked, open the cmd and execute
|
||||
"easy_install". If you have UAC enabled it should prompt you for admin
|
||||
privileges.
|
||||
Then you are done. To check that it worked, open the Command Prompt and
|
||||
execute ``easy_install``. If you have User Account Control enabled on
|
||||
Windows Vista or Windows 7, it should prompt you for admin privileges.
|
||||
|
||||
|
||||
.. _ez_setup.py: http://peak.telecommunity.com/dist/ez_setup.py
|
||||
|
|
|
|||
|
|
@ -1,21 +1,48 @@
|
|||
License
|
||||
=======
|
||||
|
||||
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 around, the conditions are not modified and the disclaimer is
|
||||
present. Furthermore you must not use the names of the authors to promote
|
||||
derivates of the software without written consent.
|
||||
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
|
||||
around, the conditions are not modified and the disclaimer is present.
|
||||
Furthermore you must not use the names of the authors to promote derivates
|
||||
of the software without written consent.
|
||||
|
||||
.. _BSD License:
|
||||
http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29
|
||||
The full license text can be found below (:ref:`flask-license`). For the
|
||||
documentation and artwork different licenses apply.
|
||||
|
||||
.. _authors:
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
.. include:: ../AUTHORS
|
||||
|
||||
License Text
|
||||
------------
|
||||
General License Definitions
|
||||
---------------------------
|
||||
|
||||
The following section contains the full license texts for Flask and the
|
||||
documentation.
|
||||
|
||||
- "AUTHORS" hereby refers to all the authors listed in the
|
||||
:ref:`authors` section.
|
||||
|
||||
- The ":ref:`flask-license`" applies to all the sourcecode shipped as
|
||||
part of Flask (Flask itself as well as the examples and the unittests)
|
||||
as well as documentation.
|
||||
|
||||
- The ":ref:`artwork-license`" applies to the project's Horn-Logo.
|
||||
|
||||
.. _flask-license:
|
||||
|
||||
Flask License
|
||||
-------------
|
||||
|
||||
.. include:: ../LICENSE
|
||||
|
||||
|
||||
.. _artwork-license:
|
||||
|
||||
Flask Artwork License
|
||||
---------------------
|
||||
|
||||
.. include:: ../artwork/LICENSE
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Application Factories
|
|||
=====================
|
||||
|
||||
If you are already using packages and modules for your application
|
||||
(:ref:`packages`) there are couple of really nice ways to further improve
|
||||
(:ref:`packages`) there are a couple of really nice ways to further improve
|
||||
the experience. A common pattern is creating the application object when
|
||||
the module is imported. But if you move the creation of this object,
|
||||
into a function, you can then create multiple instances of this and later.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
: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
|
||||
------------------
|
||||
|
||||
|
|
|
|||
196
docs/patterns/fabric.rst
Normal file
196
docs/patterns/fabric.rst
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
.. _fabric-deployment:
|
||||
|
||||
Deploying with Fabric
|
||||
=====================
|
||||
|
||||
`Fabric`_ is a tool for Python similar to Makefiles but with the ability
|
||||
to execute commands on a remote server. In combination with a properly
|
||||
set up Python package (:ref:`larger-applications`) and a good concept for
|
||||
configurations (:ref:`config`) it is very easy to deploy Flask
|
||||
applications to external servers.
|
||||
|
||||
Before we get started, here a quick checklist of things we have to ensure
|
||||
upfront:
|
||||
|
||||
- Fabric 1.0 has to be installed locally. This tutorial assumes the
|
||||
latest version of Fabric.
|
||||
- The application already has to be a package and requires a working
|
||||
`setup.py` file (:ref:`distribute-deployment`).
|
||||
- In the following example we are using `mod_wsgi` for the remote
|
||||
servers. You can of course use your own favourite server there, but
|
||||
for this example we chose Apache + `mod_wsgi` because it's very easy
|
||||
to setup and has a simple way to reload applications without root
|
||||
access.
|
||||
|
||||
Creating the first Fabfile
|
||||
--------------------------
|
||||
|
||||
A fabfile is what controls what Fabric executes. It is named `fabfile.py`
|
||||
and executed by the `fab` command. All the functions defined in that file
|
||||
will show up as `fab` subcommands. They are executed on one or more
|
||||
hosts. These hosts can be defined either in the fabfile or on the command
|
||||
line. In this case we will add them to the fabfile.
|
||||
|
||||
This is a basic first example that has the ability to upload the current
|
||||
sourcecode to the server and install it into a already existing
|
||||
virtual environment::
|
||||
|
||||
from fabric.api import *
|
||||
|
||||
# the user to use for the remote commands
|
||||
env.user = 'appuser'
|
||||
# the servers where the commands are executed
|
||||
env.hosts = ['server1.example.com', 'server2.example.com']
|
||||
|
||||
def pack():
|
||||
# create a new source distribution as tarball
|
||||
local('python setup.py sdist --formats=gztar', capture=False)
|
||||
|
||||
def deploy():
|
||||
# figure out the release name and version
|
||||
dist = local('python setup.py --fullname').strip()
|
||||
# upload the source tarball to the temporary folder on the server
|
||||
put('sdist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz')
|
||||
# create a place where we can unzip the tarball, then enter
|
||||
# that directory and unzip it
|
||||
run('mkdir yourapplication')
|
||||
with cd('/tmp/yourapplication'):
|
||||
run('tar xzf /tmp/yourapplication.tar.gz')
|
||||
# now setup the package with our virtual environment's
|
||||
# python interpreter
|
||||
run('/var/www/yourapplication/env/bin/python setup.py install')
|
||||
# now that all is set up, delete the folder again
|
||||
run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz')
|
||||
# and finally touch the .wsgi file so that mod_wsgi triggers
|
||||
# a reload of the application
|
||||
run('touch /var/www/yourapplication.wsgi')
|
||||
|
||||
The example above is well documented and should be straightforward. Here
|
||||
a recap of the most common commands fabric provides:
|
||||
|
||||
- `run` - executes a command on a remote server
|
||||
- `local` - executes a command on the local machine
|
||||
- `put` - uploads a file to the remote server
|
||||
- `cd` - changes the directory on the serverside. This has to be used
|
||||
in combination with the `with` statement.
|
||||
|
||||
Running Fabfiles
|
||||
----------------
|
||||
|
||||
Now how do you execute that fabfile? You use the `fab` command. To
|
||||
deploy the current version of the code on the remote server you would use
|
||||
this command::
|
||||
|
||||
$ fab pack deploy
|
||||
|
||||
However this requires that our server already has the
|
||||
``/var/www/yourapplication`` folder created and
|
||||
``/var/www/yourapplication/env`` to be a virtual environment. Furthermore
|
||||
are we not creating the configuration or `.wsgi` file on the server. So
|
||||
how do we bootstrap a new server into our infrastructure?
|
||||
|
||||
This now depends on the number of servers we want to set up. If we just
|
||||
have one application server (which the majority of applications will
|
||||
have), creating a command in the fabfile for this is overkill. But
|
||||
obviously you can do that. In that case you would probably call it
|
||||
`setup` or `bootstrap` and then pass the servername explicitly on the
|
||||
command line::
|
||||
|
||||
$ fab -H newserver.example.com bootstrap
|
||||
|
||||
To setup a new server you would roughly do these steps:
|
||||
|
||||
1. Create the directory structure in ``/var/www``::
|
||||
|
||||
$ mkdir /var/www/yourapplication
|
||||
$ cd /var/www/yourapplication
|
||||
$ virtualenv --distribute env
|
||||
|
||||
2. Upload a new `application.wsgi` file to the server and the
|
||||
configuration file for the application (eg: `application.cfg`)
|
||||
|
||||
3. Create a new Apache config for `yourapplication` and activate it.
|
||||
Make sure to activate watching for changes of the `.wsgi` file so
|
||||
that we can automatically reload the application by touching it.
|
||||
(See :ref:`mod_wsgi-deployment` for more information)
|
||||
|
||||
So now the question is, where do the `application.wsgi` and
|
||||
`application.cfg` files come from?
|
||||
|
||||
The WSGI File
|
||||
-------------
|
||||
|
||||
The WSGI file has to import the application and also to set an environment
|
||||
variable so that the application knows where to look for the config. This
|
||||
is a short example that does exactly that::
|
||||
|
||||
import os
|
||||
os.environ['YOURAPPLICATION_CONFIG'] = '/var/www/yourapplication/application.cfg'
|
||||
from yourapplication import app
|
||||
|
||||
The application itself then has to initialize itself like this to look for
|
||||
the config at that environment variable::
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object('yourapplication.default_config')
|
||||
app.config.from_envvar('YOURAPPLICATION_CONFIG')
|
||||
|
||||
This approach is explained in detail in the :ref:`config` section of the
|
||||
documentation.
|
||||
|
||||
The Configuration File
|
||||
----------------------
|
||||
|
||||
Now as mentioned above, the application will find the correct
|
||||
configuration file by looking up the `YOURAPPLICATION_CONFIG` environment
|
||||
variable. So we have to put the configuration in a place where the
|
||||
application will able to find it. Configuration files have the unfriendly
|
||||
quality of being different on all computers, so you do not version them
|
||||
usually.
|
||||
|
||||
A popular approach is to store configuration files for different servers
|
||||
in a separate version control repository and check them out on all
|
||||
servers. Then symlink the file that is active for the server into the
|
||||
location where it's expected (eg: ``/var/www/yourapplication``).
|
||||
|
||||
Either way, in our case here we only expect one or two servers and we can
|
||||
upload them ahead of time by hand.
|
||||
|
||||
First Deployment
|
||||
----------------
|
||||
|
||||
Now we can do our first deployment. We have set up the servers so that
|
||||
they have their virtual environments and activated apache configs. Now we
|
||||
can pack up the application and deploy it::
|
||||
|
||||
$ fab pack deploy
|
||||
|
||||
Fabric will now connect to all servers and run the commands as written
|
||||
down in the fabfile. First it will execute pack so that we have our
|
||||
tarball ready and then it will execute deploy and upload the source code
|
||||
to all servers and install it there. Thanks to the `setup.py` file we
|
||||
will automatically pull in the required libraries into our virtual
|
||||
environment.
|
||||
|
||||
Next Steps
|
||||
----------
|
||||
|
||||
From that point onwards there is so much that can be done to make
|
||||
deployment actually fun:
|
||||
|
||||
- Create a `bootstrap` command that initializes new servers. It could
|
||||
initialize a new virtual environment, setup apache appropriately etc.
|
||||
- Put configuration files into a separate version control repository
|
||||
and symlink the active configs into place.
|
||||
- You could also put your application code into a repository and check
|
||||
out the latest version on the server and then install. That way you
|
||||
can also easily go back to older versions.
|
||||
- hook in testing functionality so that you can deploy to an external
|
||||
server and run the testsuite.
|
||||
|
||||
Working with Fabric is fun and you will notice that it's quite magical to
|
||||
type ``fab deploy`` and see your application being deployed automatically
|
||||
to one or more remote servers.
|
||||
|
||||
|
||||
.. _Fabric: http://fabfile.org/
|
||||
|
|
@ -28,8 +28,6 @@ bootstrapping code for our application::
|
|||
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
|
||||
|
||||
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
|
||||
:func:`werkzeug.secure_filename` is explained a little bit later. The
|
||||
|
|
@ -100,14 +98,23 @@ before storing it directly on the filesystem.
|
|||
>>> secure_filename('../../../../home/username/.bashrc')
|
||||
'home_username_.bashrc'
|
||||
|
||||
Now if we run that application, you will notice that uploading works, but
|
||||
you won't actually see that uploaded file. Well, you would have to
|
||||
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
|
||||
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::
|
||||
Now one last thing is missing: the serving of the uploaded files. As of
|
||||
Flask 0.5 we can use a function that does that for us::
|
||||
|
||||
from flask import send_from_directory
|
||||
|
||||
@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
|
||||
app.add_url_rule('/uploads/<filename>', 'uploaded_file',
|
||||
build_only=True)
|
||||
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
|
||||
'/uploads': UPLOAD_FOLDER
|
||||
})
|
||||
|
|
@ -118,27 +125,29 @@ If you now run the application everything should work as expected.
|
|||
Improving Uploads
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
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
|
||||
temporary location (as returned by :func:`tempfile.gettempdir`). But how
|
||||
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
|
||||
memory, but you can limit that by subclassing the request and overriding
|
||||
the Werkzeug provided :attr:`~werkzeug.BaseRequest.max_form_memory_size`
|
||||
attribute::
|
||||
memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH``
|
||||
config key::
|
||||
|
||||
from flask import Flask, Request
|
||||
|
||||
class LimitedRequest(Request):
|
||||
max_form_memory_size = 16 * 1024 * 1024
|
||||
|
||||
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.
|
||||
If a larger file is transmitted, Flask will raise an
|
||||
: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
|
||||
--------------------
|
||||
|
|
@ -158,3 +167,14 @@ following libraries for some nice examples how to do that:
|
|||
- `Plupload <http://www.plupload.com/>`_ - HTML5, Java, Flash
|
||||
- `SWFUpload <http://www.swfupload.org/>`_ - Flash
|
||||
- `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/
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
Patterns for Flask
|
||||
==================
|
||||
|
||||
Certain things are common enough that the changes are high you will find
|
||||
Certain things are common enough that the chances are high you will find
|
||||
them in most web applications. For example quite a lot of applications
|
||||
are using relational databases and user authentication. In that case,
|
||||
changes are they will open a database connection at the beginning of the
|
||||
chances are they will open a database connection at the beginning of the
|
||||
request and get the information of the currently logged in user. At the
|
||||
end of the request, the database connection is closed again.
|
||||
|
||||
|
|
@ -19,6 +19,7 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_.
|
|||
packages
|
||||
appfactories
|
||||
distribute
|
||||
fabric
|
||||
sqlite3
|
||||
sqlalchemy
|
||||
fileuploads
|
||||
|
|
@ -30,3 +31,4 @@ Snippet Archives <http://flask.pocoo.org/snippets/>`_.
|
|||
jquery
|
||||
errorpages
|
||||
lazyloading
|
||||
mongokit
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ Another method is using Google's `AJAX Libraries API
|
|||
|
||||
In this case you don't have to put jQuery into your static folder, it will
|
||||
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
|
||||
will already be in the browser cache. Downside is that if you don't have
|
||||
network connectivity during development jQuery will not load.
|
||||
|
|
@ -76,7 +76,7 @@ inside a `script` block here where different rules apply.
|
|||
In HTML the `script` tag is declared `CDATA` which means that entities
|
||||
will not be parsed. Everything until ``</script>`` is handled as script.
|
||||
This also means that there must never be any ``</`` between the script
|
||||
tags. ``|tojson`` is kindly enough to do the right thing here and
|
||||
tags. ``|tojson`` is kind enough to do the right thing here and
|
||||
escape slashes for you (``{{ "</script>"|tojson|safe }}`` is rendered as
|
||||
``"<\/script>"``).
|
||||
|
||||
|
|
@ -150,7 +150,7 @@ explanation of the little bit of code above:
|
|||
|
||||
1. ``$(function() { ... })`` specifies code that should run once the
|
||||
browser is done loading the basic parts of the page.
|
||||
2. ``#('selector')`` selects an element and lets you operate on it.
|
||||
2. ``$('selector')`` selects an element and lets you operate on it.
|
||||
3. ``element.bind('event', func)`` specifies a function that should run
|
||||
when the user clicked on the element. If that function returns
|
||||
`false`, the default behaviour will not kick in (in this case, navigate
|
||||
|
|
|
|||
144
docs/patterns/mongokit.rst
Normal file
144
docs/patterns/mongokit.rst
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
.. mongokit-pattern:
|
||||
|
||||
MongoKit in Flask
|
||||
=================
|
||||
|
||||
Using a document database rather than a full DBMS gets more common these days.
|
||||
This pattern shows how to use MongoKit, a document mapper library, to
|
||||
integrate with MongoDB.
|
||||
|
||||
This pattern requires a running MongoDB server and the MongoKit library
|
||||
installed.
|
||||
|
||||
There are two very common ways to use MongoKit. I will outline each of them
|
||||
here:
|
||||
|
||||
|
||||
Declarative
|
||||
-----------
|
||||
|
||||
The default behaviour of MongoKit is the declarative one that is based on
|
||||
common ideas from Django or the SQLAlchemy declarative extension.
|
||||
|
||||
Here an example `app.py` module for your application::
|
||||
|
||||
from flask import Flask
|
||||
from mongokit import Connection, Document
|
||||
|
||||
# configuration
|
||||
MONGODB_HOST = 'localhost'
|
||||
MONGODB_PORT = 27017
|
||||
|
||||
# create the little application object
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(__name__)
|
||||
|
||||
# connect to the database
|
||||
connection = Connection(app.config['MONGODB_HOST'],
|
||||
app.config['MONGODB_PORT'])
|
||||
|
||||
|
||||
To define your models, just subclass the `Document` class that is imported
|
||||
from MongoKit. If you've seen the SQLAlchemy pattern you may wonder why we do
|
||||
not have a session and even do not define a `init_db` function here. On the
|
||||
one hand, MongoKit does not have something like a session. This sometimes
|
||||
makes it more to type but also makes it blazingly fast. On the other hand,
|
||||
MongoDB is schemaless. This means you can modify the data structure from one
|
||||
insert query to the next without any problem. MongoKit is just schemaless
|
||||
too, but implements some validation to ensure data integrity.
|
||||
|
||||
Here is an example document (put this also into `app.py`, e.g.)::
|
||||
|
||||
def max_length(length):
|
||||
def validate(value):
|
||||
if len(value) <= length:
|
||||
return True
|
||||
raise Exception('%s must be at most %s characters long' % length)
|
||||
return validate
|
||||
|
||||
class User(Document):
|
||||
structure = {
|
||||
'name': unicode,
|
||||
'email': unicode,
|
||||
}
|
||||
validators = {
|
||||
'name': max_length(50),
|
||||
'email': max_length(120)
|
||||
}
|
||||
use_dot_notation = True
|
||||
def __repr__(self):
|
||||
return '<User %r>' % (self.name)
|
||||
|
||||
# register the User document with our current connection
|
||||
connection.register([User])
|
||||
|
||||
|
||||
This example shows you how to define your schema (named structure), a
|
||||
validator for the maximum character length and uses a special MongoKit feature
|
||||
called `use_dot_notation`. Per default MongoKit behaves like a python
|
||||
dictionary but with `use_dot_notation` set to `True` you can use your
|
||||
documents like you use models in nearly any other ORM by using dots to
|
||||
seperate 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/>`_.
|
||||
|
|
@ -59,7 +59,7 @@ following quick checklist:
|
|||
Not the object itself, but the module it is in. Do the importing at
|
||||
the *bottom* of the file.
|
||||
|
||||
Here an example `__init__.py`::
|
||||
Here's an example `__init__.py`::
|
||||
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
|
@ -109,7 +109,7 @@ Working with Modules
|
|||
--------------------
|
||||
|
||||
For larger applications with more than a dozen views it makes sense to
|
||||
split the views into module. First let's look at the typical struture of
|
||||
split the views into modules. First let's look at the typical structure of
|
||||
such an application::
|
||||
|
||||
/yourapplication
|
||||
|
|
@ -133,10 +133,10 @@ sure to place an empty `__init__.py` file in there. Let's start with the
|
|||
|
||||
First we have to create a :class:`~flask.Module` object with the name of
|
||||
the package. This works very similar to the :class:`~flask.Flask` object
|
||||
you have already worked with, it just does not support all of the method,
|
||||
you have already worked with, it just does not support all of the methods,
|
||||
but most of them are the same.
|
||||
|
||||
Long story short, here a nice and concise example::
|
||||
Long story short, here's a nice and concise example::
|
||||
|
||||
from flask import Module
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ Long story short, here a nice and concise example::
|
|||
pass
|
||||
|
||||
@admin.route('/logout')
|
||||
def login():
|
||||
def logout():
|
||||
pass
|
||||
|
||||
Do the same with the `frontend.py` and then make sure to register the
|
||||
|
|
@ -186,7 +186,7 @@ different module (say `frontend`). This would look like this::
|
|||
def index():
|
||||
return "I'm the frontend index"
|
||||
|
||||
Now let's say we only want to redirect to a different module in the same
|
||||
Now let's say we only want to redirect to a different function in the same
|
||||
module. Then we can either use the full qualified endpoint name like we
|
||||
did in the example above, or we just use the function name::
|
||||
|
||||
|
|
@ -197,3 +197,69 @@ did in the example above, or we just use the function name::
|
|||
@frontend.route('/')
|
||||
def 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
|
||||
/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.
|
||||
|
||||
.. admonition:: References to Static Folders
|
||||
|
||||
Please keep in mind that if you are using unqualified endpoints by
|
||||
default Flask will always assume the module's static folder, even if
|
||||
there is no such folder.
|
||||
|
||||
If you want to refer to the application's static folder, use a leading
|
||||
dot::
|
||||
|
||||
# this refers to the application's static folder
|
||||
url_for('.static', filename='static.css')
|
||||
|
||||
# this refers to the current module's static folder
|
||||
url_for('static', filename='static.css')
|
||||
|
||||
This is the case for all endpoints, not just static folders, but for
|
||||
static folders it's more common that you will stumble upon this because
|
||||
most applications will have a static folder in the application and not
|
||||
a specific module.
|
||||
|
|
|
|||
|
|
@ -8,9 +8,23 @@ encouraged to use a package instead of a module for your flask application
|
|||
and drop the models into a separate module (:ref:`larger-applications`).
|
||||
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:
|
||||
|
||||
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
|
||||
-----------
|
||||
|
||||
|
|
@ -68,7 +82,7 @@ Here is an example model (put this into `models.py`, e.g.)::
|
|||
self.email = email
|
||||
|
||||
def __repr__(self):
|
||||
return '<User %r>' % (self.name, self.email)
|
||||
return '<User %r>' % (self.name)
|
||||
|
||||
You can insert entries into the database like this:
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
Using SQLite 3 with Flask
|
||||
=========================
|
||||
|
||||
In Flask you can implement opening of database connections at the beginning
|
||||
of the request and closing at the end with the
|
||||
In Flask you can implement the opening of database connections at the
|
||||
beginning of the request and closing at the end with the
|
||||
:meth:`~flask.Flask.before_request` and :meth:`~flask.Flask.after_request`
|
||||
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
|
||||
from flask import g
|
||||
|
|
@ -33,7 +33,7 @@ Easy Querying
|
|||
-------------
|
||||
|
||||
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::
|
||||
|
||||
def query_db(query, args=(), one=False):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
---------
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@ see your hello world greeting.
|
|||
So what did that code do?
|
||||
|
||||
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
|
||||
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
|
||||
templates, static files and so on.
|
||||
|
|
@ -264,7 +269,8 @@ 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
|
||||
are handled like the `HTTP RFC`_ (the document describing 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
|
||||
introduction in HTTP methods and why they matter:
|
||||
|
|
@ -305,6 +311,11 @@ very common:
|
|||
`DELETE`
|
||||
Remove the information that the given location.
|
||||
|
||||
`OPTIONS`
|
||||
Provides a quick way for a requesting 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
|
||||
form might submit to the server are `GET` and `POST`. But with JavaScript
|
||||
and future HTML standards you can use other methods as well. Furthermore
|
||||
|
|
@ -368,7 +379,8 @@ package it's actually inside your package:
|
|||
/hello.html
|
||||
|
||||
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.
|
||||
|
||||
Here an example template:
|
||||
|
|
@ -409,6 +421,13 @@ Markup(u'<blink>hacker</blink>')
|
|||
>>> Markup('<em>Marked up</em> » HTML').striptags()
|
||||
u'Marked up \xbb HTML'
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
|
||||
Autoescaping is no longer enabled for all templates. The following
|
||||
extensions for templates trigger autoescaping: ``.html``, ``.htm``,
|
||||
``.xml``, ``.xhtml``. Templates loaded from string will have
|
||||
autoescaping disabled.
|
||||
|
||||
.. [#] Unsure what that :class:`~flask.g` object is? It's something you
|
||||
can store information on yourself, check the documentation of that
|
||||
object (:class:`~flask.g`) and the :ref:`sqlite3` for more
|
||||
|
|
@ -561,7 +580,7 @@ Werkzeug provides for you::
|
|||
@app.route('/upload', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
f= request.files['the_file']
|
||||
f = request.files['the_file']
|
||||
f.save('/var/www/uploads/' + secure_filename(f.filename))
|
||||
...
|
||||
|
||||
|
|
@ -628,7 +647,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
|
||||
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('/')
|
||||
def index():
|
||||
|
|
@ -652,6 +673,7 @@ sessions work::
|
|||
def logout():
|
||||
# remove the username from the session if its there
|
||||
session.pop('username', None)
|
||||
return redirect(url_for('index'))
|
||||
|
||||
# set the secret key. keep this really secret:
|
||||
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
|
||||
|
|
@ -713,3 +735,14 @@ Here are some example log calls::
|
|||
The attached :attr:`~flask.Flask.logger` is a standard logging
|
||||
:class:`~logging.Logger`, so head over to the official stdlib
|
||||
documentation 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)
|
||||
|
|
|
|||
168
docs/security.rst
Normal file
168
docs/security.rst
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
Security Considerations
|
||||
=======================
|
||||
|
||||
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
|
||||
for you, but there are a couple more you have to take care of yourself.
|
||||
|
||||
.. _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
|
||||
explicitly told otherwise. This should rule out all XSS problems caused
|
||||
in templates, but there are still other places where you have to be
|
||||
careful:
|
||||
|
||||
- generating HTML without the help of Jinja2
|
||||
- calling :class:`~flask.Markup` on data submitted by users
|
||||
- sending out HTML from uploaded files, never do that, use the
|
||||
`Content-Disposition: attachment` header to prevent that problem.
|
||||
- sending out textfiles from uploaded files. Some browsers are using
|
||||
content-type guessing based on the first few bytes so users could
|
||||
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)
|
||||
---------------------------------
|
||||
|
||||
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
|
||||
prevent it.
|
||||
|
||||
So if your authentication information is stored in cookies you have
|
||||
implicit state management. By that I mean that the state of "being logged
|
||||
in" is controlled by a cookie and that cookie is sent with each request to
|
||||
a page. Unfortunately that really means "each request" so also requests
|
||||
triggered by 3rd party sites. If you don't keep that in mind some people
|
||||
might be able to trick your application's users with social engineering to
|
||||
do stupid things without them knowing.
|
||||
|
||||
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
|
||||
attacker now creates a page that sents a post request to that page with
|
||||
some JavaScript he just has to trick some users to that page and their
|
||||
profiles will end up being deleted.
|
||||
|
||||
Imagine you would run Facebook with millions of concurrent users and
|
||||
someone would send out links to images of little kittens. When a user
|
||||
would go to that page their profiles would get deleted while they are
|
||||
looking at images of fluffy cats.
|
||||
|
||||
So how can you prevent yourself from that? Basically for each request
|
||||
that modifies content on the server you would have to either use a
|
||||
one-time token and store that in the cookie **and** also transmit it with
|
||||
the form data. After recieving the data on the server again you would
|
||||
then have to 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
|
||||
the form validation framework which does not exist in Flask.
|
||||
|
||||
.. _json-security:
|
||||
|
||||
JSON Security
|
||||
-------------
|
||||
|
||||
JSON itself is a high-level serialization format, so there is barely
|
||||
anything that could cause security problems, right? You can't declare
|
||||
recursive structures that could cause problems and the only thing that
|
||||
could possibly break are very large responses that can cause some kind of
|
||||
denial of service at the receiver's side.
|
||||
|
||||
However there is a catch. Due to how browsers work the CSRF issue comes
|
||||
up with JSON unfortunately. Fortunately there is also a weird part of the
|
||||
JavaScript specification that can be used to solve that problem easily and
|
||||
Flask is kinda doing that for you by preventing you from doing dangerous
|
||||
stuff. Unfortunately that protection is only there for
|
||||
:func:`~flask.jsonify` so you are still at risk when using other ways to
|
||||
generate JSON.
|
||||
|
||||
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
|
||||
request. Say that's exporting the names and email adresses of all your
|
||||
friends for a part of the userinterface that is written in JavaScript.
|
||||
Not very uncommon:
|
||||
|
||||
.. sourcecode:: javascript
|
||||
|
||||
[
|
||||
{"username": "admin",
|
||||
"email": "admin@localhost"}
|
||||
]
|
||||
|
||||
And it is doing that of course only as long as you are logged in and only
|
||||
for you. And it is doing that for all `GET` requests to a certain URL,
|
||||
say the URL for that request is
|
||||
``http://example.com/api/get_friends.json``.
|
||||
|
||||
So now what happens if a clever hacker is embedding this to his website
|
||||
and social engineers a victim to visiting his site:
|
||||
|
||||
.. sourcecode:: html
|
||||
|
||||
<script type=text/javascript>
|
||||
var captured = [];
|
||||
var oldArray = Array;
|
||||
function Array() {
|
||||
var obj = this, id = 0, capture = function(value) {
|
||||
obj.__defineSetter__(id++, capture);
|
||||
if (value)
|
||||
captured.push(value);
|
||||
};
|
||||
capture();
|
||||
}
|
||||
</script>
|
||||
<script type=text/javascript
|
||||
src=http://example.com/api/get_friends.json></script>
|
||||
<script type=text/javascript>
|
||||
Array = oldArray;
|
||||
// now we have all the data in the captured array.
|
||||
</script>
|
||||
|
||||
If you know a bit of JavaScript internals you might know that it's
|
||||
possible to patch constructors and register callbacks for setters. An
|
||||
attacker can use this (like above) to get all the data you exported in
|
||||
your JSON file. The browser will totally ignore the ``application/json``
|
||||
mimetype if ``text/javascript`` is defined as content type in the script
|
||||
tag and evaluate that as JavaScript. Because toplevel array elements are
|
||||
allowed (albeit useless) and we hooked in our own constructor, after that
|
||||
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
|
||||
(``{...}``) 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
|
||||
is only allowing objects as toplevel elements when using
|
||||
:func:`~flask.jsonify`. Make sure to do the same when using an ordinary
|
||||
JSON generate function.
|
||||
232
docs/signals.rst
Normal file
232
docs/signals.rst
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
.. _signals:
|
||||
|
||||
Signals
|
||||
=======
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
Starting with Flask 0.6, there is integrated support for signalling in
|
||||
Flask. This support is provided by the excellent `blinker`_ library and
|
||||
will gracefully fall back if it is not available.
|
||||
|
||||
What are signals? Signals help you decouple applications by sending
|
||||
notifications when actions occur elsewhere in the core framework or
|
||||
another Flask extensions. In short, signals allow certain senders to
|
||||
notify subscribers that something happened.
|
||||
|
||||
Flask comes with a couple of signals and other extensions might provide
|
||||
more. Also keep in mind that signals are intended to notify subscribers
|
||||
and should not encourage subscribers to modify data. You will notice that
|
||||
there are signals that appear to do the same thing like some of the
|
||||
builtin decorators do (eg: :data:`~flask.request_started` is very similar
|
||||
to :meth:`~flask.Flask.before_request`). There are however difference in
|
||||
how they work. The core :meth:`~flask.Flask.before_request` handler for
|
||||
example is executed in a specific order and is able to abort the request
|
||||
early by returning a response. In contrast all signal handlers are
|
||||
executed in undefined order and do not modify any data.
|
||||
|
||||
The big advantage of signals over handlers is that you can safely
|
||||
subscribe to them for the split of a second. These temporary
|
||||
subscriptions are helpful for unittesting for example. Say you want to
|
||||
know what templates were rendered as part of a request: signals allow you
|
||||
to do exactly that.
|
||||
|
||||
Subscribing to Signals
|
||||
----------------------
|
||||
|
||||
To subscribe to a signal, you can use the
|
||||
:meth:`~blinker.base.Signal.connect` method of a signal. The first
|
||||
argument is the function that should be called when the signal is emitted,
|
||||
the optional second argument specifies a sender. To unsubscribe from a
|
||||
signal, you can use the :meth:`~blinker.base.Signal.disconnect` method.
|
||||
|
||||
For all core Flask signals, the sender is the application that issued the
|
||||
signal. When you subscribe to a signal, be sure to also provide a sender
|
||||
unless you really want to listen for signals of all applications. This is
|
||||
especially true if you are developing an extension.
|
||||
|
||||
Here for example a helper context manager that can be used to figure out
|
||||
in a unittest which templates were rendered and what variables were passed
|
||||
to the template::
|
||||
|
||||
from flask import template_rendered
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def captured_templates(app):
|
||||
recorded = []
|
||||
def record(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(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(template, context):
|
||||
print 'Template %s is rendered with %s' % (template.name, context)
|
||||
|
||||
Core Signals
|
||||
------------
|
||||
|
||||
.. when modifying this list, also update the one in api.rst
|
||||
|
||||
The following signals exist in Flask:
|
||||
|
||||
.. data:: flask.template_rendered
|
||||
:noindex:
|
||||
|
||||
This signal is sent when a template was successfully rendered. The
|
||||
signal is invoked with the instance of the template as `template`
|
||||
and the context as dictionary (named `context`).
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def log_template_renders(sender, template, context):
|
||||
sender.logger.debug('Rendering template "%s" with context %s',
|
||||
template.name or 'string template',
|
||||
context)
|
||||
|
||||
from flask import request_started
|
||||
request_started.connect(log_template_renders, app)
|
||||
|
||||
.. data:: flask.request_started
|
||||
:noindex:
|
||||
|
||||
This signal is sent before any request processing started but when the
|
||||
request context was set up. Because the request context is already
|
||||
bound, the subscriber can access the request with the standard global
|
||||
proxies such as :class:`~flask.request`.
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def log_request(sender):
|
||||
sender.logger.debug('Request context is set up')
|
||||
|
||||
from flask import request_started
|
||||
request_started.connect(log_request, app)
|
||||
|
||||
.. data:: flask.request_finished
|
||||
:noindex:
|
||||
|
||||
This signal is sent right before the response is sent to the client.
|
||||
It is passed the response to be sent named `response`.
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def log_response(sender, response):
|
||||
sender.logger.debug('Request context is about to close down. '
|
||||
'Response: %s', response)
|
||||
|
||||
from flask import request_finished
|
||||
request_finished.connect(log_response, app)
|
||||
|
||||
.. data:: flask.got_request_exception
|
||||
:noindex:
|
||||
|
||||
This signal is sent when an exception happens during request processing.
|
||||
It is sent *before* the standard exception handling kicks in and even
|
||||
in debug mode, where no exception handling happens. The exception
|
||||
itself is passed to the subscriber as `exception`.
|
||||
|
||||
Example subscriber::
|
||||
|
||||
def log_exception(sender, exception):
|
||||
sender.logger.debug('Got exception during processing: %s', exception)
|
||||
|
||||
from flask import got_request_exception
|
||||
got_request_exception.connect(log_exception, app)
|
||||
|
||||
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||
200
docs/styleguide.rst
Normal file
200
docs/styleguide.rst
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
Pocoo Styleguide
|
||||
================
|
||||
|
||||
The Pocoo styleguide is the styleguide for all Pocoo Projects, including
|
||||
Flask. This styleguide is a requirement for Patches to Flask and a
|
||||
recommendation for Flask extensions.
|
||||
|
||||
In general the Pocoo Styleguide closely follows :pep:`8` with some small
|
||||
differences and extensions.
|
||||
|
||||
General Layout
|
||||
--------------
|
||||
|
||||
Indentation:
|
||||
4 real spaces. No tabs, no exceptions.
|
||||
|
||||
Maximum line length:
|
||||
79 characters with a soft limit for 84 if absolutely necessary. Try
|
||||
to avoid too nested code by cleverly placing `break`, `continue` and
|
||||
`return` statements.
|
||||
|
||||
Continuing long statements:
|
||||
To continue a statement you can use backslashes in which case you should
|
||||
align the next line with the last dot or equal sign, or indent four
|
||||
spaces::
|
||||
|
||||
this_is_a_very_long(function_call, 'with many parameters') \
|
||||
.that_returns_an_object_with_an_attribute
|
||||
|
||||
MyModel.query.filter(MyModel.scalar > 120) \
|
||||
.order_by(MyModel.name.desc()) \
|
||||
.limit(10)
|
||||
|
||||
If you break in a statement with parentheses or brances, 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
|
||||
(eg: ``-``, ``~`` 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 arbitary types: ``==`` and ``!=``
|
||||
- against singletones 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 functin 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
|
||||
layed out differently. If it's just one line, the closing tripple
|
||||
quote is on the same line as the opening, otherwise the text is on
|
||||
the same line as the opening quote and the tripple 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 tripple quote
|
||||
is on its own line.
|
||||
"""
|
||||
|
||||
Module header:
|
||||
The module header consists of an utf-8 encoding declaration (if non
|
||||
ASCII letters are used, but it is recommended all the time) and a
|
||||
standard docstring::
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
package.module
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
A brief description goes here.
|
||||
|
||||
:copyright: (c) YEAR by AUTHOR.
|
||||
:license: LICENSE_NAME, see LICENSE_FILE for more details.
|
||||
"""
|
||||
|
||||
Please keep in mind that proper copyrights and license files are a
|
||||
requirement for approved Flask extensions.
|
||||
|
||||
|
||||
Comments
|
||||
--------
|
||||
|
||||
Rules for comments are similar to docstrings. Both are formatted with
|
||||
reStructuredText. If a comment is used to document an attribute, put a
|
||||
colon after the opening pound sign (``#``)::
|
||||
|
||||
class User(object):
|
||||
#: the name of the user as unicode string
|
||||
name = Column(String)
|
||||
#: the sha1 hash of the password + inline salt
|
||||
pw_hash = Column(String)
|
||||
188
docs/templating.rst
Normal file
188
docs/templating.rst
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
Templates
|
||||
=========
|
||||
|
||||
Flask leverages Jinja2 as template engine. You are obviously free to use
|
||||
a different template engine, but you still have to install Jinja2 to run
|
||||
Flask itself. This requirement is necessary to enable rich extensions.
|
||||
An extension can depend on Jinja2 being present.
|
||||
|
||||
This section only gives a very quick introduction into how Jinja2
|
||||
is integrated into Flask. If you want information on the template
|
||||
engine's syntax itself, head over to the official `Jinja2 Template
|
||||
Documentation <http://jinja.pocoo.org/2/documentation/templates>`_ for
|
||||
more information.
|
||||
|
||||
Jinja Setup
|
||||
-----------
|
||||
|
||||
Unless customized, Jinja2 is configured by Flask as follows:
|
||||
|
||||
- autoescaping is enabled for all templates ending in ``.html``,
|
||||
``.htm``, ``.xml`` as well as ``.xhtml``
|
||||
- a template has the ability to opt in/out autoescaping with the
|
||||
``{% autoescape %}`` tag.
|
||||
- Flask inserts a couple of global functions and helpers into the
|
||||
Jinja2 context, additionally to the values that are present by
|
||||
default.
|
||||
|
||||
Standard Context
|
||||
----------------
|
||||
|
||||
The following global variables are available within Jinja2 templates
|
||||
by default:
|
||||
|
||||
.. data:: config
|
||||
:noindex:
|
||||
|
||||
The current configuration object (:data:`flask.config`)
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
.. data:: request
|
||||
:noindex:
|
||||
|
||||
The current request object (:class:`flask.request`)
|
||||
|
||||
.. data:: session
|
||||
:noindex:
|
||||
|
||||
The current session object (:class:`flask.session`)
|
||||
|
||||
.. data:: g
|
||||
:noindex:
|
||||
|
||||
The request-bound object for global variables (:data:`flask.g`)
|
||||
|
||||
.. function:: url_for
|
||||
:noindex:
|
||||
|
||||
The :func:`flask.url_for` function.
|
||||
|
||||
.. function:: get_flashed_messages
|
||||
:noindex:
|
||||
|
||||
The :func:`flask.get_flashed_messages` function.
|
||||
|
||||
.. admonition:: The Jinja Context Behaviour
|
||||
|
||||
These variables are added to the context of variables, they are not
|
||||
global variables. The difference is that by default these will not
|
||||
show up in the context of imported templates. This is partially caused
|
||||
by performance considerations, partially to keep things explicit.
|
||||
|
||||
What does this mean for you? If you have a macro you want to import,
|
||||
that needs to access the request object you have two possibilities:
|
||||
|
||||
1. you explicitly pass the request to the macro as parameter, or
|
||||
the attribute of the request object you are interested in.
|
||||
2. you import the macro "with context".
|
||||
|
||||
Importing with context looks like this:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% from '_helpers.html' import my_macro with context %}
|
||||
|
||||
Standard Filters
|
||||
----------------
|
||||
|
||||
These filters are available in Jinja2 additionally to the filters provided
|
||||
by Jinja2 itself:
|
||||
|
||||
.. function:: tojson
|
||||
:noindex:
|
||||
|
||||
This function converts the given object into JSON representation. This
|
||||
is for example very helpful if you try to generate JavaScript on the
|
||||
fly.
|
||||
|
||||
Note that inside `script` tags no escaping must take place, so make
|
||||
sure to disable escaping with ``|safe`` if you intend to use it inside
|
||||
`script` tags:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
<script type=text/javascript>
|
||||
doSomethingWith({{ user.username|tojson|safe }});
|
||||
</script>
|
||||
|
||||
That the ``|tojson`` filter escapes forward slashes properly for you.
|
||||
|
||||
Controlling Autoescaping
|
||||
------------------------
|
||||
|
||||
Autoescaping is the concept of automatically escaping special characters
|
||||
of you. Special characters in the sense of HTML (or XML, and thus XHTML)
|
||||
are ``&``, ``>``, ``<``, ``"`` as well as ``'``. Because these characters
|
||||
carry specific meanings in documents on their own you have to replace them
|
||||
by so called "entities" if you want to use them for text. Not doing so
|
||||
would not only cause user frustration by the inability to use these
|
||||
characters in text, but can also lead to security problems. (see
|
||||
:ref:`xss`)
|
||||
|
||||
Sometimes however you will need to disable autoescaping in templates.
|
||||
This can be the case if you want to explicitly inject HTML into pages, for
|
||||
example if they come from a system that generate secure HTML like a
|
||||
markdown to HTML converter.
|
||||
|
||||
There are three ways to accomplish that:
|
||||
|
||||
- In the Python code, wrap the HTML string in a :class:`~flask.Markup`
|
||||
object before passing it to the template. This is in general the
|
||||
recommended way.
|
||||
- Inside the template, use the ``|safe`` filter to explicitly mark a
|
||||
string as safe HTML (``{{ myvariable|safe }}``)
|
||||
- Temporarily disable the autoescape system altogether.
|
||||
|
||||
To disable the autoescape system in templates, you can use the ``{%
|
||||
autoescape %}`` block:
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
{% autoescape false %}
|
||||
<p>autoescaping is disabled here
|
||||
<p>{{ will_not_be_escaped }}
|
||||
{% endautoescape %}
|
||||
|
||||
Whenever you do this, please be very cautious about the varibles you are
|
||||
using in this block.
|
||||
|
||||
Registering Filters
|
||||
-------------------
|
||||
|
||||
If you want to register your own filters in Jinja2 you have two ways to do
|
||||
that. You can either put them by hand into the
|
||||
:attr:`~flask.Flask.jinja_env` of the application or use the
|
||||
:meth:`~flask.Flask.template_filter` decorator.
|
||||
|
||||
The two following examples work the same and both reverse an object::
|
||||
|
||||
@app.template_filter('reverse')
|
||||
def reverse_filter(s):
|
||||
return s[::-1]
|
||||
|
||||
def reverse_filter(s):
|
||||
return s[::-1]
|
||||
app.jinja_env.filters['reverse'] = reverse_filter
|
||||
|
||||
In case of the decorator the argument is optional if you want to use the
|
||||
function name as name of the filter.
|
||||
|
||||
Context Processors
|
||||
------------------
|
||||
|
||||
To inject new variables automatically into the context of a template
|
||||
context processors exist in Flask. Context processors run before the
|
||||
template is rendered and have the ability to inject new values into the
|
||||
template context. A context processor is a function that returns a
|
||||
dictionary. The keys and values of this dictionary are then merged with
|
||||
the template context::
|
||||
|
||||
@app.context_processor
|
||||
def inject_user():
|
||||
return dict(user=g.user)
|
||||
|
||||
The context processor above makes a variable called `user` available in
|
||||
the template with the value of `g.user`. This example is not very
|
||||
interesting because `g` is available in templates anyways, but it gives an
|
||||
idea how this works.
|
||||
|
|
@ -8,8 +8,8 @@ Testing Flask Applications
|
|||
Not sure where that is coming from, and it's not entirely correct, but
|
||||
also not that far from the truth. Untested applications make it hard to
|
||||
improve existing code and developers of untested applications tend to
|
||||
become pretty paranoid. If an application however has automated tests, you
|
||||
can safely change things and you will instantly know if your change broke
|
||||
become pretty paranoid. If an application has automated tests, you can
|
||||
safely change things, and you will instantly know if your change broke
|
||||
something.
|
||||
|
||||
Flask gives you a couple of ways to test applications. It mainly does
|
||||
|
|
@ -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
|
||||
:meth:`~unittest.TestCase.tearDown` method. What the test client does is
|
||||
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.
|
||||
|
||||
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 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
|
||||
login and logout pages with the required form data (username and
|
||||
password). Because the login and logout pages redirect, we tell the
|
||||
|
|
@ -200,12 +200,12 @@ suite.
|
|||
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
|
||||
the `with` statement can be used to activate a request context
|
||||
temporarily. With that you can access the :class:`~flask.request`,
|
||||
: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__)
|
||||
|
||||
|
|
@ -218,3 +218,27 @@ All the other objects that are context bound can be used the same.
|
|||
If you want to test your application with different configurations and
|
||||
there does not seem to be a good way to do that, consider switching to
|
||||
application factories (see :ref:`app-factories`).
|
||||
|
||||
|
||||
Keeping the Context Around
|
||||
--------------------------
|
||||
|
||||
.. versionadded:: 0.4
|
||||
|
||||
Sometimes it can be helpful to trigger a regular request but keep the
|
||||
context around for a little longer so that additional introspection can
|
||||
happen. With Flask 0.4 this is possible by using the
|
||||
:meth:`~flask.Flask.test_client` with a `with` block::
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
with app.test_client() as c:
|
||||
rv = c.get('/?tequila=42')
|
||||
assert request.args['tequila'] == '42'
|
||||
|
||||
If you would just be using the :meth:`~flask.Flask.test_client` without
|
||||
the `with` block, the `assert` would fail with an error because `request`
|
||||
is no longer available (because used outside of an actual request).
|
||||
Keep in mind however that :meth:`~flask.Flask.after_request` functions
|
||||
are already called at that point so your database connection and
|
||||
everything involved is probably already closed down.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
debug flag enables or disables the interactive debugger. Never leave
|
||||
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
|
||||
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'])
|
||||
|
||||
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__':
|
||||
app.run()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Bonus: Testing the Application
|
|||
==============================
|
||||
|
||||
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
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ Show Entries
|
|||
|
||||
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.
|
||||
The one with the highest id (the newest entry) on top. The rows returned
|
||||
from the cursor are tuples with the columns ordered like specified in the
|
||||
select statement. This is good enough for small applications like here,
|
||||
but you might want to convert them into a dict. If you are interested how
|
||||
to do that, check out the :ref:`easy-querying` example.
|
||||
The one with the highest id (the newest entry) will be on top. The rows
|
||||
returned from the cursor are tuples with the columns ordered like specified
|
||||
in the select statement. This is good enough for small applications like
|
||||
here, but you might want to convert them into a dict. If you are
|
||||
interested in how to do that, check out the :ref:`easy-querying` example.
|
||||
|
||||
The view function will pass the entries as dicts to the
|
||||
`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
|
||||
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
|
||||
----------------
|
||||
|
||||
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
|
||||
`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`
|
||||
page. In that case also a message is flashed that informs the user he or
|
||||
she was logged in successfully. If an error occoured the template is
|
||||
notified about that and the user asked again::
|
||||
`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`
|
||||
page. In addition, a message is flashed that informs the user that he or
|
||||
she was logged in successfully. If an error occurred, the template is
|
||||
notified about that, and the user is asked again::
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
|
|
@ -73,12 +80,12 @@ notified about that and the user asked again::
|
|||
return redirect(url_for('show_entries'))
|
||||
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
|
||||
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
|
||||
key was not in there. This is helpful because we don't have to check in
|
||||
that case if the user was logged in.
|
||||
key is not in there. This is helpful because now we don't have to check
|
||||
if the user was logged in.
|
||||
|
||||
::
|
||||
|
||||
|
|
|
|||
107
docs/unicode.rst
Normal file
107
docs/unicode.rst
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
Unicode in Flask
|
||||
================
|
||||
|
||||
Flask like Jinja2 and Werkzeug is totally unicode based when it comes to
|
||||
text. Not only these libraries, also the majority of web related Python
|
||||
libraries that deal with text. If you don't know unicode so far, you
|
||||
should probably read `The Absolute Minimum Every Software Developer
|
||||
Absolutely, Positively Must Know About Unicode and Character Sets
|
||||
<http://www.joelonsoftware.com/articles/Unicode.html>`_. This part of the
|
||||
documentation just tries to cover the very basics so that you have a
|
||||
pleasant experience with unicode related things.
|
||||
|
||||
Automatic Conversion
|
||||
--------------------
|
||||
|
||||
Flask has a few assumptions about your application (which you can change
|
||||
of course) that give you basic and painless unicode support:
|
||||
|
||||
- the encoding for text on your website is UTF-8
|
||||
- internally you will always use unicode exclusively for text except
|
||||
for literal strings with only ASCII character points.
|
||||
- encoding and decoding happens whenever you are talking over a protocol
|
||||
that requires bytes to be transmitted.
|
||||
|
||||
So what does this mean to you?
|
||||
|
||||
HTTP is based on bytes. Not only the protocol, also the system used to
|
||||
address documents on servers (so called URIs or URLs). However HTML which
|
||||
is usually transmitted on top of HTTP supports a large variety of
|
||||
character sets and which ones are used, are transmitted in an HTTP header.
|
||||
To not make this too complex Flask just assumes that if you are sending
|
||||
unicode out you want it to be UTF-8 encoded. Flask will do the encoding
|
||||
and setting of the appropriate headers for you.
|
||||
|
||||
The same is true if you are talking to databases with the help of
|
||||
SQLAlchemy or a similar ORM system. Some databases have a protocol that
|
||||
already transmits unicode and if they do not, SQLAlchemy or your other ORM
|
||||
should take care of that.
|
||||
|
||||
The Golden Rule
|
||||
---------------
|
||||
|
||||
So the rule of thumb: if you are not dealing with binary data, work with
|
||||
unicode. What does working with unicode in Python 2.x mean?
|
||||
|
||||
- as long as you are using ASCII charpoints only (basically numbers,
|
||||
some special characters of latin letters without umlauts or anything
|
||||
fancy) you can use regular string literals (``'Hello World'``).
|
||||
- if you need anything else than ASCII in a string you have to mark
|
||||
this string as unicode string by prefixing it with a lowercase `u`.
|
||||
(like ``u'Hänsel und Gretel'``)
|
||||
- if you are using non-unicode characters in your Python files you have
|
||||
to tell Python which encoding your file uses. Again, I recommend
|
||||
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
|
||||
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
|
||||
------------------------------
|
||||
|
||||
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
|
||||
with unicode interface. So for example if you want to load a file on the
|
||||
filesystem and embed it into a Jinja2 template you will have to decode it
|
||||
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
|
||||
limit yourself to UTF-8 for text files as well.
|
||||
|
||||
Anyways. To load such a file with unicode you can use the built-in
|
||||
:meth:`str.decode` method::
|
||||
|
||||
def read_file(filename, charset='utf-8'):
|
||||
with open(filename, 'r') as f:
|
||||
return f.read().decode(charset)
|
||||
|
||||
To go from unicode into a specific charset such as UTF-8 you can use the
|
||||
:meth:`unicode.encode` method::
|
||||
|
||||
def write_file(filename, contents, charset='utf-8'):
|
||||
with open(filename, 'w') as f:
|
||||
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.
|
||||
|
|
@ -14,6 +14,62 @@ This section of the documentation enumerates all the changes in Flask from
|
|||
release to release and how you can change your code to have a painless
|
||||
updating experience.
|
||||
|
||||
If you want to use the `easy_install` command to upgrade your Flask
|
||||
installation, make sure to pass it the ``-U`` parameter::
|
||||
|
||||
$ easy_install -U Flask
|
||||
|
||||
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
|
||||
dependend 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
|
||||
-----------
|
||||
|
||||
For application developers there are no changes that require changes in
|
||||
your code. In case you are developing on a Flask extension however, and
|
||||
that extension has a unittest-mode you might want to link the activation
|
||||
of that mode to the new ``TESTING`` flag.
|
||||
|
||||
Version 0.3
|
||||
-----------
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
<title>jQuery Example</title>
|
||||
<script type=text/javascript
|
||||
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>
|
||||
var $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
|
||||
</script>
|
||||
|
|
|
|||
34
flask/__init__.py
Normal file
34
flask/__init__.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask
|
||||
~~~~~
|
||||
|
||||
A microframework based on Werkzeug. It's extensively documented
|
||||
and follows best practice patterns.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
# utilities we import from Werkzeug and Jinja2 that are unused
|
||||
# in the module but are exported as public interface.
|
||||
from werkzeug import abort, redirect
|
||||
from jinja2 import Markup, escape
|
||||
|
||||
from .app import Flask, Request, Response
|
||||
from .config import Config
|
||||
from .helpers import url_for, jsonify, json_available, flash, \
|
||||
send_file, send_from_directory, get_flashed_messages, \
|
||||
get_template_attribute, make_response
|
||||
from .globals import current_app, g, request, session, _request_ctx_stack
|
||||
from .module import Module
|
||||
from .templating import render_template, render_template_string
|
||||
from .session import Session
|
||||
|
||||
# the signals
|
||||
from .signals import signals_available, template_rendered, request_started, \
|
||||
request_finished, got_request_exception
|
||||
|
||||
# only import json if it's available
|
||||
if json_available:
|
||||
from .helpers import json
|
||||
875
flask/app.py
Normal file
875
flask/app.py
Normal file
|
|
@ -0,0 +1,875 @@
|
|||
# -*- 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
|
||||
|
||||
import os
|
||||
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, NotFound
|
||||
|
||||
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:`before_request` decorator.
|
||||
self.after_request_funcs = {}
|
||||
|
||||
#: A dictionary with list of functions that are called without argument
|
||||
#: to populate the template context. They 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 = {}
|
||||
|
||||
#: 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':
|
||||
rv = self.response_class()
|
||||
rv.allow.update(rule.methods)
|
||||
return rv
|
||||
# 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_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)
|
||||
152
flask/config.py
Normal file
152
flask/config.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# -*- 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
|
||||
execfile(filename, d.__dict__)
|
||||
self.from_object(d)
|
||||
|
||||
def from_object(self, obj):
|
||||
"""Updates the values from the given object. An object can be of one
|
||||
of the following two types:
|
||||
|
||||
- a string: in this case the object with that name will be imported
|
||||
- an actual object reference: that object is used directly
|
||||
|
||||
Objects are usually either modules or classes.
|
||||
|
||||
Just the uppercase variables in that object are stored in the config
|
||||
after lowercasing. Example usage::
|
||||
|
||||
app.config.from_object('yourapplication.default_config')
|
||||
from yourapplication import default_config
|
||||
app.config.from_object(default_config)
|
||||
|
||||
You should not use this function to load the actual configuration but
|
||||
rather configuration defaults. The actual config should be loaded
|
||||
with :meth:`from_pyfile` and ideally from a location not within the
|
||||
package because the package might be installed system wide.
|
||||
|
||||
:param obj: an import name or object
|
||||
"""
|
||||
if isinstance(obj, basestring):
|
||||
obj = import_string(obj)
|
||||
for key in dir(obj):
|
||||
if key.isupper():
|
||||
self[key] = getattr(obj, key)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
|
||||
66
flask/ctx.py
Normal file
66
flask/ctx.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.ctx
|
||||
~~~~~~~~~
|
||||
|
||||
Implements the objects required to keep the context.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from .globals import _request_ctx_stack
|
||||
from .session import _NullSession
|
||||
|
||||
|
||||
class _RequestGlobals(object):
|
||||
pass
|
||||
|
||||
|
||||
class _RequestContext(object):
|
||||
"""The request context contains all request relevant information. It is
|
||||
created at the beginning of the request and pushed to the
|
||||
`_request_ctx_stack` and removed at the end of it. It will create the
|
||||
URL adapter and request object for the WSGI environment provided.
|
||||
"""
|
||||
|
||||
def __init__(self, app, environ):
|
||||
self.app = app
|
||||
self.request = app.request_class(environ)
|
||||
self.url_adapter = app.create_url_adapter(self.request)
|
||||
self.session = app.open_session(self.request)
|
||||
if self.session is None:
|
||||
self.session = _NullSession()
|
||||
self.g = _RequestGlobals()
|
||||
self.flashes = None
|
||||
|
||||
try:
|
||||
url_rule, self.request.view_args = \
|
||||
self.url_adapter.match(return_rule=True)
|
||||
self.request.url_rule = url_rule
|
||||
except HTTPException, e:
|
||||
self.request.routing_exception = e
|
||||
|
||||
def push(self):
|
||||
"""Binds the request context."""
|
||||
_request_ctx_stack.push(self)
|
||||
|
||||
def pop(self):
|
||||
"""Pops the request context."""
|
||||
_request_ctx_stack.pop()
|
||||
|
||||
def __enter__(self):
|
||||
self.push()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
# do not pop the request stack if we are in debug mode and an
|
||||
# exception happened. This will allow the debugger to still
|
||||
# access the request object in the interactive shell. Furthermore
|
||||
# the context can be force kept alive for the test client.
|
||||
# See flask.testing for how this works.
|
||||
if not self.request.environ.get('flask._preserve_context') and \
|
||||
(tb is None or not self.app.debug):
|
||||
self.pop()
|
||||
20
flask/globals.py
Normal file
20
flask/globals.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# -*- 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 werkzeug import LocalStack, LocalProxy
|
||||
|
||||
# context locals
|
||||
_request_ctx_stack = LocalStack()
|
||||
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
|
||||
request = LocalProxy(lambda: _request_ctx_stack.top.request)
|
||||
session = LocalProxy(lambda: _request_ctx_stack.top.session)
|
||||
g = LocalProxy(lambda: _request_ctx_stack.top.g)
|
||||
464
flask/helpers.py
Normal file
464
flask/helpers.py
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
# -*- 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
|
||||
from functools import wraps
|
||||
|
||||
# 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, is_resource_modified, 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 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).
|
||||
|
||||
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.
|
||||
|
||||
: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:
|
||||
file = filename_or_fp
|
||||
filename = getattr(file, 'name', None)
|
||||
if filename is not None:
|
||||
if not os.path.isabs(filename):
|
||||
filename = os.path.join(current_app.root_path, filename)
|
||||
if mimetype is None and (filename or attachment_filename):
|
||||
mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
|
||||
if mimetype is None:
|
||||
mimetype = 'application/octet-stream'
|
||||
|
||||
headers = Headers()
|
||||
if as_attachment:
|
||||
if attachment_filename is None:
|
||||
if filename is None:
|
||||
raise TypeError('filename unavailable, required for '
|
||||
'sending as attachment')
|
||||
attachment_filename = os.path.basename(filename)
|
||||
headers.add('Content-Disposition', 'attachment',
|
||||
filename=attachment_filename)
|
||||
|
||||
if current_app.use_x_sendfile and filename:
|
||||
if file is not None:
|
||||
file.close()
|
||||
headers['X-Sendfile'] = filename
|
||||
data = None
|
||||
else:
|
||||
if file is None:
|
||||
file = open(filename, 'rb')
|
||||
mtime = os.path.getmtime(filename)
|
||||
data = wrap_file(request.environ, file)
|
||||
|
||||
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
|
||||
direct_passthrough=True)
|
||||
|
||||
# if we know the file modification date, we can store it as the
|
||||
# current time to better support conditional requests. Werkzeug
|
||||
# as of 0.6.1 will override this value however in the conditional
|
||||
# response with the current time. This will be fixed in Werkzeug
|
||||
# with a new release, however many WSGI servers will still emit
|
||||
# a separate date header.
|
||||
if mtime is not None:
|
||||
rv.date = int(mtime)
|
||||
|
||||
rv.cache_control.public = True
|
||||
if cache_timeout:
|
||||
rv.cache_control.max_age = cache_timeout
|
||||
rv.expires = int(time() + cache_timeout)
|
||||
|
||||
if add_etags and filename is not None:
|
||||
rv.set_etag('flask-%s-%s-%s' % (
|
||||
os.path.getmtime(filename),
|
||||
os.path.getsize(filename),
|
||||
adler32(filename) & 0xffffffff
|
||||
))
|
||||
if conditional:
|
||||
rv = rv.make_conditional(request)
|
||||
# make sure we don't send x-sendfile for servers that
|
||||
# ignore the 304 status code for x-sendfile.
|
||||
if rv.status_code == 304:
|
||||
rv.headers.pop('x-sendfile', None)
|
||||
return rv
|
||||
|
||||
|
||||
def send_from_directory(directory, filename, **options):
|
||||
"""Send a file from a given directory with :func:`send_file`. This
|
||||
is a secure way to quickly expose static files from an upload folder
|
||||
or something similar.
|
||||
|
||||
Example usage::
|
||||
|
||||
@app.route('/uploads/<path:filename>')
|
||||
def download_file(filename):
|
||||
return send_from_directory(app.config['UPLOAD_FOLDER'],
|
||||
filename, as_attachment=True)
|
||||
|
||||
.. admonition:: Sending files and Performance
|
||||
|
||||
It is strongly recommended to activate either `X-Sendfile` support in
|
||||
your webserver or (if no authentication happens) to tell the webserver
|
||||
to serve files for the given path on its own without calling into the
|
||||
web application for improved performance.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
:param directory: the directory where all the files are stored.
|
||||
:param filename: the filename relative to that directory to
|
||||
download.
|
||||
:param options: optional keyword arguments that are directly
|
||||
forwarded to :func:`send_file`.
|
||||
"""
|
||||
filename = posixpath.normpath(filename)
|
||||
if filename.startswith(('/', '../')):
|
||||
raise NotFound()
|
||||
filename = os.path.join(directory, filename)
|
||||
if not os.path.isfile(filename):
|
||||
raise NotFound()
|
||||
return send_file(filename, conditional=True, **options)
|
||||
|
||||
|
||||
def _get_package_path(name):
|
||||
"""Returns the path to a package or cwd if that cannot be found."""
|
||||
try:
|
||||
return os.path.abspath(os.path.dirname(sys.modules[name].__file__))
|
||||
except (KeyError, AttributeError):
|
||||
return os.getcwd()
|
||||
|
||||
|
||||
class _PackageBoundObject(object):
|
||||
|
||||
def __init__(self, import_name):
|
||||
#: The name of the package or module. Do not change this once
|
||||
#: it was set by the constructor.
|
||||
self.import_name = import_name
|
||||
|
||||
#: Where is the app root located?
|
||||
self.root_path = _get_package_path(self.import_name)
|
||||
|
||||
@property
|
||||
def has_static_folder(self):
|
||||
"""This is `True` if the package bound object's container has a
|
||||
folder named ``'static'``.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
return os.path.isdir(os.path.join(self.root_path, 'static'))
|
||||
|
||||
@cached_property
|
||||
def jinja_loader(self):
|
||||
"""The Jinja loader for this package bound object.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
return FileSystemLoader(os.path.join(self.root_path, 'templates'))
|
||||
|
||||
def send_static_file(self, filename):
|
||||
"""Function used internally to send static files from the static
|
||||
folder to the browser.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
return send_from_directory(os.path.join(self.root_path, 'static'),
|
||||
filename)
|
||||
|
||||
def open_resource(self, resource):
|
||||
"""Opens a resource from the application's resource folder. To see
|
||||
how this works, consider the following folder structure::
|
||||
|
||||
/myapplication.py
|
||||
/schemal.sql
|
||||
/static
|
||||
/style.css
|
||||
/templates
|
||||
/layout.html
|
||||
/index.html
|
||||
|
||||
If you want to open the `schema.sql` file you would do the
|
||||
following::
|
||||
|
||||
with app.open_resource('schema.sql') as f:
|
||||
contents = f.read()
|
||||
do_something_with(contents)
|
||||
|
||||
:param resource: the name of the resource. To access resources within
|
||||
subfolders use forward slashes as separator.
|
||||
"""
|
||||
return open(os.path.join(self.root_path, resource), 'rb')
|
||||
42
flask/logging.py
Normal file
42
flask/logging.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.logging
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Implements the logging support for Flask.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from logging import getLogger, StreamHandler, Formatter, Logger, DEBUG
|
||||
|
||||
|
||||
def create_logger(app):
|
||||
"""Creates a logger for the given application. This logger works
|
||||
similar to a regular Python logger but changes the effective logging
|
||||
level based on the application's debug flag. Furthermore this
|
||||
function also removes all attached handlers in case there was a
|
||||
logger with the log name before.
|
||||
"""
|
||||
|
||||
class DebugLogger(Logger):
|
||||
def getEffectiveLevel(x):
|
||||
return DEBUG if app.debug else Logger.getEffectiveLevel(x)
|
||||
|
||||
class DebugHandler(StreamHandler):
|
||||
def emit(x, record):
|
||||
StreamHandler.emit(x, record) if app.debug else None
|
||||
|
||||
handler = DebugHandler()
|
||||
handler.setLevel(DEBUG)
|
||||
handler.setFormatter(Formatter(app.debug_log_format))
|
||||
logger = getLogger(app.logger_name)
|
||||
# just in case that was not a new logger, get rid of all the handlers
|
||||
# already attached to it.
|
||||
del logger.handlers[:]
|
||||
logger.__class__ = DebugLogger
|
||||
logger.addHandler(handler)
|
||||
return logger
|
||||
221
flask/module.py
Normal file
221
flask/module.py
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
# -*- 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)
|
||||
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 an example structure for a larger appliation::
|
||||
|
||||
/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 an example view module (`myapplication/views/admin.py`)::
|
||||
|
||||
from flask import Module
|
||||
|
||||
admin = Module(__name__)
|
||||
|
||||
@admin.route('/')
|
||||
def index():
|
||||
pass
|
||||
|
||||
@admin.route('/login')
|
||||
def login():
|
||||
pass
|
||||
|
||||
For a gentle introduction into modules, checkout the
|
||||
:ref:`working-with-modules` section.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
The `static_path` parameter was added and it's now possible for
|
||||
modules to refer to their own templates and static files. See
|
||||
:ref:`modules-and-resources` for more information.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
The `subdomain` parameter was added.
|
||||
|
||||
:param import_name: the name of the Python package or module
|
||||
implementing this :class:`Module`.
|
||||
:param name: the internal short name for the module. Unless specified
|
||||
the rightmost part of the import name
|
||||
:param url_prefix: an optional string that is used to prefix all the
|
||||
URL rules of this module. This can also be specified
|
||||
when registering the module with the application.
|
||||
:param subdomain: used to set the subdomain setting for URL rules that
|
||||
do not have a subdomain setting set.
|
||||
:param static_path: can be used to specify a different path for the
|
||||
static files on the web. Defaults to ``/static``.
|
||||
This does not affect the folder the files are served
|
||||
*from*.
|
||||
"""
|
||||
|
||||
def __init__(self, import_name, name=None, url_prefix=None,
|
||||
static_path=None, subdomain=None):
|
||||
if name is None:
|
||||
assert '.' in import_name, 'name required if package name ' \
|
||||
'does not point to a submodule'
|
||||
name = import_name.rsplit('.', 1)[1]
|
||||
_PackageBoundObject.__init__(self, import_name)
|
||||
self.name = name
|
||||
self.url_prefix = url_prefix
|
||||
self.subdomain = subdomain
|
||||
self._register_events = [_register_module(self, static_path)]
|
||||
|
||||
def route(self, rule, **options):
|
||||
"""Like :meth:`Flask.route` but for a module. The endpoint for the
|
||||
:func:`url_for` function is prefixed with the name of the module.
|
||||
"""
|
||||
def decorator(f):
|
||||
self.add_url_rule(rule, f.__name__, f, **options)
|
||||
return f
|
||||
return decorator
|
||||
|
||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
||||
"""Like :meth:`Flask.add_url_rule` but for a module. The endpoint for
|
||||
the :func:`url_for` function is prefixed with the name of the module.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
The `endpoint` argument is now optional and will default to the
|
||||
function name to consistent with the function of the same name
|
||||
on the application object.
|
||||
"""
|
||||
def register_rule(state):
|
||||
the_rule = rule
|
||||
if state.url_prefix:
|
||||
the_rule = state.url_prefix + rule
|
||||
options.setdefault('subdomain', state.subdomain)
|
||||
the_endpoint = endpoint
|
||||
if the_endpoint is None:
|
||||
the_endpoint = _endpoint_from_view_func(view_func)
|
||||
state.app.add_url_rule(the_rule, '%s.%s' % (self.name,
|
||||
the_endpoint),
|
||||
view_func, **options)
|
||||
self._record(register_rule)
|
||||
|
||||
def before_request(self, f):
|
||||
"""Like :meth:`Flask.before_request` but for a module. This function
|
||||
is only executed before each request that is handled by a function of
|
||||
that module.
|
||||
"""
|
||||
self._record(lambda s: s.app.before_request_funcs
|
||||
.setdefault(self.name, []).append(f))
|
||||
return f
|
||||
|
||||
def before_app_request(self, f):
|
||||
"""Like :meth:`Flask.before_request`. Such a function is executed
|
||||
before each request, even if outside of a module.
|
||||
"""
|
||||
self._record(lambda s: s.app.before_request_funcs
|
||||
.setdefault(None, []).append(f))
|
||||
return f
|
||||
|
||||
def after_request(self, f):
|
||||
"""Like :meth:`Flask.after_request` but for a module. This function
|
||||
is only executed after each request that is handled by a function of
|
||||
that module.
|
||||
"""
|
||||
self._record(lambda s: s.app.after_request_funcs
|
||||
.setdefault(self.name, []).append(f))
|
||||
return f
|
||||
|
||||
def after_app_request(self, f):
|
||||
"""Like :meth:`Flask.after_request` but for a module. Such a function
|
||||
is executed after each request, even if outside of the module.
|
||||
"""
|
||||
self._record(lambda s: s.app.after_request_funcs
|
||||
.setdefault(None, []).append(f))
|
||||
return f
|
||||
|
||||
def context_processor(self, f):
|
||||
"""Like :meth:`Flask.context_processor` but for a module. This
|
||||
function is only executed for requests handled by a module.
|
||||
"""
|
||||
self._record(lambda s: s.app.template_context_processors
|
||||
.setdefault(self.name, []).append(f))
|
||||
return f
|
||||
|
||||
def app_context_processor(self, f):
|
||||
"""Like :meth:`Flask.context_processor` but for a module. Such a
|
||||
function is executed each request, even if outside of the module.
|
||||
"""
|
||||
self._record(lambda s: s.app.template_context_processors
|
||||
.setdefault(None, []).append(f))
|
||||
return f
|
||||
|
||||
def app_errorhandler(self, code):
|
||||
"""Like :meth:`Flask.errorhandler` but for a module. This
|
||||
handler is used for all requests, even if outside of the module.
|
||||
|
||||
.. versionadded:: 0.4
|
||||
"""
|
||||
def decorator(f):
|
||||
self._record(lambda s: s.app.errorhandler(code)(f))
|
||||
return f
|
||||
return decorator
|
||||
|
||||
def _record(self, func):
|
||||
self._register_events.append(func)
|
||||
43
flask/session.py
Normal file
43
flask/session.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.session
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Implements cookie based sessions based on Werkzeug's secure cookie
|
||||
system.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from werkzeug.contrib.securecookie import SecureCookie
|
||||
|
||||
|
||||
class Session(SecureCookie):
|
||||
"""Expands the session with support for switching between permanent
|
||||
and non-permanent sessions.
|
||||
"""
|
||||
|
||||
def _get_permanent(self):
|
||||
return self.get('_permanent', False)
|
||||
|
||||
def _set_permanent(self, value):
|
||||
self['_permanent'] = bool(value)
|
||||
|
||||
permanent = property(_get_permanent, _set_permanent)
|
||||
del _get_permanent, _set_permanent
|
||||
|
||||
|
||||
class _NullSession(Session):
|
||||
"""Class used to generate nicer error messages if sessions are not
|
||||
available. Will still allow read-only access to the empty session
|
||||
but fail on setting.
|
||||
"""
|
||||
|
||||
def _fail(self, *args, **kwargs):
|
||||
raise RuntimeError('the session is unavailable because no secret '
|
||||
'key was set. Set the secret_key on the '
|
||||
'application to something unique and secret.')
|
||||
__setitem__ = __delitem__ = clear = pop = popitem = \
|
||||
update = setdefault = _fail
|
||||
del _fail
|
||||
50
flask/signals.py
Normal file
50
flask/signals.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.signals
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Implements signals based on blinker if available, otherwise
|
||||
falls silently back to a noop
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
signals_available = False
|
||||
try:
|
||||
from blinker import Namespace
|
||||
signals_available = True
|
||||
except ImportError:
|
||||
class Namespace(object):
|
||||
def signal(self, name, doc=None):
|
||||
return _FakeSignal(name, doc)
|
||||
|
||||
class _FakeSignal(object):
|
||||
"""If blinker is unavailable, create a fake class with the same
|
||||
interface that allows sending of signals but will fail with an
|
||||
error on anything else. Instead of doing anything on send, it
|
||||
will just ignore the arguments and do nothing instead.
|
||||
"""
|
||||
|
||||
def __init__(self, name, doc=None):
|
||||
self.name = name
|
||||
self.__doc__ = doc
|
||||
def _fail(self, *args, **kwargs):
|
||||
raise RuntimeError('signalling support is unavailable '
|
||||
'because the blinker library is '
|
||||
'not installed.')
|
||||
send = lambda *a, **kw: None
|
||||
connect = disconnect = has_receivers_for = receivers_for = \
|
||||
temporarily_connected_to = _fail
|
||||
del _fail
|
||||
|
||||
# the namespace for code signals. If you are not flask code, do
|
||||
# not put signals in here. Create your own namespace instead.
|
||||
_signals = Namespace()
|
||||
|
||||
|
||||
# core signals. For usage examples grep the sourcecode or consult
|
||||
# the API documentation in docs/api.rst as well as docs/signals.rst
|
||||
template_rendered = _signals.signal('template-rendered')
|
||||
request_started = _signals.signal('request-started')
|
||||
request_finished = _signals.signal('request-finished')
|
||||
got_request_exception = _signals.signal('got-request-exception')
|
||||
96
flask/templating.py
Normal file
96
flask/templating.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.templating
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Implements the bridge to Jinja2.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja2 import BaseLoader, FileSystemLoader, 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):
|
||||
loader = None
|
||||
try:
|
||||
module, name = template.split('/', 1)
|
||||
loader = self.app.modules[module].jinja_loader
|
||||
except (ValueError, KeyError):
|
||||
pass
|
||||
# if there was a module and it has a loader, try this first
|
||||
if loader is not None:
|
||||
try:
|
||||
return loader.get_source(environment, name)
|
||||
except TemplateNotFound:
|
||||
pass
|
||||
# fall back to application loader if module failed
|
||||
return self.app.jinja_loader.get_source(environment, template)
|
||||
|
||||
def list_templates(self):
|
||||
result = self.app.jinja_loader.list_templates()
|
||||
for name, module in self.app.modules.iteritems():
|
||||
if module.jinja_loader is not None:
|
||||
for template in module.jinja_loader.list_templates():
|
||||
result.append('%s/%s' % (name, template))
|
||||
return result
|
||||
|
||||
|
||||
def _render(template, context, app):
|
||||
"""Renders the template and fires the signal"""
|
||||
rv = template.render(context)
|
||||
template_rendered.send(app, template=template, context=context)
|
||||
return rv
|
||||
|
||||
|
||||
def render_template(template_name, **context):
|
||||
"""Renders a template from the template folder with the given
|
||||
context.
|
||||
|
||||
:param template_name: the name of the template to be rendered
|
||||
:param context: the variables that should be available in the
|
||||
context of the template.
|
||||
"""
|
||||
ctx = _request_ctx_stack.top
|
||||
ctx.app.update_template_context(context)
|
||||
return _render(ctx.app.jinja_env.get_template(template_name),
|
||||
context, ctx.app)
|
||||
|
||||
|
||||
def render_template_string(source, **context):
|
||||
"""Renders a template from the given template source string
|
||||
with the given context.
|
||||
|
||||
:param template_name: the sourcecode of the template to be
|
||||
rendered
|
||||
:param context: the variables that should be available in the
|
||||
context of the template.
|
||||
"""
|
||||
ctx = _request_ctx_stack.top
|
||||
ctx.app.update_template_context(context)
|
||||
return _render(ctx.app.jinja_env.from_string(source),
|
||||
context, ctx.app)
|
||||
45
flask/testing.py
Normal file
45
flask/testing.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.testing
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Implements test support helpers. This module is lazily imported
|
||||
and usually not used in production environments.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from werkzeug import Client
|
||||
from flask import _request_ctx_stack
|
||||
|
||||
|
||||
class FlaskClient(Client):
|
||||
"""Works like a regular Werkzeug test client but has some
|
||||
knowledge about how Flask works to defer the cleanup of the
|
||||
request context stack to the end of a with body when used
|
||||
in a with statement.
|
||||
"""
|
||||
|
||||
preserve_context = context_preserved = False
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
if self.context_preserved:
|
||||
_request_ctx_stack.pop()
|
||||
self.context_preserved = False
|
||||
kwargs.setdefault('environ_overrides', {}) \
|
||||
['flask._preserve_context'] = self.preserve_context
|
||||
old = _request_ctx_stack.top
|
||||
try:
|
||||
return Client.open(self, *args, **kwargs)
|
||||
finally:
|
||||
self.context_preserved = _request_ctx_stack.top is not old
|
||||
|
||||
def __enter__(self):
|
||||
self.preserve_context = True
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.preserve_context = False
|
||||
if self.context_preserved:
|
||||
_request_ctx_stack.pop()
|
||||
88
flask/wrappers.py
Normal file
88
flask/wrappers.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.wrappers
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Implements the WSGI wrappers (request and response).
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from werkzeug import Request as RequestBase, Response as ResponseBase, \
|
||||
cached_property
|
||||
|
||||
from .helpers import json, _assert_have_json
|
||||
from .globals import _request_ctx_stack
|
||||
|
||||
|
||||
class Request(RequestBase):
|
||||
"""The request object used by default in flask. Remembers the
|
||||
matched endpoint and view arguments.
|
||||
|
||||
It is what ends up as :class:`~flask.request`. If you want to replace
|
||||
the request object used you can subclass this and set
|
||||
:attr:`~flask.Flask.request_class` to your subclass.
|
||||
"""
|
||||
|
||||
#: the internal URL rule that matched the request. This can be
|
||||
#: useful to inspect which methods are allowed for the URL from
|
||||
#: a before/after handler (``request.url_rule.methods``) etc.
|
||||
#:
|
||||
#: .. versionadded:: 0.6
|
||||
url_rule = None
|
||||
|
||||
#: a dict of view arguments that matched the request. If an exception
|
||||
#: happened when matching, this will be `None`.
|
||||
view_args = None
|
||||
|
||||
#: if matching the URL failed, this is the exception that will be
|
||||
#: raised / was raised as part of the request handling. This is
|
||||
#: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
|
||||
#: something similar.
|
||||
routing_exception = None
|
||||
|
||||
@property
|
||||
def max_content_length(self):
|
||||
"""Read-only view of the `MAX_CONTENT_LENGTH` config key."""
|
||||
ctx = _request_ctx_stack.top
|
||||
if ctx is not None:
|
||||
return ctx.app.config['MAX_CONTENT_LENGTH']
|
||||
|
||||
@property
|
||||
def endpoint(self):
|
||||
"""The endpoint that matched the request. This in combination with
|
||||
:attr:`view_args` can be used to reconstruct the same or a
|
||||
modified URL. If an exception happened when matching, this will
|
||||
be `None`.
|
||||
"""
|
||||
if self.url_rule is not None:
|
||||
return self.url_rule.endpoint
|
||||
|
||||
@property
|
||||
def module(self):
|
||||
"""The name of the current module"""
|
||||
if self.url_rule and '.' in self.url_rule.endpoint:
|
||||
return self.url_rule.endpoint.rsplit('.', 1)[0]
|
||||
|
||||
@cached_property
|
||||
def json(self):
|
||||
"""If the mimetype is `application/json` this will contain the
|
||||
parsed JSON data.
|
||||
"""
|
||||
if __debug__:
|
||||
_assert_have_json()
|
||||
if self.mimetype == 'application/json':
|
||||
return json.loads(self.data)
|
||||
|
||||
|
||||
class Response(ResponseBase):
|
||||
"""The response object that is used by default in flask. Works like the
|
||||
response object from Werkzeug but is set to have a 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'
|
||||
14
setup.py
14
setup.py
|
|
@ -41,9 +41,16 @@ Links
|
|||
from setuptools import setup
|
||||
|
||||
|
||||
def run_tests():
|
||||
import os, sys
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'tests'))
|
||||
from flask_tests import suite
|
||||
return suite()
|
||||
|
||||
|
||||
setup(
|
||||
name='Flask',
|
||||
version='0.4',
|
||||
version='0.6',
|
||||
url='http://github.com/mitsuhiko/flask/',
|
||||
license='BSD',
|
||||
author='Armin Ronacher',
|
||||
|
|
@ -51,7 +58,7 @@ setup(
|
|||
description='A microframework based on Werkzeug, Jinja2 '
|
||||
'and good intentions',
|
||||
long_description=__doc__,
|
||||
py_modules=['flask'],
|
||||
packages=['flask'],
|
||||
zip_safe=False,
|
||||
platforms='any',
|
||||
install_requires=[
|
||||
|
|
@ -67,5 +74,6 @@ setup(
|
|||
'Programming Language :: Python',
|
||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
]
|
||||
],
|
||||
test_suite='__main__.run_tests'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ import sys
|
|||
import flask
|
||||
import unittest
|
||||
import tempfile
|
||||
from logging import StreamHandler
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from werkzeug import parse_date, parse_options_header
|
||||
from werkzeug import parse_date, parse_options_header, http_date
|
||||
from werkzeug.exceptions import NotFound
|
||||
from jinja2 import TemplateNotFound
|
||||
from cStringIO import StringIO
|
||||
|
||||
|
||||
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, 'minitwit'))
|
||||
|
|
@ -57,6 +59,7 @@ class ContextTestCase(unittest.TestCase):
|
|||
assert index() == 'Hello World!'
|
||||
with app.test_request_context('/meh'):
|
||||
assert meh() == 'http://localhost/meh'
|
||||
assert flask._request_ctx_stack.top is None
|
||||
|
||||
def test_manual_context_binding(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
|
@ -75,9 +78,48 @@ class ContextTestCase(unittest.TestCase):
|
|||
else:
|
||||
assert 0, 'expected runtime error'
|
||||
|
||||
def test_test_client_context_binding(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/')
|
||||
def index():
|
||||
flask.g.value = 42
|
||||
return 'Hello World!'
|
||||
|
||||
@app.route('/other')
|
||||
def other():
|
||||
1/0
|
||||
|
||||
with app.test_client() as c:
|
||||
resp = c.get('/')
|
||||
assert flask.g.value == 42
|
||||
assert resp.data == 'Hello World!'
|
||||
assert resp.status_code == 200
|
||||
|
||||
resp = c.get('/other')
|
||||
assert not hasattr(flask.g, 'value')
|
||||
assert 'Internal Server Error' in resp.data
|
||||
assert resp.status_code == 500
|
||||
flask.g.value = 23
|
||||
|
||||
try:
|
||||
flask.g.value
|
||||
except (AttributeError, RuntimeError):
|
||||
pass
|
||||
else:
|
||||
raise AssertionError('some kind of exception expected')
|
||||
|
||||
|
||||
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_request_dispatching(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/')
|
||||
|
|
@ -91,7 +133,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert c.get('/').data == 'GET'
|
||||
rv = c.post('/')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
|
||||
rv = c.head('/')
|
||||
assert rv.status_code == 200
|
||||
assert not rv.data # head truncates
|
||||
|
|
@ -99,7 +141,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert c.get('/more').data == 'GET'
|
||||
rv = c.delete('/more')
|
||||
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):
|
||||
app = flask.Flask(__name__)
|
||||
|
|
@ -115,7 +157,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert c.get('/').data == 'GET'
|
||||
rv = c.post('/')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD']
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS']
|
||||
rv = c.head('/')
|
||||
assert rv.status_code == 200
|
||||
assert not rv.data # head truncates
|
||||
|
|
@ -123,7 +165,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert c.get('/more').data == 'GET'
|
||||
rv = c.delete('/more')
|
||||
assert rv.status_code == 405
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']
|
||||
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
|
||||
|
||||
def test_session(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
|
@ -140,6 +182,20 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert c.post('/set', data={'value': '42'}).data == 'value set'
|
||||
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):
|
||||
app = flask.Flask(__name__)
|
||||
def expect_exception(f, *args, **kwargs):
|
||||
|
|
@ -240,6 +296,61 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert 'after' in evts
|
||||
assert rv == 'request|after'
|
||||
|
||||
def test_after_request_errors(self):
|
||||
app = flask.Flask(__name__)
|
||||
called = []
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
called.append(True)
|
||||
return response
|
||||
@app.route('/')
|
||||
def fails():
|
||||
1/0
|
||||
rv = app.test_client().get('/')
|
||||
assert rv.status_code == 500
|
||||
assert 'Internal Server Error' in rv.data
|
||||
assert len(called) == 1
|
||||
|
||||
def test_after_request_handler_error(self):
|
||||
called = []
|
||||
app = flask.Flask(__name__)
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
called.append(True)
|
||||
1/0
|
||||
return response
|
||||
@app.route('/')
|
||||
def fails():
|
||||
1/0
|
||||
rv = app.test_client().get('/')
|
||||
assert rv.status_code == 500
|
||||
assert 'Internal Server Error' in rv.data
|
||||
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 after1(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):
|
||||
app = flask.Flask(__name__)
|
||||
@app.errorhandler(404)
|
||||
|
|
@ -260,7 +371,7 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert rv.data == 'not found'
|
||||
rv = c.get('/error')
|
||||
assert rv.status_code == 500
|
||||
assert 'internal server error' in rv.data
|
||||
assert 'internal server error' == rv.data
|
||||
|
||||
def test_response_creation(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
|
@ -282,6 +393,24 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
|||
assert rv.status_code == 400
|
||||
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):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/hello/<name>', methods=['POST'])
|
||||
|
|
@ -367,6 +496,21 @@ class JSONTestCase(unittest.TestCase):
|
|||
rv = render('{{ "<\0/script>"|tojson|safe }}')
|
||||
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):
|
||||
|
||||
|
|
@ -381,6 +525,30 @@ class TemplatingTestCase(unittest.TestCase):
|
|||
rv = app.test_client().get('/')
|
||||
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):
|
||||
text = '<p>Hello World!'
|
||||
app = flask.Flask(__name__)
|
||||
|
|
@ -398,6 +566,14 @@ class TemplatingTestCase(unittest.TestCase):
|
|||
'<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):
|
||||
app = flask.Flask(__name__)
|
||||
with app.test_request_context():
|
||||
|
|
@ -469,6 +645,18 @@ class ModuleTestCase(unittest.TestCase):
|
|||
assert c.get('/admin/login').data == 'admin login'
|
||||
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):
|
||||
catched = []
|
||||
app = flask.Flask(__name__)
|
||||
|
|
@ -536,6 +724,77 @@ class ModuleTestCase(unittest.TestCase):
|
|||
app.register_module(admin, url_prefix='/admin')
|
||||
assert app.test_client().get('/admin/').data == '42'
|
||||
|
||||
def test_error_handling(self):
|
||||
app = flask.Flask(__name__)
|
||||
admin = flask.Module(__name__, 'admin')
|
||||
@admin.app_errorhandler(404)
|
||||
def not_found(e):
|
||||
return 'not found', 404
|
||||
@admin.app_errorhandler(500)
|
||||
def internal_server_error(e):
|
||||
return 'internal server error', 500
|
||||
@admin.route('/')
|
||||
def index():
|
||||
flask.abort(404)
|
||||
@admin.route('/error')
|
||||
def error():
|
||||
1 // 0
|
||||
app.register_module(admin)
|
||||
c = app.test_client()
|
||||
rv = c.get('/')
|
||||
assert rv.status_code == 404
|
||||
assert rv.data == 'not found'
|
||||
rv = c.get('/error')
|
||||
assert rv.status_code == 500
|
||||
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/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:
|
||||
rv = f('/etc/passwd')
|
||||
except NotFound:
|
||||
pass
|
||||
else:
|
||||
assert 0, 'expected exception'
|
||||
try:
|
||||
rv = f('../__init__.py')
|
||||
except NotFound:
|
||||
pass
|
||||
else:
|
||||
assert 0, 'expected exception'
|
||||
|
||||
|
||||
class SendfileTestCase(unittest.TestCase):
|
||||
|
||||
|
|
@ -620,12 +879,22 @@ class SendfileTestCase(unittest.TestCase):
|
|||
|
||||
class LoggingTestCase(unittest.TestCase):
|
||||
|
||||
def test_logger_cache(self):
|
||||
app = flask.Flask(__name__)
|
||||
logger1 = app.logger
|
||||
assert app.logger is logger1
|
||||
assert logger1.name == __name__
|
||||
app.logger_name = __name__ + '/test_logger_cache'
|
||||
assert app.logger is not logger1
|
||||
|
||||
def test_debug_log(self):
|
||||
app = flask.Flask(__name__)
|
||||
app.debug = True
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
app.logger.warning('the standard library is dead')
|
||||
app.logger.debug('this is a debug statement')
|
||||
return ''
|
||||
|
||||
@app.route('/exc')
|
||||
|
|
@ -636,9 +905,10 @@ class LoggingTestCase(unittest.TestCase):
|
|||
with catch_stderr() as err:
|
||||
rv = c.get('/')
|
||||
out = err.getvalue()
|
||||
assert 'WARNING in flask_tests,' in out
|
||||
assert 'WARNING in flask_tests [' in out
|
||||
assert 'flask_tests.py' in out
|
||||
assert 'the standard library is dead' in out
|
||||
assert 'this is a debug statement' in out
|
||||
|
||||
with catch_stderr() as err:
|
||||
try:
|
||||
|
|
@ -649,9 +919,9 @@ class LoggingTestCase(unittest.TestCase):
|
|||
assert False, 'debug log ate the exception'
|
||||
|
||||
def test_exception_logging(self):
|
||||
from logging import StreamHandler
|
||||
out = StringIO()
|
||||
app = flask.Flask(__name__)
|
||||
app.logger_name = 'flask_tests/test_exception_logging'
|
||||
app.logger.addHandler(StreamHandler(out))
|
||||
|
||||
@app.route('/')
|
||||
|
|
@ -738,6 +1008,141 @@ class ConfigTestCase(unittest.TestCase):
|
|||
os.environ = env
|
||||
|
||||
|
||||
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_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:
|
||||
rv = 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():
|
||||
from minitwit_tests import MiniTwitTestCase
|
||||
from flaskr_tests import FlaskrTestCase
|
||||
|
|
@ -749,8 +1154,11 @@ def suite():
|
|||
suite.addTest(unittest.makeSuite(SendfileTestCase))
|
||||
suite.addTest(unittest.makeSuite(LoggingTestCase))
|
||||
suite.addTest(unittest.makeSuite(ConfigTestCase))
|
||||
suite.addTest(unittest.makeSuite(SubdomainTestCase))
|
||||
if flask.json_available:
|
||||
suite.addTest(unittest.makeSuite(JSONTestCase))
|
||||
if flask.signals_available:
|
||||
suite.addTest(unittest.makeSuite(TestSignals))
|
||||
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
|
||||
suite.addTest(unittest.makeSuite(FlaskrTestCase))
|
||||
return suite
|
||||
|
|
|
|||
97
tests/flaskext_test.py
Normal file
97
tests/flaskext_test.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# -*- 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 tempfile, subprocess, urllib2, os
|
||||
|
||||
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/'
|
||||
tdir = tempfile.mkdtemp()
|
||||
|
||||
|
||||
def run_tests(checkout_dir):
|
||||
cmd = ['tox']
|
||||
return subprocess.call(cmd, cwd=checkout_dir,
|
||||
stdout=open(os.path.join(tdir, 'tox.log'), 'w'),
|
||||
stderr=subprocess.STDOUT)
|
||||
|
||||
|
||||
def get_test_command(checkout_dir):
|
||||
files = set(os.listdir(checkout_dir))
|
||||
if 'Makefile' in files:
|
||||
return 'make test'
|
||||
elif 'conftest.py' in files:
|
||||
return 'py.test'
|
||||
else:
|
||||
return 'nosetests'
|
||||
|
||||
|
||||
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(ext):
|
||||
name = ext['name']
|
||||
root = os.path.join(tdir, name)
|
||||
os.mkdir(root)
|
||||
checkout_path = PackageIndex().download(ext['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
|
||||
return path
|
||||
|
||||
|
||||
tox_template = """[tox]
|
||||
envlist=py26
|
||||
|
||||
[testenv]
|
||||
commands=
|
||||
%s
|
||||
downloadcache=
|
||||
%s
|
||||
"""
|
||||
|
||||
def create_tox_ini(checkout_path):
|
||||
tox_path = os.path.join(checkout_path, 'tox.ini')
|
||||
if not os.path.exists(tox_path):
|
||||
with open(tox_path, 'w') as f:
|
||||
f.write(tox_template % (get_test_command(checkout_path), tdir))
|
||||
|
||||
# XXX command line
|
||||
only_approved = True
|
||||
|
||||
def test_all_extensions(only_approved=only_approved):
|
||||
for ext in fetch_extensions_list():
|
||||
if ext['approved'] or not only_approved:
|
||||
checkout_path = checkout_extension(ext)
|
||||
create_tox_ini(checkout_path)
|
||||
ret = run_tests(checkout_path)
|
||||
yield ext['name'], ret
|
||||
|
||||
def main():
|
||||
for name, ret in test_all_extensions():
|
||||
print name, ret
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
7
tests/moduleapp/__init__.py
Normal file
7
tests/moduleapp/__init__.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
from moduleapp.apps.admin import admin
|
||||
from moduleapp.apps.frontend import frontend
|
||||
app.register_module(admin)
|
||||
app.register_module(frontend)
|
||||
0
tests/moduleapp/apps/__init__.py
Normal file
0
tests/moduleapp/apps/__init__.py
Normal file
9
tests/moduleapp/apps/admin/__init__.py
Normal file
9
tests/moduleapp/apps/admin/__init__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from flask import Module, render_template
|
||||
|
||||
|
||||
admin = Module(__name__, url_prefix='/admin')
|
||||
|
||||
|
||||
@admin.route('/')
|
||||
def index():
|
||||
return render_template('admin/index.html')
|
||||
1
tests/moduleapp/apps/admin/static/css/test.css
Normal file
1
tests/moduleapp/apps/admin/static/css/test.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
/* nested file */
|
||||
1
tests/moduleapp/apps/admin/static/test.txt
Normal file
1
tests/moduleapp/apps/admin/static/test.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Admin File
|
||||
1
tests/moduleapp/apps/admin/templates/index.html
Normal file
1
tests/moduleapp/apps/admin/templates/index.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
Hello from the Admin
|
||||
9
tests/moduleapp/apps/frontend/__init__.py
Normal file
9
tests/moduleapp/apps/frontend/__init__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from flask import Module, render_template
|
||||
|
||||
|
||||
frontend = Module(__name__)
|
||||
|
||||
|
||||
@frontend.route('/')
|
||||
def index():
|
||||
return render_template('frontend/index.html')
|
||||
1
tests/moduleapp/apps/frontend/templates/index.html
Normal file
1
tests/moduleapp/apps/frontend/templates/index.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
Hello from the Frontend
|
||||
1
tests/templates/mail.txt
Normal file
1
tests/templates/mail.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
{{ foo}} Mail
|
||||
1
tests/templates/nested/nested.txt
Normal file
1
tests/templates/nested/nested.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
I'm nested
|
||||
1
tests/templates/simple_template.html
Normal file
1
tests/templates/simple_template.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<h1>{{ whiskey }}</h1>
|
||||
Loading…
Add table
Add a link
Reference in a new issue