forked from orbit-oss/flask
Merge branch 'master' of github.com:mitsuhiko/flask
This commit is contained in:
commit
3249eeb438
32 changed files with 626 additions and 259 deletions
16
CHANGES
16
CHANGES
|
|
@ -8,6 +8,8 @@ Version 0.9
|
|||
|
||||
Relase date to be decided, codename to be chosen.
|
||||
|
||||
- The :func:`flask.Request.on_json_loading_failed` now returns a JSON formatted
|
||||
response by default.
|
||||
- The :func:`flask.url_for` function now can generate anchors to the
|
||||
generated links.
|
||||
- The :func:`flask.url_for` function now can also explicitly generate
|
||||
|
|
@ -48,7 +50,19 @@ Relase date to be decided, codename to be chosen.
|
|||
- Added :meth:`flask.Flask.app_context` which works very similar to the
|
||||
request context but only provides access to the current application. This
|
||||
also adds support for URL generation without an active request context.
|
||||
|
||||
- View functions can now return a tuple with the first instance being an
|
||||
instance of :class:`flask.Response`. This allows for returning
|
||||
``jsonify(error="error msg"), 400`` from a view function.
|
||||
- :class:`flask.Flask` now provides a `get_send_file_options` hook for
|
||||
subclasses to override behavior of serving static files from Flask when using
|
||||
:meth:`flask.Flask.send_static_file` based on keywords in
|
||||
:func:`flask.helpers.send_file`. This hook is provided a filename, which for
|
||||
example allows changing cache controls by file extension. The default
|
||||
max-age for `send_static_file` can be configured through a new
|
||||
``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, regardless of whether
|
||||
the `get_send_file_options` hook is used.
|
||||
- Fixed an assumption in sessions implementation which could break message
|
||||
flashing on sessions implementations which use external storage.
|
||||
|
||||
Version 0.8.1
|
||||
-------------
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2010 by Armin Ronacher and contributors. See AUTHORS
|
||||
Copyright (c) 2012 by Armin Ronacher and contributors. See AUTHORS
|
||||
for more details.
|
||||
|
||||
Some rights reserved.
|
||||
|
|
|
|||
6
Makefile
6
Makefile
|
|
@ -22,7 +22,6 @@ clean-pyc:
|
|||
find . -name '*.pyo' -exec rm -f {} +
|
||||
find . -name '*~' -exec rm -f {} +
|
||||
|
||||
# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html
|
||||
upload-docs:
|
||||
$(MAKE) -C docs html dirhtml latex epub
|
||||
$(MAKE) -C docs/_build/latex all-pdf
|
||||
|
|
@ -31,7 +30,10 @@ upload-docs:
|
|||
rsync -a docs/_build/latex/Flask.pdf pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.pdf
|
||||
rsync -a docs/_build/flask-docs.zip pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.zip
|
||||
rsync -a docs/_build/epub/Flask.epub pocoo.org:/var/www/flask.pocoo.org/docs/flask-docs.epub
|
||||
@echo 'Building .mobi from .epub...'
|
||||
|
||||
# ebook-convert docs: http://manual.calibre-ebook.com/cli/ebook-convert.html
|
||||
ebook:
|
||||
@echo 'Using .epub from `make upload-docs` to create .mobi.'
|
||||
@echo 'Command `ebook-covert` is provided by calibre package.'
|
||||
@echo 'Requires X-forwarding for Qt features used in conversion (ssh -X).'
|
||||
@echo 'Do not mind "Invalid value for ..." CSS errors if .mobi renders.'
|
||||
|
|
|
|||
67
docs/advanced_foreword.rst
Normal file
67
docs/advanced_foreword.rst
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
Foreword for Experienced Programmers
|
||||
====================================
|
||||
|
||||
This chapter is for programmers who have worked with other frameworks in the
|
||||
past, and who may have more specific or esoteric concerns that the typical
|
||||
user.
|
||||
|
||||
Threads in Flask
|
||||
----------------
|
||||
|
||||
One of the design decisions with Flask was that simple tasks should be simple;
|
||||
they should not take a lot of code and yet they should not limit you. Because
|
||||
of that we made a few design choices that some people might find surprising or
|
||||
unorthodox. For example, Flask uses thread-local objects internally so that
|
||||
you don’t have to pass objects around from function to function within a
|
||||
request in order to stay threadsafe. While this is a really easy approach and
|
||||
saves you a lot of time, it might also cause some troubles for very large
|
||||
applications because changes on these thread-local objects can happen anywhere
|
||||
in the same thread. In order to solve these problems we don’t hide the thread
|
||||
locals for you but instead embrace them and provide you with a lot of tools to
|
||||
make it as pleasant as possible to work with them.
|
||||
|
||||
Web Development is Dangerous
|
||||
----------------------------
|
||||
|
||||
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 securely.
|
||||
|
||||
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
|
||||
cause security problems.
|
||||
|
||||
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 security in mind when doing web development.
|
||||
|
||||
The Status of Python 3
|
||||
----------------------
|
||||
|
||||
Currently the Python community is in the process of improving libraries to
|
||||
support the new iteration of the Python programming language. While the
|
||||
situation is greatly improving there are still some issues that make it
|
||||
hard for us to switch over to Python 3 just now. These problems are
|
||||
partially caused by changes in the language that went unreviewed for too
|
||||
long, partially also because we have not quite worked out how the lower-
|
||||
level API should change to account for the Unicode differences in Python 3.
|
||||
|
||||
Werkzeug and Flask will be ported to Python 3 as soon as a solution for
|
||||
the changes 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. If you plan on upgrading to Python 3 in the near future we
|
||||
strongly recommend that you read `How to write forwards compatible
|
||||
Python code <http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/>`_.
|
||||
|
|
@ -525,7 +525,7 @@ Variable parts are passed to the view function as keyword arguments.
|
|||
The following converters are available:
|
||||
|
||||
=========== ===============================================
|
||||
`unicode` accepts any text without a slash (the default)
|
||||
`string` accepts any text without a slash (the default)
|
||||
`int` accepts integers
|
||||
`float` like `int` but for floating point values
|
||||
`path` like the default but also accepts slashes
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ master_doc = 'index'
|
|||
|
||||
# General information about the project.
|
||||
project = u'Flask'
|
||||
copyright = u'2010, Armin Ronacher'
|
||||
copyright = u'2012, Armin Ronacher'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
|
|
|||
|
|
@ -110,6 +110,13 @@ The following configuration values are used internally by Flask:
|
|||
reject incoming requests with a
|
||||
content length greater than this by
|
||||
returning a 413 status code.
|
||||
``SEND_FILE_MAX_AGE_DEFAULT``: Default cache control max age to use with
|
||||
:meth:`flask.Flask.send_static_file`, in
|
||||
seconds. Override this value on a per-file
|
||||
basis using the
|
||||
:meth:`flask.Flask.get_send_file_options` and
|
||||
:meth:`flask.Blueprint.get_send_file_options`
|
||||
hooks. Defaults to 43200 (12 hours).
|
||||
``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will
|
||||
not execute the error handlers of HTTP
|
||||
exceptions but instead treat the
|
||||
|
|
@ -276,7 +283,7 @@ configuration::
|
|||
|
||||
class ProductionConfig(Config):
|
||||
DATABASE_URI = 'mysql://user@localhost/foo'
|
||||
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DEBUG = True
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@
|
|||
FastCGI
|
||||
=======
|
||||
|
||||
FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_,
|
||||
and `cherokee`_; see :ref:`deploying-uwsgi` and
|
||||
:ref:`deploying-other-servers` for other options. To use your WSGI
|
||||
application with any of them you will need a FastCGI server first. The
|
||||
most popular one is `flup`_ which we will use for this guide. Make sure
|
||||
to have it installed to follow along.
|
||||
FastCGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
|
||||
`cherokee`_; see :ref:`deploying-uwsgi` and :ref:`deploying-wsgi-standalone`
|
||||
for other options. To use your WSGI application with any of them you will need
|
||||
a FastCGI server first. The most popular one is `flup`_ which we will use for
|
||||
this guide. Make sure to have it installed to follow along.
|
||||
|
||||
.. admonition:: Watch Out
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,14 @@ If you have a different WSGI server look up the server documentation
|
|||
about how to use a WSGI app with it. Just remember that your
|
||||
:class:`Flask` application object is the actual WSGI application.
|
||||
|
||||
For hosted options to get up and running quickly, see
|
||||
:ref:`quickstart_deployment` in the Quickstart.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
mod_wsgi
|
||||
cgi
|
||||
fastcgi
|
||||
wsgi-standalone
|
||||
uwsgi
|
||||
others
|
||||
fastcgi
|
||||
cgi
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ uWSGI
|
|||
=====
|
||||
|
||||
uWSGI is a deployment option on servers like `nginx`_, `lighttpd`_, and
|
||||
`cherokee`_; see :ref:`deploying-fastcgi` and
|
||||
:ref:`deploying-other-servers` for other options. To use your WSGI
|
||||
application with uWSGI protocol you will need a uWSGI server
|
||||
first. uWSGI is both a protocol and an application server; the
|
||||
application server can serve uWSGI, FastCGI, and HTTP protocols.
|
||||
`cherokee`_; see :ref:`deploying-fastcgi` and :ref:`deploying-wsgi-standalone`
|
||||
for other options. To use your WSGI application with uWSGI protocol you will
|
||||
need a uWSGI server first. uWSGI is both a protocol and an application server;
|
||||
the application server can serve uWSGI, FastCGI, and HTTP protocols.
|
||||
|
||||
The most popular uWSGI server is `uwsgi`_, which we will use for this
|
||||
guide. Make sure to have it installed to follow along.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,31 @@
|
|||
.. _deploying-other-servers:
|
||||
.. _deploying-wsgi-standalone:
|
||||
|
||||
Other Servers
|
||||
=============
|
||||
Standalone WSGI Containers
|
||||
==========================
|
||||
|
||||
There are popular servers written in Python that allow the execution of WSGI
|
||||
applications as well. These servers stand alone when they run; you can proxy
|
||||
to them from your web server.
|
||||
There are popular servers written in Python that contain WSGI applications and
|
||||
serve HTTP. These servers stand alone when they run; you can proxy to them
|
||||
from your web server. Note the section on :ref:`deploying-proxy-setups` if you
|
||||
run into issues.
|
||||
|
||||
Gunicorn
|
||||
--------
|
||||
|
||||
`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork
|
||||
worker model ported from Ruby's Unicorn project. It supports both `eventlet`_
|
||||
and `greenlet`_. Running a Flask application on this server is quite simple::
|
||||
|
||||
gunicorn myproject:app
|
||||
|
||||
`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``.
|
||||
For example, to run a Flask application with 4 worker processes (``-w
|
||||
4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``)::
|
||||
|
||||
gunicorn -w 4 -b 127.0.0.1:4000 myproject:app
|
||||
|
||||
.. _Gunicorn: http://gunicorn.org/
|
||||
.. _eventlet: http://eventlet.net/
|
||||
.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html
|
||||
|
||||
Tornado
|
||||
--------
|
||||
|
|
@ -14,7 +34,7 @@ Tornado
|
|||
server and tools that power `FriendFeed`_. Because it is non-blocking and
|
||||
uses epoll, it can handle thousands of simultaneous standing connections,
|
||||
which means it is ideal for real-time web services. Integrating this
|
||||
service with Flask is a trivial task::
|
||||
service with Flask is straightforward::
|
||||
|
||||
from tornado.wsgi import WSGIContainer
|
||||
from tornado.httpserver import HTTPServer
|
||||
|
|
@ -46,44 +66,54 @@ event loop::
|
|||
.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html
|
||||
.. _libevent: http://monkey.org/~provos/libevent/
|
||||
|
||||
Gunicorn
|
||||
--------
|
||||
|
||||
`Gunicorn`_ 'Green Unicorn' is a WSGI HTTP Server for UNIX. It's a pre-fork
|
||||
worker model ported from Ruby's Unicorn project. It supports both `eventlet`_
|
||||
and `greenlet`_. Running a Flask application on this server is quite simple::
|
||||
|
||||
gunicorn myproject:app
|
||||
|
||||
`Gunicorn`_ provides many command-line options -- see ``gunicorn -h``.
|
||||
For example, to run a Flask application with 4 worker processes (``-w
|
||||
4``) binding to localhost port 4000 (``-b 127.0.0.1:4000``)::
|
||||
|
||||
gunicorn -w 4 -b 127.0.0.1:4000 myproject:app
|
||||
|
||||
.. _Gunicorn: http://gunicorn.org/
|
||||
.. _eventlet: http://eventlet.net/
|
||||
.. _greenlet: http://codespeak.net/py/0.9.2/greenlet.html
|
||||
.. _deploying-proxy-setups:
|
||||
|
||||
Proxy Setups
|
||||
------------
|
||||
|
||||
If you deploy your application using one of these servers behind an HTTP
|
||||
proxy you will need to rewrite a few headers in order for the
|
||||
application to work. The two problematic values in the WSGI environment
|
||||
usually are `REMOTE_ADDR` and `HTTP_HOST`. Werkzeug ships a fixer that
|
||||
will solve some common setups, but you might want to write your own WSGI
|
||||
middleware for specific setups.
|
||||
If you deploy your application using one of these servers 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`. You can configure your httpd to pass these headers, or you
|
||||
can fix them in middleware. Werkzeug ships a fixer that will solve some common
|
||||
setups, but you might want to write your own WSGI middleware for specific
|
||||
setups.
|
||||
|
||||
The most common setup invokes the host being set from `X-Forwarded-Host`
|
||||
and the remote address from `X-Forwarded-For`::
|
||||
Here's a simple nginx configuration which proxies to an application served on
|
||||
localhost at port 8000, setting appropriate headers:
|
||||
|
||||
.. sourcecode:: nginx
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
server_name _;
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000/;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
|
||||
If your httpd is not providing these headers, the most common setup invokes the
|
||||
host being set from `X-Forwarded-Host` and the remote address from
|
||||
`X-Forwarded-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.
|
||||
.. admonition:: Trusting Headers
|
||||
|
||||
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::
|
||||
|
|
@ -125,9 +125,8 @@ 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
|
||||
consider an application that's currently connecting to SQLite like the
|
||||
documentation suggests (:ref:`sqlite3`). So how does the extension
|
||||
know the name of the application object?
|
||||
|
||||
Quite simple: you pass it to it.
|
||||
|
|
@ -135,12 +134,14 @@ 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 initializes 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
|
||||
|
|
@ -148,92 +149,18 @@ classes:
|
|||
a remote application that uses OAuth.
|
||||
|
||||
What to use depends on what you have in mind. For the SQLite 3 extension
|
||||
we will use the class-based approach because it will provide users with a
|
||||
manager object that handles opening and closing database connections.
|
||||
we will use the class-based approach because it will provide users with an
|
||||
object that handles opening and closing database connections.
|
||||
|
||||
The Extension Code
|
||||
------------------
|
||||
|
||||
Here's the contents of the `flask_sqlite3.py` for copy/paste::
|
||||
|
||||
from __future__ import absolute_import
|
||||
import sqlite3
|
||||
|
||||
from flask import _request_ctx_stack
|
||||
|
||||
class SQLite3(object):
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.app.config.setdefault('SQLITE3_DATABASE', ':memory:')
|
||||
self.app.teardown_request(self.teardown_request)
|
||||
self.app.before_request(self.before_request)
|
||||
|
||||
def connect(self):
|
||||
return sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
|
||||
|
||||
def before_request(self):
|
||||
ctx = _request_ctx_stack.top
|
||||
ctx.sqlite3_db = self.connect()
|
||||
|
||||
def teardown_request(self, exception):
|
||||
ctx = _request_ctx_stack.top
|
||||
ctx.sqlite3_db.close()
|
||||
|
||||
def get_db(self):
|
||||
ctx = _request_ctx_stack.top
|
||||
if ctx is not None:
|
||||
return ctx.sqlite3_db
|
||||
|
||||
So here's what these lines of code do:
|
||||
|
||||
1. The ``__future__`` import is necessary to activate absolute imports.
|
||||
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 requires a supplied `app` object,
|
||||
sets a configuration for the database if it's not there
|
||||
(:meth:`dict.setdefault`), and attaches `before_request` and
|
||||
`teardown_request` handlers.
|
||||
3. Next, we define a `connect` function that opens a database connection.
|
||||
4. Then we set up the request handlers we bound to the app above. Note here
|
||||
that we're attaching our database connection to the top request context via
|
||||
`_request_ctx_stack.top`. Extensions should use the top context and not the
|
||||
`g` object to store things like database connections.
|
||||
5. Finally, we add a `get_db` function that simplifies access to the context's
|
||||
database.
|
||||
|
||||
So why did we decide on a class-based approach here? Because using our
|
||||
extension looks something like this::
|
||||
|
||||
from flask import Flask
|
||||
from flask_sqlite3 import SQLite3
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile('the-config.cfg')
|
||||
manager = SQLite3(app)
|
||||
db = manager.get_db()
|
||||
|
||||
You can then use the database from views like this::
|
||||
|
||||
@app.route('/')
|
||||
def show_all():
|
||||
cur = db.cursor()
|
||||
cur.execute(...)
|
||||
|
||||
Opening a database connection from outside a view function is simple.
|
||||
|
||||
>>> from yourapplication import db
|
||||
>>> cur = db.cursor()
|
||||
>>> cur.execute(...)
|
||||
|
||||
Adding an `init_app` Function
|
||||
-----------------------------
|
||||
|
||||
In practice, you'll almost always want to permit users to initialize your
|
||||
extension and provide an app object after the fact. This can help avoid
|
||||
circular import problems when a user is breaking their app into multiple files.
|
||||
Our extension could add an `init_app` function as follows::
|
||||
|
||||
class SQLite3(object):
|
||||
|
||||
|
|
@ -251,7 +178,7 @@ Our extension could add an `init_app` function as follows::
|
|||
self.app.before_request(self.before_request)
|
||||
|
||||
def connect(self):
|
||||
return sqlite3.connect(app.config['SQLITE3_DATABASE'])
|
||||
return sqlite3.connect(self.app.config['SQLITE3_DATABASE'])
|
||||
|
||||
def before_request(self):
|
||||
ctx = _request_ctx_stack.top
|
||||
|
|
@ -261,18 +188,69 @@ Our extension could add an `init_app` function as follows::
|
|||
ctx = _request_ctx_stack.top
|
||||
ctx.sqlite3_db.close()
|
||||
|
||||
def get_db(self):
|
||||
@property
|
||||
def connection(self):
|
||||
ctx = _request_ctx_stack.top
|
||||
if ctx is not None:
|
||||
return ctx.sqlite3_db
|
||||
|
||||
The user could then initialize the extension in one file::
|
||||
|
||||
manager = SQLite3()
|
||||
So here's what these lines of code do:
|
||||
|
||||
and bind their app to the extension in another file::
|
||||
1. The ``__init__`` method takes an optional app object and, if supplied, will
|
||||
call ``init_app``.
|
||||
2. The ``init_app`` method exists so that the ``SQLite3`` object can be
|
||||
instantiated without requiring an app object. This method supports the
|
||||
factory pattern for creating applications. The ``init_app`` will set the
|
||||
configuration for the database, defaulting to an in memory database if
|
||||
no configuration is supplied. In addition, the ``init_app`` method attaches
|
||||
``before_request`` and ``teardown_request`` handlers.
|
||||
3. Next, we define a ``connect`` method that opens a database connection.
|
||||
4. Then we set up the request handlers we bound to the app above. Note here
|
||||
that we're attaching our database connection to the top request context via
|
||||
``_request_ctx_stack.top``. Extensions should use the top context and not the
|
||||
``g`` object to store things like database connections.
|
||||
5. Finally, we add a ``connection`` property that simplifies access to the context's
|
||||
database.
|
||||
|
||||
manager.init_app(app)
|
||||
So why did we decide on a class-based approach here? Because using our
|
||||
extension looks something like this::
|
||||
|
||||
from flask import Flask
|
||||
from flask_sqlite3 import SQLite3
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_pyfile('the-config.cfg')
|
||||
db = SQLite3(app)
|
||||
|
||||
You can then use the database from views like this::
|
||||
|
||||
@app.route('/')
|
||||
def show_all():
|
||||
cur = db.connection.cursor()
|
||||
cur.execute(...)
|
||||
|
||||
Additionally, the ``init_app`` method is used to support the factory pattern
|
||||
for creating apps::
|
||||
|
||||
db = Sqlite3()
|
||||
# Then later on.
|
||||
app = create_app('the-config.cfg')
|
||||
db.init_app(app)
|
||||
|
||||
Keep in mind that supporting this factory pattern for creating apps is required
|
||||
for approved flask extensions (described below).
|
||||
|
||||
|
||||
Using _request_ctx_stack
|
||||
------------------------
|
||||
|
||||
In the example above, before every request, a ``sqlite3_db`` variable is assigned
|
||||
to ``_request_ctx_stack.top``. In a view function, this variable is accessible
|
||||
using the ``connection`` property of ``SQLite3``. During the teardown of a
|
||||
request, the ``sqlite3_db`` connection is closed. By using this pattern, the
|
||||
*same* connection to the sqlite3 database is accessible to anything that needs it
|
||||
for the duration of the request.
|
||||
|
||||
End-Of-Request Behavior
|
||||
-----------------------
|
||||
|
|
@ -292,6 +270,7 @@ pattern is a good way to support both::
|
|||
else:
|
||||
app.after_request(close_connection)
|
||||
|
||||
|
||||
Strictly speaking the above code is wrong, because teardown functions are
|
||||
passed the exception and typically don't return anything. However because
|
||||
the return value is discarded this will just work assuming that the code
|
||||
|
|
@ -326,15 +305,19 @@ 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:
|
||||
|
||||
0. An approved Flask extension requires a maintainer. In the event an
|
||||
extension author would like to move beyond the project, the project should
|
||||
find a new maintainer including full source hosting transition and PyPI
|
||||
access. If no maintainer is available, give access to the Flask core team.
|
||||
1. An approved Flask extension must provide exactly one package or module
|
||||
named ``flask_extensionname``. They might also reside inside a
|
||||
``flaskext`` namespace packages though this is discouraged now.
|
||||
2. It must ship a testing suite that can either be invoked with ``make test``
|
||||
or ``python setup.py test``. For test suites 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 test suite also has to be part of the distribution.
|
||||
are installed automatically. If tests are invoked with ``python setup.py
|
||||
test``, test dependencies can be specified in the `setup.py` file. The
|
||||
test suite also has to be part of the distribution.
|
||||
3. APIs of approved extensions will be checked for the following
|
||||
characteristics:
|
||||
|
||||
|
|
|
|||
|
|
@ -8,93 +8,48 @@ should or should not be using it.
|
|||
What does "micro" mean?
|
||||
-----------------------
|
||||
|
||||
To me, the "micro" in microframework refers not only to the simplicity and
|
||||
small size of the framework, but also the fact that it does not make many
|
||||
decisions for you. While Flask does pick a templating engine for you, we
|
||||
won't make such decisions for your datastore or other parts.
|
||||
“Micro” does not mean that your whole web application has to fit into
|
||||
a single Python file (although it certainly can). Nor does it mean
|
||||
that Flask is lacking in functionality. The "micro" in microframework
|
||||
means Flask aims to keep the core simple but extensible. Flask won't make
|
||||
many decisions for you, such as what database to use. Those decisions that
|
||||
it does make, such as what templating engine to use, are easy to change.
|
||||
Everything else is up to you, so that Flask can be everything you need
|
||||
and nothing you don't.
|
||||
|
||||
However, to us the term “micro” does not mean that the whole implementation
|
||||
has to fit into a single Python file.
|
||||
By default, Flask does not include a database abstraction layer, form
|
||||
validation or anything else where different libraries already exist that can
|
||||
handle that. Instead, FLask extensions add such functionality to your
|
||||
application as if it was implemented in Flask itself. Numerous extensions
|
||||
provide database integration, form validation, upload handling, various open
|
||||
authentication technologies, and more. Flask may be "micro", but the
|
||||
possibilities are endless.
|
||||
|
||||
One of the design decisions with Flask was that simple tasks should be
|
||||
simple; they should not take a lot of code and yet they should not limit you.
|
||||
Because of that we made a few design choices that some people might find
|
||||
surprising or unorthodox. For example, Flask uses thread-local objects
|
||||
internally so that you don't have to pass objects around from function to
|
||||
function within a request in order to stay threadsafe. While this is a
|
||||
really easy approach and saves you a lot of time, it might also cause some
|
||||
troubles for very large applications because changes on these thread-local
|
||||
objects can happen anywhere in the same thread. In order to solve these
|
||||
problems we don't hide the thread locals for you but instead embrace them
|
||||
and provide you with a lot of tools to make it as pleasant as possible to
|
||||
work with them.
|
||||
Convention over Configuration
|
||||
-----------------------------
|
||||
|
||||
Flask is also based on convention over configuration, which means that
|
||||
many things are preconfigured. For example, by convention templates and
|
||||
static files are stored in subdirectories within the application's Python source tree.
|
||||
While this can be changed you usually don't have to.
|
||||
Flask is based on convention over configuration, which means that many things
|
||||
are preconfigured. For example, by convention templates and static files are
|
||||
stored in subdirectories within the application's Python source tree. While
|
||||
this can be changed you usually don't have to. We want to minimize the time
|
||||
you need to spend in order to get up and running, without assuming things
|
||||
about your needs.
|
||||
|
||||
The main reason Flask is called a "microframework" is the idea
|
||||
to keep the core simple but extensible. There is no database abstraction
|
||||
layer, no form validation or anything else where different libraries
|
||||
already exist that can handle that. However Flask supports
|
||||
extensions to add such functionality to 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.
|
||||
Growing Up
|
||||
----------
|
||||
|
||||
Since Flask is based on a very solid foundation there is not a lot of code
|
||||
in Flask itself. As such it's easy to adapt even for large applications
|
||||
and we are making sure that you can either configure it as much as
|
||||
possible by subclassing things or by forking the entire codebase. If you
|
||||
are interested in that, check out the :ref:`becomingbig` chapter.
|
||||
Since Flask is based on a very solid foundation there is not a lot of code in
|
||||
Flask itself. As such it's easy to adapt even for large applications and we
|
||||
are making sure that you can either configure it as much as possible by
|
||||
subclassing things or by forking the entire codebase. If you are interested
|
||||
in that, check out the :ref:`becomingbig` chapter.
|
||||
|
||||
If you are curious about the Flask design principles, head over to the
|
||||
section about :ref:`design`.
|
||||
If you are curious about the Flask design principles, head over to the section
|
||||
about :ref:`design`.
|
||||
|
||||
Web Development is Dangerous
|
||||
----------------------------
|
||||
For the Stalwart and Wizened...
|
||||
-------------------------------
|
||||
|
||||
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 securely.
|
||||
|
||||
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
|
||||
cause security problems.
|
||||
|
||||
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 security in mind when doing web development.
|
||||
|
||||
The Status of Python 3
|
||||
----------------------
|
||||
|
||||
Currently the Python community is in the process of improving libraries to
|
||||
support the new iteration of the Python programming language. While the
|
||||
situation is greatly improving there are still some issues that make it
|
||||
hard for us to switch over to Python 3 just now. These problems are
|
||||
partially caused by changes in the language that went unreviewed for too
|
||||
long, partially also because we have not quite worked out how the lower-
|
||||
level API should change to account for the Unicode differences in Python 3.
|
||||
|
||||
Werkzeug and Flask will be ported to Python 3 as soon as a solution for
|
||||
the changes 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. If you plan on upgrading to Python 3 in the near future we
|
||||
strongly recommend that you read `How to write forwards compatible
|
||||
Python code <http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/>`_.
|
||||
If you're more curious about the minutiae of Flask's implementation, and
|
||||
whether its structure is right for your needs, read the
|
||||
:ref:`advanced_foreword`.
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ System-Wide Installation
|
|||
------------------------
|
||||
|
||||
This is possible as well, though I do not recommend it. Just run
|
||||
`easy_install` with root privileges::
|
||||
`pip` with root privileges::
|
||||
|
||||
$ sudo pip install Flask
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,24 @@ at :func:`werkzeug.serving.run_simple`::
|
|||
Note that :func:`run_simple <werkzeug.serving.run_simple>` is not intended for
|
||||
use in production. Use a :ref:`full-blown WSGI server <deployment>`.
|
||||
|
||||
In order to use the interactive debuggger, debugging must be enabled both on
|
||||
the application and the simple server, here is the "hello world" example with
|
||||
debugging and :func:`run_simple <werkzeug.serving.run_simple>`::
|
||||
|
||||
from flask import Flask
|
||||
from werkzeug.serving import run_simple
|
||||
|
||||
app = Flask(__name__)
|
||||
app.debug = True
|
||||
|
||||
@app.route('/')
|
||||
def hello_world():
|
||||
return 'Hello World!'
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_simple('localhost', 5000, app,
|
||||
use_reloader=True, use_debugger=True, use_evalex=True)
|
||||
|
||||
|
||||
Combining Applications
|
||||
----------------------
|
||||
|
|
|
|||
|
|
@ -141,4 +141,4 @@ These results are also dict-like objects:
|
|||
u'admin@localhost'
|
||||
|
||||
For more information about MongoKit, head over to the
|
||||
`website <http://bytebucket.org/namlook/mongokit/>`_.
|
||||
`website <https://github.com/namlook/mongokit>`_.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ following quick checklist:
|
|||
`__init__.py` file. That way each module can import it safely and the
|
||||
`__name__` variable will resolve to the correct package.
|
||||
2. all the view functions (the ones with a :meth:`~flask.Flask.route`
|
||||
decorator on top) have to be imported when in the `__init__.py` file.
|
||||
decorator on top) have to be imported in the `__init__.py` file.
|
||||
Not the object itself, but the module it is in. Import the view module
|
||||
**after the application object is created**.
|
||||
|
||||
|
|
|
|||
|
|
@ -168,8 +168,8 @@ The following converters exist:
|
|||
|
||||
.. admonition:: Unique URLs / Redirection Behaviour
|
||||
|
||||
Flask's URL rules are based on Werkzeug's routing module. The idea behind
|
||||
that module is to ensure beautiful and unique also unique URLs based on
|
||||
Flask's URL rules are based on Werkzeug's routing module. The idea
|
||||
behind that module is to ensure beautiful and unique URLs based on
|
||||
precedents laid down by Apache and earlier HTTP servers.
|
||||
|
||||
Take these two rules::
|
||||
|
|
@ -193,7 +193,7 @@ The following converters exist:
|
|||
with a trailing slash will produce a 404 "Not Found" error.
|
||||
|
||||
This behavior allows relative URLs to continue working if users access the
|
||||
page when they forget a trailing slash, consistent with how with how Apache
|
||||
page when they forget a trailing slash, consistent with how Apache
|
||||
and other servers work. Also, the URLs will stay unique, which helps search
|
||||
engines avoid indexing the same page twice.
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ some examples:
|
|||
|
||||
(This also uses the :meth:`~flask.Flask.test_request_context` method, explained
|
||||
below. It tells Flask to behave as though it is handling a request, even
|
||||
though were are interacting with it through a Python shell. Have a look at the
|
||||
though we are interacting with it through a Python shell. Have a look at the
|
||||
explanation below. :ref:`context-locals`).
|
||||
|
||||
Why would you want to build URLs instead of hard-coding them into your
|
||||
|
|
@ -333,8 +333,7 @@ configured to serve them for you, but during development Flask can do that
|
|||
as well. Just create a folder called `static` in your package or next to
|
||||
your module and it will be available at `/static` on the application.
|
||||
|
||||
To generate URLs that part of the URL, use the special ``'static'`` URL
|
||||
name::
|
||||
To generate URLs for static files, use the special ``'static'`` endpoint name::
|
||||
|
||||
url_for('static', filename='style.css')
|
||||
|
||||
|
|
@ -826,3 +825,26 @@ can do it like this::
|
|||
|
||||
from werkzeug.contrib.fixers import LighttpdCGIRootFix
|
||||
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)
|
||||
|
||||
.. _quickstart_deployment:
|
||||
|
||||
Deploying to a Web Server
|
||||
-------------------------
|
||||
|
||||
Ready to deploy your new Flask app? To wrap up the quickstart, you can
|
||||
immediately deploy to a hosted platform, all of which offer a free plan for
|
||||
small projects:
|
||||
|
||||
- `Deploying Flask on Heroku <http://devcenter.heroku.com/articles/python>`_
|
||||
- `Deploying Flask on ep.io <https://www.ep.io/docs/quickstart/flask/>`_
|
||||
- `Deploying WSGI on dotCloud <http://docs.dotcloud.com/services/python/>`_
|
||||
with `Flask-specific notes <http://flask.pocoo.org/snippets/48/>`_
|
||||
|
||||
Other places where you can host your Flask app:
|
||||
|
||||
- `Deploying Flask on Webfaction <http://flask.pocoo.org/snippets/65/>`_
|
||||
- `Deploying Flask on Google App Engine <https://github.com/kamalgill/flask-appengine-template>`_
|
||||
- `Sharing your Localhost Server with Localtunnel <http://flask.pocoo.org/snippets/89/>`_
|
||||
|
||||
If you manage your own hosts and would like to host yourself, see the chapter
|
||||
on :ref:`deployment`.
|
||||
|
|
|
|||
|
|
@ -131,6 +131,8 @@ debugging. You can access the name of the signal with the
|
|||
missing blinker installations, you can do so by using the
|
||||
:class:`flask.signals.Namespace` class.
|
||||
|
||||
.. _signals-sending:
|
||||
|
||||
Sending Signals
|
||||
---------------
|
||||
|
||||
|
|
@ -156,6 +158,17 @@ function, you can pass ``current_app._get_current_object()`` as sender.
|
|||
that :data:`~flask.current_app` is a proxy and not the real application
|
||||
object.
|
||||
|
||||
|
||||
Signals and Flask's Request Context
|
||||
-----------------------------------
|
||||
|
||||
Signals fully support :ref:`request-context` when receiving signals.
|
||||
Context-local variables are consistently available between
|
||||
:data:`~flask.request_started` and :data:`~flask.request_finished`, so you can
|
||||
rely on :class:`flask.g` and others as needed. Note the limitations described
|
||||
in :ref:`signals-sending` and the :data:`~flask.request_tearing_down` signal.
|
||||
|
||||
|
||||
Decorator Based Signal Subscriptions
|
||||
------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ into the module which we will be doing here. However a cleaner solution
|
|||
would be to create a separate `.ini` or `.py` file and load that or import
|
||||
the values from there.
|
||||
|
||||
::
|
||||
In `flaskr.py`::
|
||||
|
||||
# all the imports
|
||||
import sqlite3
|
||||
|
|
@ -26,7 +26,7 @@ the values from there.
|
|||
PASSWORD = 'default'
|
||||
|
||||
Next we can create our actual application and initialize it with the
|
||||
config from the same file::
|
||||
config from the same file, in `flaskr.py`::
|
||||
|
||||
# create our little application :)
|
||||
app = Flask(__name__)
|
||||
|
|
@ -37,21 +37,21 @@ string it will import it) and then look for all uppercase variables
|
|||
defined there. In our case, the configuration we just wrote a few lines
|
||||
of code above. You can also move that into a separate file.
|
||||
|
||||
It is also a good idea to be able to load a configuration from a
|
||||
configurable file. This is what :meth:`~flask.Config.from_envvar` can
|
||||
do::
|
||||
Usually, it is a good idea to load a configuration from a configurable
|
||||
file. This is what :meth:`~flask.Config.from_envvar` can do, replacing the
|
||||
:meth:`~flask.Config.from_object` line above::
|
||||
|
||||
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
|
||||
|
||||
That way someone can set an environment variable called
|
||||
:envvar:`FLASKR_SETTINGS` to specify a config file to be loaded which will
|
||||
then override the default values. The silent switch just tells Flask to
|
||||
not complain if no such environment key is set.
|
||||
:envvar:`FLASKR_SETTINGS` to specify a config file to be loaded which will then
|
||||
override the default values. The silent switch just tells Flask to not complain
|
||||
if no such environment key is set.
|
||||
|
||||
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
|
||||
debug flag enables or disables the interactive debugger. *Never leave
|
||||
debug mode activated in a production system*, because it will allow users to
|
||||
execute code on the server!
|
||||
|
||||
We also add a method to easily connect to the database specified. That
|
||||
|
|
|
|||
23
flask/app.py
23
flask/app.py
|
|
@ -249,6 +249,7 @@ class Flask(_PackageBoundObject):
|
|||
'SESSION_COOKIE_HTTPONLY': True,
|
||||
'SESSION_COOKIE_SECURE': False,
|
||||
'MAX_CONTENT_LENGTH': None,
|
||||
'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours
|
||||
'TRAP_BAD_REQUEST_ERRORS': False,
|
||||
'TRAP_HTTP_EXCEPTIONS': False,
|
||||
'PREFERRED_URL_SCHEME': 'http'
|
||||
|
|
@ -1021,6 +1022,12 @@ class Flask(_PackageBoundObject):
|
|||
self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
|
||||
.append((code_or_exception, f))
|
||||
|
||||
def get_send_file_options(self, filename):
|
||||
# Override: Hooks in SEND_FILE_MAX_AGE_DEFAULT config.
|
||||
options = super(Flask, self).get_send_file_options(filename)
|
||||
options['cache_timeout'] = self.config['SEND_FILE_MAX_AGE_DEFAULT']
|
||||
return options
|
||||
|
||||
@setupmethod
|
||||
def template_filter(self, name=None):
|
||||
"""A decorator that is used to register custom template filter.
|
||||
|
|
@ -1362,7 +1369,21 @@ class Flask(_PackageBoundObject):
|
|||
if isinstance(rv, basestring):
|
||||
return self.response_class(rv)
|
||||
if isinstance(rv, tuple):
|
||||
return self.response_class(*rv)
|
||||
if len(rv) > 0 and isinstance(rv[0], self.response_class):
|
||||
original = rv[0]
|
||||
new_response = self.response_class('', *rv[1:])
|
||||
if len(rv) < 3:
|
||||
# The args for the response class are
|
||||
# response=None, status=None, headers=None,
|
||||
# mimetype=None, content_type=None, ...
|
||||
# so if there's at least 3 elements the rv
|
||||
# tuple contains header information so the
|
||||
# headers from rv[0] "win."
|
||||
new_response.headers = original.headers
|
||||
new_response.response = original.response
|
||||
return new_response
|
||||
else:
|
||||
return self.response_class(*rv)
|
||||
return self.response_class.force_type(rv, request.environ)
|
||||
|
||||
def create_url_adapter(self, request):
|
||||
|
|
|
|||
|
|
@ -106,8 +106,7 @@ class Config(dict):
|
|||
'loaded. Set this variable and make it '
|
||||
'point to a configuration file' %
|
||||
variable_name)
|
||||
self.from_pyfile(rv)
|
||||
return True
|
||||
return self.from_pyfile(rv, silent=silent)
|
||||
|
||||
def from_pyfile(self, filename, silent=False):
|
||||
"""Updates the values in the config from a Python file. This function
|
||||
|
|
|
|||
49
flask/exceptions.py
Normal file
49
flask/exceptions.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
flask.exceptions
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Flask specific additions to :class:`~werkzeug.exceptions.HTTPException`
|
||||
|
||||
:copyright: (c) 2011 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from werkzeug.exceptions import HTTPException, BadRequest
|
||||
from .helpers import json
|
||||
|
||||
|
||||
class JSONHTTPException(HTTPException):
|
||||
"""A base class for HTTP exceptions with ``Content-Type:
|
||||
application/json``.
|
||||
|
||||
The ``description`` attribute of this class must set to a string (*not* an
|
||||
HTML string) which describes the error.
|
||||
|
||||
"""
|
||||
|
||||
def get_body(self, environ):
|
||||
"""Overrides :meth:`werkzeug.exceptions.HTTPException.get_body` to
|
||||
return the description of this error in JSON format instead of HTML.
|
||||
|
||||
"""
|
||||
return json.dumps(dict(description=self.get_description(environ)))
|
||||
|
||||
def get_headers(self, environ):
|
||||
"""Returns a list of headers including ``Content-Type:
|
||||
application/json``.
|
||||
|
||||
"""
|
||||
return [('Content-Type', 'application/json')]
|
||||
|
||||
|
||||
class JSONBadRequest(JSONHTTPException, BadRequest):
|
||||
"""Represents an HTTP ``400 Bad Request`` error whose body contains an
|
||||
error message in JSON format instead of HTML format (as in the superclass).
|
||||
|
||||
"""
|
||||
|
||||
#: The description of the error which occurred as a string.
|
||||
description = (
|
||||
'The browser (or proxy) sent a request that this server could not '
|
||||
'understand.'
|
||||
)
|
||||
|
|
@ -118,9 +118,31 @@ def jsonify(*args, **kwargs):
|
|||
information about this, have a look at :ref:`json-security`.
|
||||
|
||||
.. versionadded:: 0.2
|
||||
|
||||
.. versionadded:: 0.9
|
||||
If the ``padded`` argument is true, the JSON object will be padded
|
||||
for JSONP calls and the response mimetype will be changed to
|
||||
``application/javascript``. By default, the request arguments ``callback``
|
||||
and ``jsonp`` will be used as the name for the callback function.
|
||||
This will work with jQuery and most other JavaScript libraries
|
||||
by default.
|
||||
|
||||
If the ``padded`` argument is a string, jsonify will look for
|
||||
the request argument with the same name and use that value as the
|
||||
callback-function name.
|
||||
"""
|
||||
if __debug__:
|
||||
_assert_have_json()
|
||||
if 'padded' in kwargs:
|
||||
if isinstance(kwargs['padded'], str):
|
||||
callback = request.args.get(kwargs['padded']) or 'jsonp'
|
||||
else:
|
||||
callback = request.args.get('callback') or \
|
||||
request.args.get('jsonp') or 'jsonp'
|
||||
del kwargs['padded']
|
||||
json_str = json.dumps(dict(*args, **kwargs), indent=None)
|
||||
content = str(callback) + "(" + json_str + ")"
|
||||
return current_app.response_class(content, mimetype='application/javascript')
|
||||
return current_app.response_class(json.dumps(dict(*args, **kwargs),
|
||||
indent=None if request.is_xhr else 2), mimetype='application/json')
|
||||
|
||||
|
|
@ -283,7 +305,16 @@ def flash(message, category='message'):
|
|||
messages and ``'warning'`` for warnings. However any
|
||||
kind of string can be used as category.
|
||||
"""
|
||||
session.setdefault('_flashes', []).append((category, message))
|
||||
# Original implementation:
|
||||
#
|
||||
# session.setdefault('_flashes', []).append((category, message))
|
||||
#
|
||||
# This assumed that changes made to mutable structures in the session are
|
||||
# are always in sync with the sess on object, which is not true for session
|
||||
# implementations that use external storage for keeping their keys/values.
|
||||
flashes = session.get('_flashes', [])
|
||||
flashes.append((category, message))
|
||||
session['_flashes'] = flashes
|
||||
|
||||
|
||||
def get_flashed_messages(with_categories=False, category_filter=[]):
|
||||
|
|
@ -341,6 +372,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
guessing requires a `filename` or an `attachment_filename` to be
|
||||
provided.
|
||||
|
||||
Note `get_send_file_options` in :class:`flask.Flask` hooks the
|
||||
``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable to set the default
|
||||
cache_timeout.
|
||||
|
||||
Please never pass filenames to this function from user sources without
|
||||
checking them first. Something like this is usually sufficient to
|
||||
avoid security problems::
|
||||
|
|
@ -517,7 +552,8 @@ def send_from_directory(directory, filename, **options):
|
|||
filename = safe_join(directory, filename)
|
||||
if not os.path.isfile(filename):
|
||||
raise NotFound()
|
||||
return send_file(filename, conditional=True, **options)
|
||||
options.setdefault('conditional', True)
|
||||
return send_file(filename, **options)
|
||||
|
||||
|
||||
def get_root_path(import_name):
|
||||
|
|
@ -673,6 +709,25 @@ class _PackageBoundObject(object):
|
|||
return FileSystemLoader(os.path.join(self.root_path,
|
||||
self.template_folder))
|
||||
|
||||
def get_send_file_options(self, filename):
|
||||
"""Provides keyword arguments to send to :func:`send_from_directory`.
|
||||
|
||||
This allows subclasses to change the behavior when sending files based
|
||||
on the filename. For example, to set the cache timeout for .js files
|
||||
to 60 seconds (note the options are keywords for :func:`send_file`)::
|
||||
|
||||
class MyFlask(flask.Flask):
|
||||
def get_send_file_options(self, filename):
|
||||
options = super(MyFlask, self).get_send_file_options(filename)
|
||||
if filename.lower().endswith('.js'):
|
||||
options['cache_timeout'] = 60
|
||||
options['conditional'] = True
|
||||
return options
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
return {}
|
||||
|
||||
def send_static_file(self, filename):
|
||||
"""Function used internally to send static files from the static
|
||||
folder to the browser.
|
||||
|
|
@ -681,7 +736,8 @@ class _PackageBoundObject(object):
|
|||
"""
|
||||
if not self.has_static_folder:
|
||||
raise RuntimeError('No static folder for this object')
|
||||
return send_from_directory(self.static_folder, filename)
|
||||
return send_from_directory(self.static_folder, filename,
|
||||
**self.get_send_file_options(filename))
|
||||
|
||||
def open_resource(self, resource, mode='rb'):
|
||||
"""Opens a resource from the application's resource folder. To see
|
||||
|
|
|
|||
|
|
@ -659,6 +659,35 @@ class BasicFunctionalityTestCase(FlaskTestCase):
|
|||
self.assert_equal(rv.data, 'W00t')
|
||||
self.assert_equal(rv.mimetype, 'text/html')
|
||||
|
||||
def test_make_response_with_response_instance(self):
|
||||
app = flask.Flask(__name__)
|
||||
with app.test_request_context():
|
||||
rv = flask.make_response(
|
||||
flask.jsonify({'msg': 'W00t'}), 400)
|
||||
self.assertEqual(rv.status_code, 400)
|
||||
self.assertEqual(rv.data,
|
||||
'{\n "msg": "W00t"\n}')
|
||||
self.assertEqual(rv.mimetype, 'application/json')
|
||||
|
||||
rv = flask.make_response(
|
||||
flask.Response(''), 400)
|
||||
self.assertEqual(rv.status_code, 400)
|
||||
self.assertEqual(rv.data, '')
|
||||
self.assertEqual(rv.mimetype, 'text/html')
|
||||
|
||||
rv = flask.make_response(
|
||||
flask.Response('', headers={'Content-Type': 'text/html'}),
|
||||
400, None, 'application/json')
|
||||
self.assertEqual(rv.status_code, 400)
|
||||
self.assertEqual(rv.headers['Content-Type'], 'application/json')
|
||||
|
||||
rv = flask.make_response(
|
||||
flask.Response('', mimetype='application/json'),
|
||||
400, {'Content-Type': 'text/html'})
|
||||
self.assertEqual(rv.status_code, 400)
|
||||
self.assertEqual(rv.headers['Content-Type'], 'text/html')
|
||||
|
||||
|
||||
def test_url_generation(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/hello/<name>', methods=['POST'])
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import unittest
|
|||
import warnings
|
||||
from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
|
||||
from werkzeug.exceptions import NotFound
|
||||
from werkzeug.http import parse_cache_control_header
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
|
||||
|
|
@ -357,6 +358,19 @@ class BlueprintTestCase(FlaskTestCase):
|
|||
rv = c.get('/admin/static/css/test.css')
|
||||
self.assert_equal(rv.data.strip(), '/* nested file */')
|
||||
|
||||
# try/finally, in case other tests use this app for Blueprint tests.
|
||||
max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT']
|
||||
try:
|
||||
expected_max_age = 3600
|
||||
if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age:
|
||||
expected_max_age = 7200
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = expected_max_age
|
||||
rv = c.get('/admin/static/css/test.css')
|
||||
cc = parse_cache_control_header(rv.headers['Cache-Control'])
|
||||
self.assert_equal(cc.max_age, expected_max_age)
|
||||
finally:
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default
|
||||
|
||||
with app.test_request_context():
|
||||
self.assert_equal(flask.url_for('admin.static', filename='test.txt'),
|
||||
'/admin/static/test.txt')
|
||||
|
|
|
|||
|
|
@ -69,6 +69,24 @@ class ConfigTestCase(FlaskTestCase):
|
|||
finally:
|
||||
os.environ = env
|
||||
|
||||
def test_config_from_envvar_missing(self):
|
||||
env = os.environ
|
||||
try:
|
||||
os.environ = {'FOO_SETTINGS': 'missing.cfg'}
|
||||
try:
|
||||
app = flask.Flask(__name__)
|
||||
app.config.from_envvar('FOO_SETTINGS')
|
||||
except IOError, e:
|
||||
msg = str(e)
|
||||
self.assert_(msg.startswith('[Errno 2] Unable to load configuration '
|
||||
'file (No such file or directory):'))
|
||||
self.assert_(msg.endswith("missing.cfg'"))
|
||||
else:
|
||||
self.fail('expected IOError')
|
||||
self.assertFalse(app.config.from_envvar('FOO_SETTINGS', silent=True))
|
||||
finally:
|
||||
os.environ = env
|
||||
|
||||
def test_config_missing(self):
|
||||
app = flask.Flask(__name__)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import unittest
|
|||
from logging import StreamHandler
|
||||
from StringIO import StringIO
|
||||
from flask.testsuite import FlaskTestCase, catch_warnings, catch_stderr
|
||||
from werkzeug.http import parse_options_header
|
||||
from werkzeug.http import parse_cache_control_header, parse_options_header
|
||||
|
||||
|
||||
def has_encoding(name):
|
||||
|
|
@ -40,6 +40,18 @@ class JSONTestCase(FlaskTestCase):
|
|||
rv = c.post('/json', data='malformed', content_type='application/json')
|
||||
self.assert_equal(rv.status_code, 400)
|
||||
|
||||
def test_json_bad_requests_content_type(self):
|
||||
app = flask.Flask(__name__)
|
||||
@app.route('/json', methods=['POST'])
|
||||
def return_json():
|
||||
return unicode(flask.request.json)
|
||||
c = app.test_client()
|
||||
rv = c.post('/json', data='malformed', content_type='application/json')
|
||||
self.assert_equal(rv.status_code, 400)
|
||||
self.assert_equal(rv.mimetype, 'application/json')
|
||||
self.assert_('description' in flask.json.loads(rv.data))
|
||||
self.assert_('<p>' not in flask.json.loads(rv.data)['description'])
|
||||
|
||||
def test_json_body_encoding(self):
|
||||
app = flask.Flask(__name__)
|
||||
app.testing = True
|
||||
|
|
@ -61,11 +73,25 @@ class JSONTestCase(FlaskTestCase):
|
|||
@app.route('/dict')
|
||||
def return_dict():
|
||||
return flask.jsonify(d)
|
||||
@app.route("/padded")
|
||||
def return_padded_json():
|
||||
return flask.jsonify(d, padded=True)
|
||||
@app.route("/padded_custom")
|
||||
def return_padded_json_custom_callback():
|
||||
return flask.jsonify(d, padded='my_func_name')
|
||||
c = app.test_client()
|
||||
for url in '/kw', '/dict':
|
||||
rv = c.get(url)
|
||||
self.assert_equal(rv.mimetype, 'application/json')
|
||||
self.assert_equal(flask.json.loads(rv.data), d)
|
||||
for get_arg in 'callback=funcName', 'jsonp=funcName':
|
||||
rv = c.get('/padded?' + get_arg)
|
||||
self.assert_( rv.data.startswith("funcName(") )
|
||||
self.assert_( rv.data.endswith(")") )
|
||||
rv_json = rv.data.split('(')[1].split(')')[0]
|
||||
self.assert_equal(flask.json.loads(rv_json), d)
|
||||
rv = c.get('/padded_custom?my_func_name=funcName')
|
||||
self.assert_( rv.data.startswith("funcName(") )
|
||||
|
||||
def test_json_attr(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
|
@ -204,6 +230,33 @@ class SendfileTestCase(FlaskTestCase):
|
|||
self.assert_equal(value, 'attachment')
|
||||
self.assert_equal(options['filename'], 'index.txt')
|
||||
|
||||
def test_static_file(self):
|
||||
app = flask.Flask(__name__)
|
||||
# default cache timeout is 12 hours
|
||||
with app.test_request_context():
|
||||
rv = app.send_static_file('index.html')
|
||||
cc = parse_cache_control_header(rv.headers['Cache-Control'])
|
||||
self.assert_equal(cc.max_age, 12 * 60 * 60)
|
||||
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600
|
||||
with app.test_request_context():
|
||||
rv = app.send_static_file('index.html')
|
||||
cc = parse_cache_control_header(rv.headers['Cache-Control'])
|
||||
self.assert_equal(cc.max_age, 3600)
|
||||
# override get_send_file_options with some new values and check them
|
||||
class StaticFileApp(flask.Flask):
|
||||
def get_send_file_options(self, filename):
|
||||
opts = super(StaticFileApp, self).get_send_file_options(filename)
|
||||
opts['cache_timeout'] = 10
|
||||
# this test catches explicit inclusion of the conditional
|
||||
# keyword arg in the guts
|
||||
opts['conditional'] = True
|
||||
return opts
|
||||
app = StaticFileApp(__name__)
|
||||
with app.test_request_context():
|
||||
rv = app.send_static_file('index.html')
|
||||
cc = parse_cache_control_header(rv.headers['Cache-Control'])
|
||||
self.assert_equal(cc.max_age, 10)
|
||||
|
||||
|
||||
class LoggingTestCase(FlaskTestCase):
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ class MethodViewType(type):
|
|||
rv = type.__new__(cls, name, bases, d)
|
||||
if 'methods' not in d:
|
||||
methods = set(rv.methods or [])
|
||||
for key, value in d.iteritems():
|
||||
for key in d:
|
||||
if key in http_method_funcs:
|
||||
methods.add(key.upper())
|
||||
# if we have no method at all in there we don't want to
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@
|
|||
"""
|
||||
|
||||
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
|
||||
from werkzeug.exceptions import BadRequest
|
||||
from werkzeug.utils import cached_property
|
||||
|
||||
from .exceptions import JSONBadRequest
|
||||
from .debughelpers import attach_enctype_error_multidict
|
||||
from .helpers import json, _assert_have_json
|
||||
from .globals import _request_ctx_stack
|
||||
|
|
@ -108,12 +108,22 @@ class Request(RequestBase):
|
|||
|
||||
def on_json_loading_failed(self, e):
|
||||
"""Called if decoding of the JSON data failed. The return value of
|
||||
this method is used by :attr:`json` when an error ocurred. The
|
||||
default implementation raises a :class:`~werkzeug.exceptions.BadRequest`.
|
||||
this method is used by :attr:`json` when an error ocurred. The default
|
||||
implementation raises a :class:`JSONBadRequest`, which is a subclass of
|
||||
:class:`~werkzeug.exceptions.BadRequest` which sets the
|
||||
``Content-Type`` to ``application/json`` and provides a JSON-formatted
|
||||
error description::
|
||||
|
||||
{"description": "The browser (or proxy) sent a request that \
|
||||
this server could not understand."}
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
Return a :class:`JSONBadRequest` instead of a
|
||||
:class:`~werkzeug.exceptions.BadRequest` by default.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
raise BadRequest()
|
||||
raise JSONBadRequest()
|
||||
|
||||
def _load_form_data(self):
|
||||
RequestBase._load_form_data(self)
|
||||
|
|
|
|||
5
setup.py
5
setup.py
|
|
@ -59,7 +59,7 @@ class run_audit(Command):
|
|||
try:
|
||||
import pyflakes.scripts.pyflakes as flakes
|
||||
except ImportError:
|
||||
print "Audit requires PyFlakes installed in your system."""
|
||||
print "Audit requires PyFlakes installed in your system."
|
||||
sys.exit(-1)
|
||||
|
||||
warns = 0
|
||||
|
|
@ -100,6 +100,9 @@ setup(
|
|||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.5',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
],
|
||||
|
|
|
|||
3
tox.ini
3
tox.ini
|
|
@ -3,3 +3,6 @@ envlist=py25,py26,py27,pypy
|
|||
|
||||
[testenv]
|
||||
commands=python run-tests.py
|
||||
|
||||
[testenv:py25]
|
||||
deps=simplejson
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue