Browse Source

Merge branch 'master' into overridable-cli

pull/2053/head
David Lord 8 years ago committed by GitHub
parent
commit
42dde23671
  1. 7
      CHANGES
  2. 2
      CONTRIBUTING.rst
  3. 28
      docs/blueprints.rst
  4. 8
      docs/conf.py
  5. 14
      docs/config.rst
  6. 230
      docs/installation.rst
  7. 82
      docs/patterns/deferredcallbacks.rst
  8. 250
      docs/quickstart.rst
  9. 22
      flask/helpers.py
  10. 1
      flask/json.py
  11. 81
      flask/sessions.py
  12. 36
      tests/test_basic.py

7
CHANGES

@ -34,6 +34,12 @@ Major release, unreleased
type is invalid. (`#2256`_) type is invalid. (`#2256`_)
- Add ``routes`` CLI command to output routes registered on the application. - Add ``routes`` CLI command to output routes registered on the application.
(`#2259`_) (`#2259`_)
- Show warning when session cookie domain is a bare hostname or an IP
address, as these may not behave properly in some browsers, such as Chrome.
(`#2282`_)
- Allow IP address as exact session cookie domain. (`#2282`_)
- ``SESSION_COOKIE_DOMAIN`` is set if it is detected through ``SERVER_NAME``.
(`#2282`_)
- Allow overriding built-in commands using ``app.cli.command`` instead of - Allow overriding built-in commands using ``app.cli.command`` instead of
creating a separate ``FlaskGroup``. (`#2217`_) creating a separate ``FlaskGroup``. (`#2217`_)
@ -46,6 +52,7 @@ Major release, unreleased
.. _#2254: https://github.com/pallets/flask/pull/2254 .. _#2254: https://github.com/pallets/flask/pull/2254
.. _#2256: https://github.com/pallets/flask/pull/2256 .. _#2256: https://github.com/pallets/flask/pull/2256
.. _#2259: https://github.com/pallets/flask/pull/2259 .. _#2259: https://github.com/pallets/flask/pull/2259
.. _#2282: https://github.com/pallets/flask/pull/2282
Version 0.12.1 Version 0.12.1
-------------- --------------

2
CONTRIBUTING.rst

@ -54,7 +54,7 @@ Install Flask as an editable package using the current source::
Then you can run the testsuite with:: Then you can run the testsuite with::
pytest pytest tests/
With only pytest installed, a large part of the testsuite will get skipped With only pytest installed, a large part of the testsuite will get skipped
though. Whether this is relevant depends on which part of Flask you're working though. Whether this is relevant depends on which part of Flask you're working

28
docs/blueprints.rst

@ -177,11 +177,11 @@ the `template_folder` parameter to the :class:`Blueprint` constructor::
admin = Blueprint('admin', __name__, template_folder='templates') admin = Blueprint('admin', __name__, template_folder='templates')
For static files, the path can be absolute or relative to the blueprint For static files, the path can be absolute or relative to the blueprint
resource folder. resource folder.
The template folder is added to the search path of templates but with a lower The template folder is added to the search path of templates but with a lower
priority than the actual application's template folder. That way you can priority than the actual application's template folder. That way you can
easily override templates that a blueprint provides in the actual application. easily override templates that a blueprint provides in the actual application.
This also means that if you don't want a blueprint template to be accidentally This also means that if you don't want a blueprint template to be accidentally
overridden, make sure that no other blueprint or actual application template overridden, make sure that no other blueprint or actual application template
has the same relative path. When multiple blueprints provide the same relative has the same relative path. When multiple blueprints provide the same relative
@ -194,7 +194,7 @@ want to render the template ``'admin/index.html'`` and you have provided
this: :file:`yourapplication/admin/templates/admin/index.html`. The reason this: :file:`yourapplication/admin/templates/admin/index.html`. The reason
for the extra ``admin`` folder is to avoid getting our template overridden for the extra ``admin`` folder is to avoid getting our template overridden
by a template named ``index.html`` in the actual application template by a template named ``index.html`` in the actual application template
folder. folder.
To further reiterate this: if you have a blueprint named ``admin`` and you To further reiterate this: if you have a blueprint named ``admin`` and you
want to render a template called :file:`index.html` which is specific to this want to render a template called :file:`index.html` which is specific to this
@ -245,4 +245,22 @@ Here is an example for a "404 Page Not Found" exception::
def page_not_found(e): def page_not_found(e):
return render_template('pages/404.html') return render_template('pages/404.html')
Most errorhandlers will simply work as expected; however, there is a caveat
concerning handlers for 404 and 405 exceptions. These errorhandlers are only
invoked from an appropriate ``raise`` statement or a call to ``abort`` in another
of the blueprint's view functions; they are not invoked by, e.g., an invalid URL
access. This is because the blueprint does not "own" a certain URL space, so
the application instance has no way of knowing which blueprint errorhandler it
should run if given an invalid URL. If you would like to execute different
handling strategies for these errors based on URL prefixes, they may be defined
at the application level using the ``request`` proxy object::
@app.errorhandler(404)
@app.errorhandler(405)
def _handle_api_error(ex):
if request.path.startswith('/api/'):
return jsonify_error(ex)
else:
return ex
More information on error handling see :ref:`errorpages`. More information on error handling see :ref:`errorpages`.

8
docs/conf.py

@ -38,6 +38,14 @@ extensions = [
'flaskdocext' 'flaskdocext'
] ]
try:
__import__('sphinxcontrib.log_cabinet')
except ImportError:
print('sphinxcontrib-log-cabinet is not installed.')
print('Changelog directives will not be re-organized.')
else:
extensions.append('sphinxcontrib.log_cabinet')
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ['_templates']

14
docs/config.rst

@ -132,13 +132,13 @@ The following configuration values are used internally by Flask:
by default enables URL generation by default enables URL generation
without a request context but with an without a request context but with an
application context. application context.
``APPLICATION_ROOT`` If the application does not occupy ``APPLICATION_ROOT`` The path value used for the session
a whole domain or subdomain this can cookie if ``SESSION_COOKIE_PATH`` isn't
be set to the path where the application set. If it's also ``None`` ``'/'`` is used.
is configured to live. This is for Note that to actually serve your Flask
session cookie as path value. If app under a subpath you need to tell
domains are used, this should be your WSGI container the ``SCRIPT_NAME``
``None``. WSGI environment variable.
``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will ``MAX_CONTENT_LENGTH`` If set to a value in bytes, Flask will
reject incoming requests with a reject incoming requests with a
content length greater than this by content length greater than this by

230
docs/installation.rst

@ -3,163 +3,173 @@
Installation Installation
============ ============
Flask depends on some external libraries, like `Werkzeug Python Version
<http://werkzeug.pocoo.org/>`_ and `Jinja2 <http://jinja.pocoo.org/>`_. --------------
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 quickly? There are many ways you We recommend using the latest version of Python 3. Flask supports Python 3.3
could do that, but the most kick-ass method is virtualenv, so let's have a look and newer, Python 2.6 and newer, and PyPy.
at that first.
You will need Python 2.6 or newer to get started, so be sure to have an Dependencies
up-to-date Python 2.x installation. For using Flask with Python 3 have a ------------
look at :ref:`python3-support`.
.. _virtualenv: These distributions will be installed automatically when installing Flask.
virtualenv * `Werkzeug`_ implements WSGI, the standard Python interface between
---------- applications and servers.
* `Jinja`_ is a template language that renders the pages your application
serves.
* `MarkupSafe`_ comes with Jinja. It escapes untrusted input when rendering
templates to avoid injection attacks.
* `ItsDangerous`_ securely signs data to ensure its integrity. This is used
to protect Flask's session cookie.
* `Click`_ is a framework for writing command line applications. It provides
the ``flask`` command and allows adding custom management commands.
Virtualenv is probably what you want to use during development, and if you have .. _Werkzeug: http://werkzeug.pocoo.org/
shell access to your production machines, you'll probably want to use it there, .. _Jinja: http://jinja.pocoo.org/
too. .. _MarkupSafe: https://pypi.python.org/pypi/MarkupSafe
.. _ItsDangerous: https://pythonhosted.org/itsdangerous/
.. _Click: http://click.pocoo.org/
What problem does virtualenv solve? If you like Python as much as I do, Optional dependencies
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?
Virtualenv to the rescue! Virtualenv enables multiple side-by-side These distributions will not be installed automatically. Flask will detect and
installations of Python, one for each project. It doesn't actually install use them if you install them.
separate copies of Python, but it does provide a clever way to keep different
project environments isolated. Let's see how virtualenv works.
If you are on Mac OS X or Linux, chances are that the following * `Blinker`_ provides support for :ref:`signals`.
command will work for you:: * `SimpleJSON`_ is a fast JSON implementation that is compatible with
Python's ``json`` module. It is preferred for JSON operations if it is
installed.
$ sudo pip install virtualenv .. _Blinker: https://pythonhosted.org/blinker/
.. _SimpleJSON: https://simplejson.readthedocs.io/
It will probably install virtualenv on your system. Maybe it's even Virtual environments
in your package manager. If you use Ubuntu, try:: --------------------
$ sudo apt-get install python-virtualenv Use a virtual environment to manage the dependencies for your project, both in
development and in production.
If you are on Windows and don't have the ``easy_install`` command, you must What problem does a virtual environment solve? The more Python projects you
install it first. Check the :ref:`windows-easy-install` section for more have, the more likely it is that you need to work with different versions of
information about how to do that. Once you have it installed, run the same Python libraries, or even Python itself. Newer versions of libraries for one
commands as above, but without the ``sudo`` prefix. project can break compatibility in another project.
Once you have virtualenv installed, just fire up a shell and create Virtual environments are independent groups of Python libraries, one for each
your own environment. I usually create a project folder and a :file:`venv` project. Packages installed for one project will not affect other projects or
folder within:: the operating system's packages.
$ mkdir myproject Python 3 comes bundled with the :mod:`venv` module to create virtual
$ cd myproject environments. If you're using a modern version of Python, you can continue on
$ virtualenv venv to the next section.
New python executable in venv/bin/python
Installing setuptools, pip............done.
Now, whenever you want to work on a project, you only have to activate the If you're using Python 2, see :ref:`install-install-virtualenv` first.
corresponding environment. On OS X and Linux, do the following::
$ . venv/bin/activate .. _install-create-env:
If you are a Windows user, the following command is for you:: Create an environment
~~~~~~~~~~~~~~~~~~~~~
$ venv\Scripts\activate Create a project folder and a :file:`venv` folder within:
Either way, you should now be using your virtualenv (notice how the prompt of .. code-block:: sh
your shell has changed to show the active environment).
And if you want to go back to the real world, use the following command:: mkdir myproject
cd myproject
python3 -m venv venv
$ deactivate On Windows:
After doing this, the prompt of your shell should be as familiar as before. .. code-block:: bat
Now, let's move on. Enter the following command to get Flask activated in your py -3 -m venv venv
virtualenv::
$ pip install Flask If you needed to install virtualenv because you are on an older version of
Python, use the following command instead:
A few seconds later and you are good to go. .. code-block:: sh
virtualenv venv
System-Wide Installation On Windows:
------------------------
This is possible as well, though I do not recommend it. Just run .. code-block:: bat
``pip`` with root privileges::
$ sudo pip install Flask \Python27\Scripts\virtualenv.exe venv
(On Windows systems, run it in a command-prompt window with administrator Activate the environment
privileges, and leave out ``sudo``.) ~~~~~~~~~~~~~~~~~~~~~~~~
Before you work on your project, activate the corresponding environment:
Living on the Edge .. code-block:: sh
------------------
. venv/bin/activate
On Windows:
.. code-block:: bat
venv\Scripts\activate
Your shell prompt will change to show the name of the activated environment.
Install Flask
-------------
If you want to work with the latest version of Flask, there are two ways: you Within the activated environment, use the following command to install Flask:
can either let ``pip`` pull in the development version, or you can tell
it to operate on a git checkout. Either way, virtualenv is recommended.
Get the git checkout in a new virtualenv and run in development mode:: .. code-block:: sh
$ git clone https://github.com/pallets/flask.git pip install Flask
Initialized empty Git repository in ~/dev/flask/.git/
$ cd flask Living on the edge
$ virtualenv venv ~~~~~~~~~~~~~~~~~~
New python executable in venv/bin/python
Installing setuptools, pip............done. If you want to work with the latest Flask code before it's released, install or
$ . venv/bin/activate update the code from the master branch:
$ python setup.py develop
... .. code-block:: sh
Finished processing dependencies for Flask
pip install -U https://github.com/pallets/flask/archive/master.tar.gz
.. _install-install-virtualenv:
Install virtualenv
------------------
This will pull in the dependencies and activate the git head as the current If you are using Python 2, the venv module is not available. Instead,
version inside the virtualenv. Then all you have to do is run ``git pull install `virtualenv`_.
origin`` to update to the latest version.
.. _windows-easy-install: On Linux, virtualenv is provided by your package manager:
`pip` and `setuptools` on Windows .. code-block:: sh
---------------------------------
Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools`` # Debian, Ubuntu
and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial sudo apt-get install python-virtualenv
package you will need is pip - this will let you install
anything else (like virtualenv). Fortunately there is a "bootstrap script"
you can run to install.
If you don't currently have ``pip``, then `get-pip.py` will install it for you. # CentOS, Fedora
sudo yum install python-virtualenv
`get-pip.py`_ # Arch
sudo pacman -S python-virtualenv
It should be double-clickable once you download it. If you already have ``pip``, If you are on Mac OS X or Windows, download `get-pip.py`_, then:
you can upgrade them by running::
> pip install --upgrade pip setuptools .. code-block:: sh
Most often, once you pull up a command prompt you want to be able to type ``pip`` sudo python2 Downloads/get-pip.py
and ``python`` which will run those things, but this might not automatically happen sudo python2 -m pip install virtualenv
on Windows, because it doesn't know where those executables are (give either a try!).
To fix this, you should be able to navigate to your Python install directory On Windows, as an administrator:
(e.g :file:`C:\Python27`), then go to :file:`Tools`, then :file:`Scripts`, then find the
:file:`win_add2path.py` file and run that. Open a **new** Command Prompt and
check that you can now just type ``python`` to bring up the interpreter.
Finally, to install `virtualenv`_, you can simply run:: .. code-block:: bat
> pip install virtualenv \Python27\python.exe Downloads\get-pip.py
\Python27\python.exe -m pip install virtualenv
Then you can be off on your way following the installation instructions above. Now you can continue to :ref:`install-create-env`.
.. _virtualenv: https://virtualenv.pypa.io/
.. _get-pip.py: https://bootstrap.pypa.io/get-pip.py .. _get-pip.py: https://bootstrap.pypa.io/get-pip.py

82
docs/patterns/deferredcallbacks.rst

@ -3,71 +3,43 @@
Deferred Request Callbacks Deferred Request Callbacks
========================== ==========================
One of the design principles of Flask is that response objects are created One of the design principles of Flask is that response objects are created and
and passed down a chain of potential callbacks that can modify them or passed down a chain of potential callbacks that can modify them or replace
replace them. When the request handling starts, there is no response them. When the request handling starts, there is no response object yet. It is
object yet. It is created as necessary either by a view function or by created as necessary either by a view function or by some other component in
some other component in the system. the system.
But what happens if you want to modify the response at a point where the What happens if you want to modify the response at a point where the response
response does not exist yet? A common example for that would be a does not exist yet? A common example for that would be a
before-request function that wants to set a cookie on the response object. :meth:`~flask.Flask.before_request` callback that wants to set a cookie on the
response object.
One way is to avoid the situation. Very often that is possible. For
instance you can try to move that logic into an after-request callback One way is to avoid the situation. Very often that is possible. For instance
instead. Sometimes however moving that code there is just not a very you can try to move that logic into a :meth:`~flask.Flask.after_request`
pleasant experience or makes code look very awkward. callback instead. However, sometimes moving code there makes it more
more complicated or awkward to reason about.
As an alternative possibility you can attach a bunch of callback functions
to the :data:`~flask.g` object and call them at the end of the request. As an alternative, you can use :func:`~flask.after_this_request` to register
This way you can defer code execution from anywhere in the application. callbacks that will execute after only the current request. This way you can
defer code execution from anywhere in the application, based on the current
request.
The Decorator
-------------
The following decorator is the key. It registers a function on a list on
the :data:`~flask.g` object::
from flask import g
def after_this_request(f):
if not hasattr(g, 'after_request_callbacks'):
g.after_request_callbacks = []
g.after_request_callbacks.append(f)
return f
Calling the Deferred
--------------------
Now you can use the `after_this_request` decorator to mark a function to
be called at the end of the request. But we still need to call them. For
this the following function needs to be registered as
:meth:`~flask.Flask.after_request` callback::
@app.after_request
def call_after_request_callbacks(response):
for callback in getattr(g, 'after_request_callbacks', ()):
callback(response)
return response
A Practical Example
-------------------
At any time during a request, we can register a function to be called at the At any time during a request, we can register a function to be called at the
end of the request. For example you can remember the current language of the end of the request. For example you can remember the current language of the
user in a cookie in the before-request function:: user in a cookie in a :meth:`~flask.Flask.before_request` callback::
from flask import request from flask import request, after_this_request
@app.before_request @app.before_request
def detect_user_language(): def detect_user_language():
language = request.cookies.get('user_lang') language = request.cookies.get('user_lang')
if language is None: if language is None:
language = guess_language_from_request() language = guess_language_from_request()
# when the response exists, set a cookie with the language
@after_this_request @after_this_request
def remember_language(response): def remember_language(response):
response.set_cookie('user_lang', language) response.set_cookie('user_lang', language)
g.language = language g.language = language

250
docs/quickstart.rst

@ -159,14 +159,11 @@ Have another debugger in mind? See :ref:`working-with-debuggers`.
Routing Routing
------- -------
Modern web applications have beautiful URLs. This helps people remember Modern web applications use meaningful URLs to help users. Users are more
the URLs, which is especially handy for applications that are used from likely to like a page and come back if the page uses a meaningful URL they can
mobile devices with slower network connections. If the user can directly remember and use to directly visit a page.
go to the desired page without having to hit the index page it is more
likely they will like the page and come back next time.
As you have seen above, the :meth:`~flask.Flask.route` decorator is used to Use the :meth:`~flask.Flask.route` decorator to bind a function to a URL. ::
bind a function to a URL. Here are some basic examples::
@app.route('/') @app.route('/')
def index(): def index():
@ -176,16 +173,16 @@ bind a function to a URL. Here are some basic examples::
def hello(): def hello():
return 'Hello, World' return 'Hello, World'
But there is more to it! You can make certain parts of the URL dynamic and You can do more! You can make parts of the URL dynamic and attach multiple
attach multiple rules to a function. rules to a function.
Variable Rules Variable Rules
`````````````` ``````````````
To add variable parts to a URL you can mark these special sections as You can add variable sections to a URL by marking sections with
``<variable_name>``. Such a part is then passed as a keyword argument to your ``<variable_name>``. Your function then receives the ``<variable_name>``
function. Optionally a converter can be used by specifying a rule with as a keyword argument. Optionally, you can use a converter to specify the type
``<converter:variable_name>``. Here are some nice examples:: of the argument like ``<converter:variable_name>``. ::
@app.route('/user/<username>') @app.route('/user/<username>')
def show_user_profile(username): def show_user_profile(username):
@ -197,175 +194,122 @@ function. Optionally a converter can be used by specifying a rule with
# show the post with the given id, the id is an integer # show the post with the given id, the id is an integer
return 'Post %d' % post_id return 'Post %d' % post_id
The following converters exist: @app.route('/path/<path:subpath>')
def show_subpath(subpath):
# show the subpath after /path/
return 'Subpath %s' % subpath
=========== =============================================== Converter types:
`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
`any` matches one of the items provided
`uuid` accepts UUID strings
=========== ===============================================
.. admonition:: Unique URLs / Redirection Behavior ========== ==========================================
``string`` (default) accepts any text without a slash
``int`` accepts positive integers
``float`` accepts positive floating point values
``path`` like ``string`` but also accepts slashes
``uuid`` accepts UUID strings
========== ==========================================
Flask's URL rules are based on Werkzeug's routing module. The idea Unique URLs / Redirection Behavior
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:: Take these two rules::
@app.route('/projects/') @app.route('/projects/')
def projects(): def projects():
return 'The project page' return 'The project page'
@app.route('/about') @app.route('/about')
def about(): def about():
return 'The about page' return 'The about page'
Though they look rather similar, they differ in their use of the trailing Though they look similar, they differ in their use of the trailing slash in
slash in the URL *definition*. In the first case, the canonical URL for the the URL. In the first case, the canonical URL for the ``projects`` endpoint
``projects`` endpoint has a trailing slash. In that sense, it is similar to uses a trailing slash. It's similar to a folder in a file system; if you
a folder on a filesystem. Accessing it without a trailing slash will cause access the URL without a trailing slash, Flask redirects you to the
Flask to redirect to the canonical URL with the trailing slash. canonical URL with the trailing slash.
In the second case, however, the URL is defined without a trailing slash, In the second case, however, the URL definition lacks a trailing slash,
rather like the pathname of a file on UNIX-like systems. Accessing the URL like the pathname of a file on UNIX-like systems. Accessing the URL with a
with a trailing slash will produce a 404 "Not Found" error. trailing slash produces a 404 “Not Found” error.
This behavior allows relative URLs to continue working even if the trailing
slash is omitted, consistent with how Apache and other servers work. Also,
the URLs will stay unique, which helps search engines avoid indexing the
same page twice.
This behavior allows relative URLs to continue working even if the trailing
slash is omitted, consistent with how Apache and other servers work. Also,
the URLs will stay unique, which helps search engines avoid indexing the
same page twice.
.. _url-building: .. _url-building:
URL Building URL Building
```````````` ````````````
If it can match URLs, can Flask also generate them? Of course it can. To To build a URL to a specific function, use the :func:`~flask.url_for` function.
build a URL to a specific function you can use the :func:`~flask.url_for` It accepts the name of the function as its first argument and any number of
function. It accepts the name of the function as first argument and a number keyword arguments, each corresponding to a variable part of the URL rule.
of keyword arguments, each corresponding to the variable part of the URL rule. Unknown variable parts are appended to the URL as query parameters.
Unknown variable parts are appended to the URL as query parameters. Here are
some examples::
>>> from flask import Flask, url_for
>>> app = Flask(__name__)
>>> @app.route('/')
... def index(): pass
...
>>> @app.route('/login')
... def login(): pass
...
>>> @app.route('/user/<username>')
... def profile(username): pass
...
>>> with app.test_request_context():
... print(url_for('index'))
... print(url_for('login'))
... print(url_for('login', next='/'))
... print(url_for('profile', username='John Doe'))
...
/
/login
/login?next=/
/user/John%20Doe
(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 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 using the URL reversing function Why would you want to build URLs using the URL reversing function
:func:`~flask.url_for` instead of hard-coding them into your templates? :func:`~flask.url_for` instead of hard-coding them into your templates?
There are three good reasons for this:
1. Reversing is often more descriptive than hard-coding the URLs. More 1. Reversing is often more descriptive than hard-coding the URLs.
importantly, it allows you to change URLs in one go, without having to 2. You can change your URLs in one go instead of needing to remember to
remember to change URLs all over the place. manually change hard-coded URLs.
2. URL building will handle escaping of special characters and Unicode 3. URL building handles escaping of special characters and Unicode data
data transparently for you, so you don't have to deal with them. transparently.
3. If your application is placed outside the URL root - say, in 4. If your application is placed outside the URL root, for example, in
``/myapplication`` instead of ``/`` - :func:`~flask.url_for` will handle ``/myapplication`` instead of ``/``, :func:`~flask.url_for` properly
that properly for you. handles that for you.
For example, here we use the :meth:`~flask.Flask.test_request_context` method
to try out :func:`~flask.url_for`. :meth:`~flask.Flask.test_request_context`
tells Flask to behave as though it's handling a request even while we use a
Python shell. See :ref:`context-locals`. ::
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def index():
return 'index'
@app.route('/login')
def login():
return 'login'
@app.route('/user/<username>')
def profile(username):
return '{}'s profile'.format(username)
with app.test_request_context():
print(url_for('index'))
print(url_for('login'))
print(url_for('login', next='/'))
print(url_for('profile', username='John Doe'))
/
/login
/login?next=/
/user/John%20Doe
HTTP Methods HTTP Methods
```````````` ````````````
HTTP (the protocol web applications are speaking) knows different methods for Web applications use different HTTP methods when accessing URLs. You should
accessing URLs. By default, a route only answers to ``GET`` requests, but that familiarize yourself with the HTTP methods as you work with Flask. By default,
can be changed by providing the ``methods`` argument to the a route only answers to ``GET`` requests. You can use the ``methods`` argument
:meth:`~flask.Flask.route` decorator. Here are some examples:: of the :meth:`~flask.Flask.route` decorator to handle different HTTP methods.
::
from flask import request
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
if request.method == 'POST': if request.method == 'POST':
return do_the_login() do_the_login()
else: else:
return show_the_login_form() show_the_login_form()
If ``GET`` is present, ``HEAD`` will be added automatically for you. You If ``GET`` is present, Flask automatically adds support for the ``HEAD`` method
don't have to deal with that. It will also make sure that ``HEAD`` requests and handles ``HEAD`` requests according to the the `HTTP RFC`_. Likewise,
are handled as the `HTTP RFC`_ (the document describing the HTTP ``OPTIONS`` is automatically implemented for you.
protocol) demands, so you can completely ignore that part of the HTTP
specification. Likewise, as of Flask 0.6, ``OPTIONS`` is implemented for you
automatically as well.
You have no idea what an HTTP method is? Worry not, here is a quick
introduction to HTTP methods and why they matter:
The HTTP method (also often called "the verb") tells the server what the
client wants to *do* with the requested page. The following methods are
very common:
``GET``
The browser tells the server to just *get* the information stored on
that page and send it. This is probably the most common method.
``HEAD``
The browser tells the server to get the information, but it is only
interested in the *headers*, not the content of the page. An
application is supposed to handle that as if a ``GET`` request was
received but to not deliver the actual content. In Flask you don't
have to deal with that at all, the underlying Werkzeug library handles
that for you.
``POST``
The browser tells the server that it wants to *post* some new
information to that URL and that the server must ensure the data is
stored and only stored once. This is how HTML forms usually
transmit data to the server.
``PUT``
Similar to ``POST`` but the server might trigger the store procedure
multiple times by overwriting the old values more than once. Now you
might be asking why this is useful, but there are some good reasons
to do it this way. Consider that the connection is lost during
transmission: in this situation a system between the browser and the
server might receive the request safely a second time without breaking
things. With ``POST`` that would not be possible because it must only
be triggered once.
``DELETE``
Remove the information at the given location.
``OPTIONS``
Provides a quick way for a client to figure out which methods are
supported by this URL. Starting with Flask 0.6, this is implemented
for you automatically.
Now the interesting part is that in HTML4 and XHTML1, the only methods a
form can submit to the server are ``GET`` and ``POST``. But with JavaScript
and future HTML standards you can use the other methods as well. Furthermore
HTTP has become quite popular lately and browsers are no longer the only
clients that are using HTTP. For instance, many revision control systems
use it.
.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt .. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt

22
flask/helpers.py

@ -10,6 +10,7 @@
""" """
import os import os
import socket
import sys import sys
import pkgutil import pkgutil
import posixpath import posixpath
@ -976,3 +977,24 @@ def total_seconds(td):
:rtype: int :rtype: int
""" """
return td.days * 60 * 60 * 24 + td.seconds return td.days * 60 * 60 * 24 + td.seconds
def is_ip(value):
"""Determine if the given string is an IP address.
:param value: value to check
:type value: str
:return: True if string is an IP address
:rtype: bool
"""
for family in (socket.AF_INET, socket.AF_INET6):
try:
socket.inet_pton(family, value)
except socket.error:
pass
else:
return True
return False

1
flask/json.py

@ -13,7 +13,6 @@ import uuid
from datetime import date from datetime import date
from .globals import current_app, request from .globals import current_app, request
from ._compat import text_type, PY2 from ._compat import text_type, PY2
from .ctx import has_request_context
from werkzeug.http import http_date from werkzeug.http import http_date
from jinja2 import Markup from jinja2 import Markup

81
flask/sessions.py

@ -11,13 +11,14 @@
import uuid import uuid
import hashlib import hashlib
import warnings
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
from datetime import datetime from datetime import datetime
from werkzeug.http import http_date, parse_date from werkzeug.http import http_date, parse_date
from werkzeug.datastructures import CallbackDict from werkzeug.datastructures import CallbackDict
from . import Markup, json from . import Markup, json
from ._compat import iteritems, text_type from ._compat import iteritems, text_type
from .helpers import total_seconds from .helpers import total_seconds, is_ip
from itsdangerous import URLSafeTimedSerializer, BadSignature from itsdangerous import URLSafeTimedSerializer, BadSignature
@ -200,30 +201,62 @@ class SessionInterface(object):
return isinstance(obj, self.null_session_class) return isinstance(obj, self.null_session_class)
def get_cookie_domain(self, app): def get_cookie_domain(self, app):
"""Helpful helper method that returns the cookie domain that should """Returns the domain that should be set for the session cookie.
be used for the session cookie if session cookies are used.
Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
falls back to detecting the domain based on ``SERVER_NAME``.
Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is
updated to avoid re-running the logic.
""" """
if app.config['SESSION_COOKIE_DOMAIN'] is not None:
return app.config['SESSION_COOKIE_DOMAIN'] rv = app.config['SESSION_COOKIE_DOMAIN']
if app.config['SERVER_NAME'] is not None:
# chop off the port which is usually not supported by browsers # set explicitly, or cached from SERVER_NAME detection
rv = '.' + app.config['SERVER_NAME'].rsplit(':', 1)[0] # if False, return None
if rv is not None:
# Google chrome does not like cookies set to .localhost, so return rv if rv else None
# we just go with no domain then. Flask documents anyways that
# cross domain cookies need a fully qualified domain name rv = app.config['SERVER_NAME']
if rv == '.localhost':
rv = None # server name not set, cache False to return none next time
if not rv:
# If we infer the cookie domain from the server name we need app.config['SESSION_COOKIE_DOMAIN'] = False
# to check if we are in a subpath. In that case we can't return None
# set a cross domain cookie.
if rv is not None: # chop off the port which is usually not supported by browsers
path = self.get_cookie_path(app) # remove any leading '.' since we'll add that later
if path != '/': rv = rv.rsplit(':', 1)[0].lstrip('.')
rv = rv.lstrip('.')
if '.' not in rv:
return rv # Chrome doesn't allow names without a '.'
# this should only come up with localhost
# hack around this by not setting the name, and show a warning
warnings.warn(
'"{rv}" is not a valid cookie domain, it must contain a ".".'
' Add an entry to your hosts file, for example'
' "{rv}.localdomain", and use that instead.'.format(rv=rv)
)
app.config['SESSION_COOKIE_DOMAIN'] = False
return None
ip = is_ip(rv)
if ip:
warnings.warn(
'The session cookie domain is an IP address. This may not work'
' as intended in some browsers. Add an entry to your hosts'
' file, for example "localhost.localdomain", and use that'
' instead.'
)
# if this is not an ip and app is mounted at the root, allow subdomain
# matching by adding a '.' prefix
if self.get_cookie_path(app) == '/' and not ip:
rv = '.' + rv
app.config['SESSION_COOKIE_DOMAIN'] = rv
return rv
def get_cookie_path(self, app): def get_cookie_path(self, app):
"""Returns the path for which the cookie should be valid. The """Returns the path for which the cookie should be valid. The

36
tests/test_basic.py

@ -351,6 +351,42 @@ def test_session_using_session_settings():
assert 'httponly' not in cookie assert 'httponly' not in cookie
def test_session_localhost_warning(recwarn):
app = flask.Flask(__name__)
app.config.update(
SECRET_KEY='testing',
SERVER_NAME='localhost:5000',
)
@app.route('/')
def index():
flask.session['testing'] = 42
return 'testing'
rv = app.test_client().get('/', 'http://localhost:5000/')
assert 'domain' not in rv.headers['set-cookie'].lower()
w = recwarn.pop(UserWarning)
assert '"localhost" is not a valid cookie domain' in str(w.message)
def test_session_ip_warning(recwarn):
app = flask.Flask(__name__)
app.config.update(
SECRET_KEY='testing',
SERVER_NAME='127.0.0.1:5000',
)
@app.route('/')
def index():
flask.session['testing'] = 42
return 'testing'
rv = app.test_client().get('/', 'http://127.0.0.1:5000/')
assert 'domain=127.0.0.1' in rv.headers['set-cookie'].lower()
w = recwarn.pop(UserWarning)
assert 'cookie domain is an IP' in str(w.message)
def test_missing_session(): def test_missing_session():
app = flask.Flask(__name__) app = flask.Flask(__name__)

Loading…
Cancel
Save