From a3bbd155a3b16aaa54ebb2baf89f17561658a347 Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Mon, 8 Jun 2015 16:31:35 -0500 Subject: [PATCH 001/153] Add kwarg to disable auto OPTIONS on add_url_rule Adds support for a kwarg `provide_automatic_options` on `add_url_rule`, which lets you turn off the automatic OPTIONS response on a per-URL basis even if your view functions are functions, not classes (so you can't provide attrs on them). --- flask/app.py | 6 ++++-- tests/test_basic.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index f0a8b69b..5b186577 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1013,8 +1013,10 @@ class Flask(_PackageBoundObject): # starting with Flask 0.8 the view_func object can disable and # force-enable the automatic options handling. - provide_automatic_options = getattr(view_func, - 'provide_automatic_options', None) + provide_automatic_options = options.pop( + 'provide_automatic_options', getattr(view_func, + 'provide_automatic_options', None)) + if provide_automatic_options is None: if 'OPTIONS' not in methods: diff --git a/tests/test_basic.py b/tests/test_basic.py index 695e346e..08e03aca 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1604,3 +1604,44 @@ def test_run_server_port(monkeypatch): hostname, port = 'localhost', 8000 app.run(hostname, port, debug=True) assert rv['result'] == 'running on %s:%s ...' % (hostname, port) + + +def test_disable_automatic_options(): + # Issue 1488: Add support for a kwarg to add_url_rule to disable the auto OPTIONS response + app = flask.Flask(__name__) + + def index(): + return flask.request.method + + def more(): + return flask.request.method + + app.add_url_rule('/', 'index', index, provide_automatic_options=False) + app.add_url_rule('/more', 'more', more, methods=['GET', 'POST'], provide_automatic_options=False) + + c = app.test_client() + assert c.get('/').data == b'GET' + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD'] + # Older versions of Werkzeug.test.Client don't have an options method + if hasattr(c, 'options'): + rv = c.options('/') + else: + rv = c.open('/', method='OPTIONS') + assert rv.status_code == 405 + + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == b'POST' + assert c.get('/more').data == b'GET' + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'POST'] + # Older versions of Werkzeug.test.Client don't have an options method + if hasattr(c, 'options'): + rv = c.options('/more') + else: + rv = c.open('/more', method='OPTIONS') + assert rv.status_code == 405 From 82d3ce1a18b8b5411028062e8b77b3c70584372f Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Tue, 7 Jul 2015 13:21:51 -0500 Subject: [PATCH 002/153] Updated changelog --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index b33c3795..d1529526 100644 --- a/CHANGES +++ b/CHANGES @@ -70,6 +70,9 @@ Version 1.0 - ``flask.g`` now has ``pop()`` and ``setdefault`` methods. - Turn on autoescape for ``flask.templating.render_template_string`` by default (pull request ``#1515``). +- Added support for `provide_automatic_options` in `**kwargs` on + :meth:`add_url_rule` to turn off automatic OPTIONS when the `view_func` + argument is not a class (pull request ``#1489``). Version 0.10.2 -------------- From 588d3519454543c185d818869cdbb3b151e86cf9 Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Mon, 14 Sep 2015 13:25:52 -0500 Subject: [PATCH 003/153] provide_automatic_options as explicit arg In add_url_rule, break provide_automatic_options out to an explicit kwarg, and add notes to the docstring. --- flask/app.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/flask/app.py b/flask/app.py index e03ff221..de147e9e 100644 --- a/flask/app.py +++ b/flask/app.py @@ -944,7 +944,8 @@ class Flask(_PackageBoundObject): return iter(self._blueprint_order) @setupmethod - def add_url_rule(self, rule, endpoint=None, view_func=None, **options): + def add_url_rule(self, rule, endpoint=None, view_func=None, + provide_automatic_options=None, **options): """Connects a URL rule. Works exactly like the :meth:`route` decorator. If a view_func is provided it will be registered with the endpoint. @@ -984,6 +985,10 @@ class Flask(_PackageBoundObject): endpoint :param view_func: the function to call when serving a request to the provided endpoint + :param provide_automatic_options: controls whether ``OPTIONS`` should + be provided automatically. If this + is not set, will check attributes on + the view or list of methods. :param options: the options to be forwarded to the underlying :class:`~werkzeug.routing.Rule` object. A change to Werkzeug is handling of method options. methods @@ -1013,10 +1018,9 @@ class Flask(_PackageBoundObject): # starting with Flask 0.8 the view_func object can disable and # force-enable the automatic options handling. - provide_automatic_options = options.pop( - 'provide_automatic_options', getattr(view_func, - 'provide_automatic_options', None)) - + if provide_automatic_options is None: + provide_automatic_options = getattr(view_func, + 'provide_automatic_options', None) if provide_automatic_options is None: if 'OPTIONS' not in methods: From ca30de73a32d5fa42dff461ddfdcf030f0b87716 Mon Sep 17 00:00:00 2001 From: Jimmy McCarthy Date: Mon, 14 Sep 2015 13:29:16 -0500 Subject: [PATCH 004/153] Update change log --- CHANGES | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index cac78f73..b459fc1d 100644 --- a/CHANGES +++ b/CHANGES @@ -70,9 +70,9 @@ Version 1.0 - ``flask.g`` now has ``pop()`` and ``setdefault`` methods. - Turn on autoescape for ``flask.templating.render_template_string`` by default (pull request ``#1515``). -- Added support for `provide_automatic_options` in `**kwargs` on - :meth:`add_url_rule` to turn off automatic OPTIONS when the `view_func` - argument is not a class (pull request ``#1489``). +- Added support for `provide_automatic_options` in :meth:`add_url_rule` to + turn off automatic OPTIONS when the `view_func` argument is not a class + (pull request ``#1489``). Version 0.10.2 -------------- From 99eda625f63fe6e137f626cce7d510bc7d8b69d9 Mon Sep 17 00:00:00 2001 From: Randy Liou Date: Fri, 3 Jun 2016 15:50:38 -0700 Subject: [PATCH 005/153] Enhance code coverage for Blueprint.endpoint Add basic test for the endpoint decorator for the Blueprint object. --- tests/test_blueprints.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index a3309037..de293e7f 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -355,6 +355,25 @@ def test_route_decorator_custom_endpoint_with_dots(): rv = c.get('/py/bar/123') assert rv.status_code == 404 + +def test_endpoint_decorator(): + from werkzeug.routing import Rule + app = flask.Flask(__name__) + app.url_map.add(Rule('/foo', endpoint='bar')) + + bp = flask.Blueprint('bp', __name__) + + @bp.endpoint('bar') + def foobar(): + return flask.request.endpoint + + app.register_blueprint(bp, url_prefix='/bp_prefix') + + c = app.test_client() + assert c.get('/foo').data == b'bar' + assert c.get('/bp_prefix/bar').status_code == 404 + + def test_template_filter(): bp = flask.Blueprint('bp', __name__) @bp.app_template_filter() From a098a9a625c2e7b68c53cd5a771f3ae5c0e8730f Mon Sep 17 00:00:00 2001 From: Sudheer Satyanarayana Date: Tue, 21 Jun 2016 20:57:03 +0530 Subject: [PATCH 006/153] Fix small typo in python3 docs Add missing 'be' --- docs/python3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/python3.rst b/docs/python3.rst index 4d488f16..af61584c 100644 --- a/docs/python3.rst +++ b/docs/python3.rst @@ -23,8 +23,8 @@ specifically added for the Python 3 support and are quite new. Unless you require absolute compatibility, you should be fine with Python 3 nowadays. Most libraries and Flask extensions have been ported by now and using Flask with Python 3 is generally a smooth ride. However, keep in mind -that most libraries (including Werkzeug and Flask) might not quite as stable -on Python 3 yet. You might therefore sometimes run into bugs that are +that most libraries (including Werkzeug and Flask) might not be quite as +stable on Python 3 yet. You might therefore sometimes run into bugs that are usually encoding-related. The majority of the upgrade pain is in the lower-level libraries like From f5dc8b38802faf9f6da7f69aac11d6592590bb97 Mon Sep 17 00:00:00 2001 From: Olexander Yermakov Date: Tue, 5 Jul 2016 22:00:43 +0300 Subject: [PATCH 007/153] Update installation documentation for using 'pip' command (#1920) --- docs/installation.rst | 47 +++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 638d07ce..91d95270 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -40,24 +40,20 @@ installations of Python, one for each project. It doesn't actually install separate copies of Python, but it does provide a clever way to keep different project environments isolated. Let's see how virtualenv works. -If you are on Mac OS X or Linux, chances are that one of the following two -commands will work for you:: - - $ sudo easy_install virtualenv - -or even better:: +If you are on Mac OS X or Linux, chances are that the following +command will work for you:: $ sudo pip install virtualenv -One of these will probably install virtualenv on your system. Maybe it's even +It will probably install virtualenv on your system. Maybe it's even in your package manager. If you use Ubuntu, try:: $ sudo apt-get install python-virtualenv -If you are on Windows and don't have the :command:`easy_install` command, you must +If you are on Windows and don't have the ``easy_install`` command, you must install it first. Check the :ref:`windows-easy-install` section for more information about how to do that. Once you have it installed, run the same -commands as above, but without the :command:`sudo` prefix. +commands as above, but without the ``sudo`` prefix. Once you have virtualenv installed, just fire up a shell and create your own environment. I usually create a project folder and a :file:`venv` @@ -99,19 +95,19 @@ System-Wide Installation ------------------------ This is possible as well, though I do not recommend it. Just run -:command:`pip` with root privileges:: +``pip`` with root privileges:: $ sudo pip install Flask (On Windows systems, run it in a command-prompt window with administrator -privileges, and leave out :command:`sudo`.) +privileges, and leave out ``sudo``.) Living on the Edge ------------------ If you want to work with the latest version of Flask, there are two ways: you -can either let :command:`pip` pull in the development version, or you can tell +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:: @@ -131,40 +127,34 @@ This will pull in the dependencies and activate the git head as the current version inside the virtualenv. Then all you have to do is run ``git pull origin`` to update to the latest version. - .. _windows-easy-install: `pip` and `setuptools` on Windows --------------------------------- -Sometimes getting the standard "Python packaging tools" like *pip*, *setuptools* -and *virtualenv* can be a little trickier, but nothing very hard. The two crucial -packages you will need are setuptools and pip - these will let you install -anything else (like virtualenv). Fortunately there are two "bootstrap scripts" -you can run to install either. +Sometimes getting the standard "Python packaging tools" like ``pip``, ``setuptools`` +and ``virtualenv`` can be a little trickier, but nothing very hard. The crucial +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 either, then :file:`get-pip.py` will install both for you -(you won't need to run :file:`ez_setup.py`). +If you don't currently have ``pip``, then `get-pip.py` will install it for you. `get-pip.py`_ -To install the latest setuptools, you can use its bootstrap file: - -`ez_setup.py`_ - -Either should be double-clickable once you download them. If you already have pip, +It should be double-clickable once you download it. If you already have ``pip``, you can upgrade them by running:: > pip install --upgrade pip setuptools -Most often, once you pull up a command prompt you want to be able to type :command:`pip` -and :command:`python` which will run those things, but this might not automatically happen +Most often, once you pull up a command prompt you want to be able to type ``pip`` +and ``python`` which will run those things, but this might not automatically happen 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 (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 :command:`python` to bring up the interpreter. +check that you can now just type ``python`` to bring up the interpreter. Finally, to install `virtualenv`_, you can simply run:: @@ -173,4 +163,3 @@ Finally, to install `virtualenv`_, you can simply run:: Then you can be off on your way following the installation instructions above. .. _get-pip.py: https://bootstrap.pypa.io/get-pip.py -.. _ez_setup.py: https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py From 6179f41fd934523f86ceb4b04f4876f23534312e Mon Sep 17 00:00:00 2001 From: Hyunchel Kim Date: Tue, 5 Jul 2016 14:46:01 -0500 Subject: [PATCH 008/153] Enhance tests.test_cli.test_find_best_app (#1882) This commit adds a test case for `test_find_best_app` where Module object does not contain Flask application. Also cleans the function little bit to provides more meaningful comment. --- tests/test_cli.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 4a3d0831..3f2cceab 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -32,24 +32,27 @@ def test_cli_name(test_apps): def test_find_best_app(test_apps): - """Test of find_best_app.""" - class mod: + """Test if `find_best_app` behaves as expected with different combinations of input.""" + class Module: app = Flask('appname') - assert find_best_app(mod) == mod.app + assert find_best_app(Module) == Module.app - class mod: + class Module: application = Flask('appname') - assert find_best_app(mod) == mod.application + assert find_best_app(Module) == Module.application - class mod: + class Module: myapp = Flask('appname') - assert find_best_app(mod) == mod.myapp + assert find_best_app(Module) == Module.myapp - class mod: - myapp = Flask('appname') + class Module: + pass + pytest.raises(NoAppException, find_best_app, Module) + + class Module: + myapp1 = Flask('appname1') myapp2 = Flask('appname2') - - pytest.raises(NoAppException, find_best_app, mod) + pytest.raises(NoAppException, find_best_app, Module) def test_prepare_exec_for_file(test_apps): From d83d72b4801b65fc236be4431b4d8b63b253a5a0 Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Tue, 5 Jul 2016 20:30:59 -0400 Subject: [PATCH 009/153] Address #1902: Converts example/flaskr to have a setup.py (#1945) * Converts example/flaskr to have a setup.py Makes the flaskr app easier to run, ex. workflow: - pip install --editable . - export FLASK_APP=flaskr.flaskr - flask initdb - flask run Testing is also easier now: - python setup.py test * Fixed an import error in flaskr/tests - the statement `import flaskr` caused errors in python3 - `from . import flaskr` fixes the issue in 2.7.11 and 3.5.1 * Better project structure and updates the docs - Re-factors *flaskr*'s project structure a bit - Updates docs to make sense with the new structure - Adds a new step about installing Flask apps with setuptools - Switches first-person style writing to second-person (reads better IMO) - Adds segments in *testing.rst* for running tests with setuptools * Remove __init__.py from tests - py.test recommends not using __init__.py * Fix testing import errors --- docs/tutorial/css.rst | 4 +- docs/tutorial/dbcon.rst | 28 +++--- docs/tutorial/dbinit.rst | 29 +++--- docs/tutorial/folders.rst | 26 +++--- docs/tutorial/index.rst | 1 + docs/tutorial/introduction.rst | 9 +- docs/tutorial/schema.rst | 10 +-- docs/tutorial/setup.rst | 73 ++++++--------- docs/tutorial/setuptools.rst | 89 +++++++++++++++++++ docs/tutorial/templates.rst | 15 ++-- docs/tutorial/testing.rst | 78 ++++++++++++++++ docs/tutorial/views.rst | 43 +++++---- examples/flaskr/.gitignore | 1 + examples/flaskr/MANIFEST.in | 3 + examples/flaskr/README | 14 +-- examples/flaskr/flaskr/__init__.py | 0 examples/flaskr/{ => flaskr}/flaskr.py | 0 examples/flaskr/{ => flaskr}/schema.sql | 0 examples/flaskr/{ => flaskr}/static/style.css | 0 .../flaskr/{ => flaskr}/templates/layout.html | 0 .../flaskr/{ => flaskr}/templates/login.html | 0 .../{ => flaskr}/templates/show_entries.html | 0 examples/flaskr/setup.cfg | 2 + examples/flaskr/setup.py | 16 ++++ examples/flaskr/tests/context.py | 6 ++ examples/flaskr/{ => tests}/test_flaskr.py | 3 +- 26 files changed, 323 insertions(+), 127 deletions(-) create mode 100644 docs/tutorial/setuptools.rst create mode 100644 examples/flaskr/MANIFEST.in create mode 100644 examples/flaskr/flaskr/__init__.py rename examples/flaskr/{ => flaskr}/flaskr.py (100%) rename examples/flaskr/{ => flaskr}/schema.sql (100%) rename examples/flaskr/{ => flaskr}/static/style.css (100%) rename examples/flaskr/{ => flaskr}/templates/layout.html (100%) rename examples/flaskr/{ => flaskr}/templates/login.html (100%) rename examples/flaskr/{ => flaskr}/templates/show_entries.html (100%) create mode 100644 examples/flaskr/setup.cfg create mode 100644 examples/flaskr/setup.py create mode 100644 examples/flaskr/tests/context.py rename examples/flaskr/{ => tests}/test_flaskr.py (98%) diff --git a/docs/tutorial/css.rst b/docs/tutorial/css.rst index 3f0e932a..ea461a89 100644 --- a/docs/tutorial/css.rst +++ b/docs/tutorial/css.rst @@ -1,11 +1,11 @@ .. _tutorial-css: -Step 7: Adding Style +Step 9: Adding Style ==================== Now that everything else works, it's time to add some style to the application. Just create a stylesheet called :file:`style.css` in the -:file:`static` folder we created before: +:file:`static` folder: .. sourcecode:: css diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index 4b5b0915..9f4428b9 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -1,25 +1,23 @@ .. _tutorial-dbcon: -Step 3: Database Connections +Step 4: Database Connections ---------------------------- -We have created a function for establishing a database connection with -`connect_db`, but by itself, that's not particularly useful. Creating and -closing database connections all the time is very inefficient, so we want -to keep it around for longer. Because database connections encapsulate a -transaction, we also need to make sure that only one request at the time -uses the connection. How can we elegantly do that with Flask? +You now have a function for establishing a database connection with +`connect_db`, but by itself, it is not particularly useful. Creating and +closing database connections all the time is very inefficient, so you will +need to keep it around for longer. Because database connections +encapsulate a transaction, you will need to make sure that only one +request at a time uses the connection. An elegant way to do this is by +utilizing the *application context*. -This is where the application context comes into play, so let's start -there. - -Flask provides us with two contexts: the application context and the -request context. For the time being, all you have to know is that there +Flask provides two contexts: the *application context* and the +*request context*. For the time being, all you have to know is that there are special variables that use these. For instance, the :data:`~flask.request` variable is the request object associated with the current request, whereas :data:`~flask.g` is a general purpose -variable associated with the current application context. We will go into -the details of this a bit later. +variable associated with the current application context. The tutorial +will cover some more details of this later on. For the time being, all you have to know is that you can store information safely on the :data:`~flask.g` object. @@ -37,7 +35,7 @@ already established connection:: g.sqlite_db = connect_db() return g.sqlite_db -So now we know how to connect, but how do we properly disconnect? For +Now you know how to connect, but how can you properly disconnect? For that, Flask provides us with the :meth:`~flask.Flask.teardown_appcontext` decorator. It's executed every time the application context tears down:: diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index 2c26dd1a..09997906 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -1,6 +1,6 @@ .. _tutorial-dbinit: -Step 4: Creating The Database +Step 5: Creating The Database ============================= As outlined earlier, Flaskr is a database powered application, and more @@ -16,13 +16,14 @@ Such a schema can be created by piping the ``schema.sql`` file into the The downside of this is that it requires the ``sqlite3`` command to be installed, which is not necessarily the case on every system. This also -requires that we provide the path to the database, which can introduce +requires that you provide the path to the database, which can introduce errors. It's a good idea to add a function that initializes the database -for you to the application. +for you, to the application. -To do this, we can create a function and hook it into the :command:`flask` -command that initializes the database. Let me show you the code first. Just -add this function below the `connect_db` function in :file:`flaskr.py`:: +To do this, you can create a function and hook it into a :command:`flask` +command that initializes the database. For now just take a look at the +code segment below. A good place to add this function, and command, is +just below the `connect_db` function in :file:`flaskr.py`:: def init_db(): db = get_db() @@ -38,23 +39,23 @@ add this function below the `connect_db` function in :file:`flaskr.py`:: The ``app.cli.command()`` decorator registers a new command with the :command:`flask` script. When the command executes, Flask will automatically -create an application context for us bound to the right application. -Within the function, we can then access :attr:`flask.g` and other things as -we would expect. When the script ends, the application context tears down +create an application context which is bound to the right application. +Within the function, you can then access :attr:`flask.g` and other things as +you might expect. When the script ends, the application context tears down and the database connection is released. -We want to keep an actual function around that initializes the database, +You will want to keep an actual function around that initializes the database, though, so that we can easily create databases in unit tests later on. (For more information see :ref:`testing`.) The :func:`~flask.Flask.open_resource` method of the application object is a convenient helper function that will open a resource that the application provides. This function opens a file from the resource -location (your ``flaskr`` folder) and allows you to read from it. We are -using this here to execute a script on the database connection. +location (the :file:`flaskr/flaskr` folder) and allows you to read from it. +It is used in this example to execute a script on the database connection. -The connection object provided by SQLite can give us a cursor object. -On that cursor, there is a method to execute a complete script. Finally, we +The connection object provided by SQLite can give you a cursor object. +On that cursor, there is a method to execute a complete script. Finally, you only have to commit the changes. SQLite3 and other transactional databases will not commit unless you explicitly tell it to. diff --git a/docs/tutorial/folders.rst b/docs/tutorial/folders.rst index fba19d72..4e117d1f 100644 --- a/docs/tutorial/folders.rst +++ b/docs/tutorial/folders.rst @@ -3,21 +3,25 @@ Step 0: Creating The Folders ============================ -Before we get started, let's create the folders needed for this +Before getting started, you will need to create the folders needed for this application:: /flaskr - /static - /templates + /flaskr + /static + /templates -The ``flaskr`` folder is not a Python package, but just something where we -drop our files. Later on, we will put our database schema as well as main -module into this folder. It is done in the following way. The files inside -the :file:`static` folder are available to users of the application via HTTP. -This is the place where CSS and Javascript files go. Inside the -:file:`templates` folder, Flask will look for `Jinja2`_ templates. The -templates you create later on in the tutorial will go in this directory. +The application will be installed and run as Python package. This is the +recommended way to install and run Flask applications. You will see exactly +how to run ``flaskr`` later on in this tutorial. For now go ahead and create +the applications directory structure. In the next few steps you will be +creating the database schema as well as the main module. -Continue with :ref:`tutorial-schema`. +As a quick side note, the files inside of the :file:`static` folder are +available to users of the application via HTTP. This is the place where CSS and +Javascript files go. Inside the :file:`templates` folder, Flask will look for +`Jinja2`_ templates. You will see examples of this later on. + +For now you should continue with :ref:`tutorial-schema`. .. _Jinja2: http://jinja.pocoo.org/ diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index 80b9fc28..ccd4e7d2 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -24,6 +24,7 @@ the `example source`_. folders schema setup + setuptools dbcon dbinit views diff --git a/docs/tutorial/introduction.rst b/docs/tutorial/introduction.rst index e3da4b97..dd46628b 100644 --- a/docs/tutorial/introduction.rst +++ b/docs/tutorial/introduction.rst @@ -3,8 +3,9 @@ Introducing Flaskr ================== -We will call our blogging application Flaskr, but feel free to choose your own -less Web-2.0-ish name ;) Essentially, we want it to do the following things: +This tutorial will demonstrate a blogging application named Flaskr, but feel +free to choose your own less Web-2.0-ish name ;) Essentially, it will do the +following things: 1. Let the user sign in and out with credentials specified in the configuration. Only one user is supported. @@ -14,8 +15,8 @@ less Web-2.0-ish name ;) Essentially, we want it to do the following things: 3. The index page shows all entries so far in reverse chronological order (newest on top) and the user can add new ones from there if logged in. -We will be using SQLite3 directly for this application because it's good -enough for an application of this size. For larger applications, however, +SQLite3 will be used directly for this application because it's good enough +for an application of this size. For larger applications, however, it makes a lot of sense to use `SQLAlchemy`_, as it handles database connections in a more intelligent way, allowing you to target different relational databases at once and more. You might also want to consider diff --git a/docs/tutorial/schema.rst b/docs/tutorial/schema.rst index b164b94c..00f56f09 100644 --- a/docs/tutorial/schema.rst +++ b/docs/tutorial/schema.rst @@ -3,10 +3,10 @@ Step 1: Database Schema ======================= -First, we want to create the database schema. Only a single table is needed -for this application and we only want to support SQLite, so creating the -database schema is quite easy. Just put the following contents into a file -named `schema.sql` in the just created `flaskr` folder: +In this step, you will create the database schema. Only a single table is +needed for this application and it will only support SQLite. All you need to do +is put the following contents into a file named :file:`schema.sql` in the +:file:`flaskr/flaskr` folder: .. sourcecode:: sql @@ -17,7 +17,7 @@ named `schema.sql` in the just created `flaskr` folder: 'text' text not null ); -This schema consists of a single table called ``entries``. Each row in +This schema consists of a single table called ``entries``. Each row in this table has an ``id``, a ``title``, and a ``text``. The ``id`` is an automatically incrementing integer and a primary key, the other two are strings that must not be null. diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index fef71722..78b6390a 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -3,15 +3,16 @@ Step 2: Application Setup Code ============================== -Now that we have the schema in place, we can create the application module. -Let's call it ``flaskr.py``. We will place this file inside the ``flaskr`` -folder. We will begin by adding the imports we need and by adding the config -section. For small applications, it is possible to drop the configuration -directly into the module, and this is what we will be doing here. However, -a cleaner solution would be to create a separate ``.ini`` or ``.py`` file, -load that, and import the values from there. +Now that the schema is in place, you can create the application module, +:file:`flaskr.py`. This file should be placed inside of the +:file:`flaskr/flaskr` folder. The first several lines of code in the +application module are the needed import statements. After that there will be a +few lines of configuration code. For small applications like ``flaskr``, it is +possible to drop the configuration directly into the module. However, a cleaner +solution is to create a separate ``.ini`` or ``.py`` file, load that, and +import the values from there. -First, we add the imports in :file:`flaskr.py`:: +Here are the import statements (in :file:`flaskr.py`):: # all the imports import os @@ -19,12 +20,13 @@ First, we add the imports in :file:`flaskr.py`:: from flask import Flask, request, session, g, redirect, url_for, abort, \ render_template, flash -Next, we can create our actual application and initialize it with the -config from the same file in :file:`flaskr.py`:: +The next couple lines will create the actual application instance and +initialize it with the config from the same file in :file:`flaskr.py`: - # create our little application :) - app = Flask(__name__) - app.config.from_object(__name__) +.. sourcecode:: python + + app = Flask(__name__) # create the application instance :) + app.config.from_object(__name__) # load config from this file , flaskr.py # Load default config and override config from an environment variable app.config.update(dict( @@ -35,8 +37,8 @@ config from the same file in :file:`flaskr.py`:: )) app.config.from_envvar('FLASKR_SETTINGS', silent=True) -The :class:`~flask.Config` object works similarly to a dictionary so we -can update it with new values. +The :class:`~flask.Config` object works similarly to a dictionary, so it can be +updated with new values. .. admonition:: Database Path @@ -55,10 +57,10 @@ can update it with new values. Usually, it is a good idea to load a separate, environment-specific configuration file. Flask allows you to import multiple configurations and it -will use the setting defined in the last import. This enables robust +will use the setting defined in the last import. This enables robust configuration setups. :meth:`~flask.Config.from_envvar` can help achieve this. -.. code-block:: python +.. sourcecode:: python app.config.from_envvar('FLASKR_SETTINGS', silent=True) @@ -74,15 +76,15 @@ that in all cases, only variable names that are uppercase are considered. 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. -We will also add a method that allows for easy connections to the -specified database. This can be used to open a connection on request and +Lastly, you will add a method that allows for easy connections to the +specified database. This can be used to open a connection on request and also from the interactive Python shell or a script. This will come in -handy later. We create a simple database connection through SQLite and +handy later. You can create a simple database connection through SQLite and then tell it to use the :class:`sqlite3.Row` object to represent rows. -This allows us to treat the rows as if they were dictionaries instead of +This allows the rows to be treated as if they were dictionaries instead of tuples. -:: +.. sourcecode:: python def connect_db(): """Connects to the specific database.""" @@ -90,29 +92,6 @@ tuples. rv.row_factory = sqlite3.Row return rv -With that out of the way, you should be able to start up the application -without problems. Do this with the following commands:: +In the next section you will see how to run the application. - export FLASK_APP=flaskr - export FLASK_DEBUG=1 - flask run - -(In case you are on Windows you need to use `set` instead of `export`). -The :envvar:`FLASK_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! - -You will see a message telling you that server has started along with -the address at which you can access it. - -When you head over to the server in your browser, you will get a 404 error -because we don't have any views yet. We will focus on that a little later, -but first, we should get the database working. - -.. admonition:: Externally Visible Server - - Want your server to be publicly available? Check out the - :ref:`externally visible server ` section for more - information. - -Continue with :ref:`tutorial-dbcon`. +Continue with :ref:`tutorial-setuptools`. diff --git a/docs/tutorial/setuptools.rst b/docs/tutorial/setuptools.rst new file mode 100644 index 00000000..306d94d3 --- /dev/null +++ b/docs/tutorial/setuptools.rst @@ -0,0 +1,89 @@ +.. _tutorial-setuptools: + +Step 3: Installing flaskr with setuptools +========================================= + +Flask is now shipped with built-in support for `Click`_. Click provides +Flask with enhanced and extensible command line utilities. Later in this +tutorial you will see exactly how to extend the ``flask`` command line +interface (CLI). + +A useful pattern to manage a Flask application is to install your app +using `setuptools`_. This involves creating a :file:`setup.py` +in the projects root directory. You also need to add an empty +:file:`__init__.py` file to make the :file:`flaskr/flaskr` directory +a package. The code structure at this point should be:: + + /flaskr + /flaskr + __init__.py + /static + /templates + setup.py + +The content of the ``setup.py`` file for ``flaskr`` is: + +.. sourcecode:: python + + from setuptools import setup + + setup( + name='flaskr', + packages=['flaskr'], + include_package_data=True, + install_requires=[ + 'flask', + ], + ) + +When using setuptools, it is also necessary to specify any special files +that should be included in your package (in the :file:`MANIFEST.in`). +In this case, the static and templates directories need to be included, +as well as the schema. Create the :file:`MANIFEST.in` and add the +following lines:: + + graft flaskr/templates + graft flaskr/static + include flaskr/schema.sql + +At this point you should be able to install the application. As usual, it +is recommended to install your Flask application within a `virtualenv`_. +With that said, go ahead and install the application with:: + + pip install --editable . + +.. note:: The above installation command assumes that it is run within the + projects root directory, `flaskr/`. Also, the `editable` flag allows + editing source code without having to reinstall the Flask app each time + you make changes. + +With that out of the way, you should be able to start up the application. +Do this with the following commands:: + + export FLASK_APP=flaskr.flaskr + export FLASK_DEBUG=1 + flask run + +(In case you are on Windows you need to use `set` instead of `export`). +The :envvar:`FLASK_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! + +You will see a message telling you that server has started along with +the address at which you can access it. + +When you head over to the server in your browser, you will get a 404 error +because we don't have any views yet. That will be addressed a little later, +but first, you should get the database working. + +.. admonition:: Externally Visible Server + + Want your server to be publicly available? Check out the + :ref:`externally visible server ` section for more + information. + +Continue with :ref:`tutorial-dbcon`. + +.. _Click: http://click.pocoo.org +.. _setuptools: https://setuptools.readthedocs.io +.. _virtualenv: https://virtualenv.pypa.io diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index 77991893..d6558233 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -1,11 +1,12 @@ .. _tutorial-templates: -Step 6: The Templates +Step 8: The Templates ===================== -Now we should start working on the templates. If we were to request the URLs -now, we would only get an exception that Flask cannot find the templates. The -templates are using `Jinja2`_ syntax and have autoescaping enabled by +Now it is time to start working on the templates. As you may have +noticed, if you make requests with the app running, you will get +an exception that Flask cannot find the templates. The templates +are using `Jinja2`_ syntax and have autoescaping enabled by default. This means that unless you mark a value in the code with :class:`~flask.Markup` or with the ``|safe`` filter in the template, Jinja2 will ensure that special characters such as ``<`` or ``>`` are @@ -57,9 +58,9 @@ show_entries.html This template extends the :file:`layout.html` template from above to display the messages. Note that the ``for`` loop iterates over the messages we passed -in with the :func:`~flask.render_template` function. We also tell the -form to submit to your `add_entry` function and use ``POST`` as HTTP -method: +in with the :func:`~flask.render_template` function. Notice that the form is +configured to to submit to the `add_entry` view function and use ``POST`` as +HTTP method: .. sourcecode:: html+jinja diff --git a/docs/tutorial/testing.rst b/docs/tutorial/testing.rst index d70b4abe..c5ecf7dd 100644 --- a/docs/tutorial/testing.rst +++ b/docs/tutorial/testing.rst @@ -8,3 +8,81 @@ expected, it's probably not a bad idea to add automated tests to simplify modifications in the future. The application above is used as a basic example of how to perform unit testing in the :ref:`testing` section of the documentation. Go there to see how easy it is to test Flask applications. + +Adding Tests to flaskr +====================== + +Assuming you have seen the testing section above and have either written +your own tests for ``flaskr`` or have followed along with the examples +provided, you might be wondering about ways to organize the project. + +One possible and recommended project structure is:: + + flaskr/ + flaskr/ + __init__.py + static/ + templates/ + tests/ + context.py + test_flaskr.py + setup.py + MANIFEST.in + +For now go ahead a create the :file:`tests/` directory as well as the +:file:`context.py` and :file:`test_flaskr.py` files, if you haven't +already. The context file is used as an import helper. The contents +of that file are:: + + import sys, os + + basedir = os.path.dirname(os.path.abspath(__file__)) + sys.path.insert(0, basedir + '/../') + + from flaskr import flaskr + +Testing + Setuptools +==================== + +One way to handle testing is to integrate it with ``setuptools``. All it +requires is adding a couple of lines to the :file:`setup.py` file and +creating a new file :file:`setup.cfg`. Go ahead and update the +:file:`setup.py` to contain:: + + from setuptools import setup + + setup( + name='flaskr', + packages=['flaskr'], + include_package_data=True, + install_requires=[ + 'flask', + ], + ) + setup_requires=[ + 'pytest-runner', + ], + tests_require=[ + 'pytest', + ], + ) +Now create :file:`setup.cfg` in the project root (alongside +:file:`setup.py`):: + + [aliases] + test=pytest + +Now you can run:: + + python setup.py test + +This calls on the alias created in :file:`setup.cfg` which in turn runs +``pytest`` via ``pytest-runner``, as the :file:`setup.py` script has +been called. (Recall the `setup_requires` argument in :file:`setup.py`) +Following the standard rules of test-discovery your tests will be +found, run, and hopefully pass. + +This is one possible way to run and manage testing. Here ``pytest`` is +used, but there are other options such as ``nose``. Integrating testing +with ``setuptools`` is convenient because it is not necessary to actually +download ``pytest`` or any other testing framework one might use. \ No newline at end of file diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index 618c97c6..d9838073 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -1,10 +1,10 @@ .. _tutorial-views: -Step 5: The View Functions +Step 7: The View Functions ========================== -Now that the database connections are working, we can start writing the -view functions. We will need four of them: +Now that the database connections are working, you can start writing the +view functions. You will need four of them: Show Entries ------------ @@ -30,7 +30,7 @@ Add New Entry This view lets the user add new entries if they are logged in. This only responds to ``POST`` requests; the actual form is shown on the -`show_entries` page. If everything worked out well, we will +`show_entries` page. If everything worked out well, it will :func:`~flask.flash` an information message to the next request and redirect back to the `show_entries` page:: @@ -45,8 +45,8 @@ redirect back to the `show_entries` page:: flash('New entry was successfully posted') return redirect(url_for('show_entries')) -Note that we check that the user is logged in here (the `logged_in` key is -present in the session and ``True``). +Note that this view checks that the user is logged in (that is, if the +`logged_in` key is present in the session and ``True``). .. admonition:: Security Note @@ -81,11 +81,11 @@ notified about that, and the user is asked again:: return render_template('login.html', error=error) The `logout` function, on the other hand, removes that key from the session -again. We use a neat trick here: if you use the :meth:`~dict.pop` method +again. There is a neat trick here: if you use the :meth:`~dict.pop` method of the dict and pass a second parameter to it (the default), the method will delete the key from the dictionary if present or do nothing when that -key is not in there. This is helpful because now we don't have to check -if the user was logged in. +key is not in there. This is helpful because now it is not necessary to +check if the user was logged in. :: @@ -94,11 +94,24 @@ if the user was logged in. session.pop('logged_in', None) flash('You were logged out') return redirect(url_for('show_entries')) - -Note that it is not a good idea to store passwords in plain text. You want to -protect login credentials if someone happens to have access to your database. -One way to do this is to use Security Helpers from Werkzeug to hash the -password. However, the emphasis of this tutorial is to demonstrate the basics -of Flask and plain text passwords are used for simplicity. + +.. admonition:: Security Note + + Passwords should never be stored in plain text in a production + system. This tutorial uses plain text passwords for simplicity. If you + plan to release a project based off this tutorial out into the world, + passwords should be both `hashed and salted`_ before being stored in a + database or file. + + Fortunately, there are Flask extensions for the purpose of + hashing passwords and verifying passwords against hashes, so adding + this functionality is fairly straight forward. There are also + many general python libraries that can be used for hashing. + + You can find a list of recommended Flask extensions + `here `_ + Continue with :ref:`tutorial-templates`. + +.. _hashed and salted: https://blog.codinghorror.com/youre-probably-storing-passwords-incorrectly/ diff --git a/examples/flaskr/.gitignore b/examples/flaskr/.gitignore index fb46a3af..8d567f84 100644 --- a/examples/flaskr/.gitignore +++ b/examples/flaskr/.gitignore @@ -1 +1,2 @@ flaskr.db +.eggs/ diff --git a/examples/flaskr/MANIFEST.in b/examples/flaskr/MANIFEST.in new file mode 100644 index 00000000..efbd93df --- /dev/null +++ b/examples/flaskr/MANIFEST.in @@ -0,0 +1,3 @@ +graft flaskr/templates +graft flaskr/static +include flaskr/schema.sql diff --git a/examples/flaskr/README b/examples/flaskr/README index bdf91983..3cb021e7 100644 --- a/examples/flaskr/README +++ b/examples/flaskr/README @@ -13,15 +13,19 @@ export an FLASKR_SETTINGS environment variable pointing to a configuration file. - 2. Instruct flask to use the right application + 2. install the app from the root of the project directory - export FLASK_APP=flaskr + pip install --editable . - 3. initialize the database with this command: + 3. Instruct flask to use the right application + + export FLASK_APP=flaskr.flaskr + + 4. initialize the database with this command: flask initdb - 4. now you can run flaskr: + 5. now you can run flaskr: flask run @@ -30,5 +34,5 @@ ~ Is it tested? - You betcha. Run the `test_flaskr.py` file to see + You betcha. Run `python setup.py test` to see the tests pass. diff --git a/examples/flaskr/flaskr/__init__.py b/examples/flaskr/flaskr/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr/flaskr.py similarity index 100% rename from examples/flaskr/flaskr.py rename to examples/flaskr/flaskr/flaskr.py diff --git a/examples/flaskr/schema.sql b/examples/flaskr/flaskr/schema.sql similarity index 100% rename from examples/flaskr/schema.sql rename to examples/flaskr/flaskr/schema.sql diff --git a/examples/flaskr/static/style.css b/examples/flaskr/flaskr/static/style.css similarity index 100% rename from examples/flaskr/static/style.css rename to examples/flaskr/flaskr/static/style.css diff --git a/examples/flaskr/templates/layout.html b/examples/flaskr/flaskr/templates/layout.html similarity index 100% rename from examples/flaskr/templates/layout.html rename to examples/flaskr/flaskr/templates/layout.html diff --git a/examples/flaskr/templates/login.html b/examples/flaskr/flaskr/templates/login.html similarity index 100% rename from examples/flaskr/templates/login.html rename to examples/flaskr/flaskr/templates/login.html diff --git a/examples/flaskr/templates/show_entries.html b/examples/flaskr/flaskr/templates/show_entries.html similarity index 100% rename from examples/flaskr/templates/show_entries.html rename to examples/flaskr/flaskr/templates/show_entries.html diff --git a/examples/flaskr/setup.cfg b/examples/flaskr/setup.cfg new file mode 100644 index 00000000..b7e47898 --- /dev/null +++ b/examples/flaskr/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/examples/flaskr/setup.py b/examples/flaskr/setup.py new file mode 100644 index 00000000..910f23ac --- /dev/null +++ b/examples/flaskr/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup + +setup( + name='flaskr', + packages=['flaskr'], + include_package_data=True, + install_requires=[ + 'flask', + ], + setup_requires=[ + 'pytest-runner', + ], + tests_require=[ + 'pytest', + ], +) diff --git a/examples/flaskr/tests/context.py b/examples/flaskr/tests/context.py new file mode 100644 index 00000000..3c773332 --- /dev/null +++ b/examples/flaskr/tests/context.py @@ -0,0 +1,6 @@ +import sys, os + +basedir = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, basedir + '/../') + +from flaskr import flaskr \ No newline at end of file diff --git a/examples/flaskr/test_flaskr.py b/examples/flaskr/tests/test_flaskr.py similarity index 98% rename from examples/flaskr/test_flaskr.py rename to examples/flaskr/tests/test_flaskr.py index 084f13f4..4715c417 100644 --- a/examples/flaskr/test_flaskr.py +++ b/examples/flaskr/tests/test_flaskr.py @@ -10,11 +10,10 @@ """ import pytest - import os -import flaskr import tempfile +from context import flaskr @pytest.fixture def client(request): From 21cb0923cb050878d5244c1ec23f2b280f3ab8e8 Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 6 Jul 2016 08:02:13 -0700 Subject: [PATCH 010/153] persona is discontinued, remove example closes #1947 --- examples/persona/README.md | 3 -- examples/persona/persona.py | 55 ------------------------- examples/persona/static/persona.js | 52 ----------------------- examples/persona/static/spinner.png | Bin 31201 -> 0 bytes examples/persona/static/style.css | 39 ------------------ examples/persona/templates/index.html | 23 ----------- examples/persona/templates/layout.html | 27 ------------ 7 files changed, 199 deletions(-) delete mode 100644 examples/persona/README.md delete mode 100644 examples/persona/persona.py delete mode 100644 examples/persona/static/persona.js delete mode 100644 examples/persona/static/spinner.png delete mode 100644 examples/persona/static/style.css delete mode 100644 examples/persona/templates/index.html delete mode 100644 examples/persona/templates/layout.html diff --git a/examples/persona/README.md b/examples/persona/README.md deleted file mode 100644 index d9482d36..00000000 --- a/examples/persona/README.md +++ /dev/null @@ -1,3 +0,0 @@ -A simple example for integrating [Persona](https://login.persona.org/) into a -Flask application. In addition to Flask, it requires the -[requests](www.python-requests.org/) library. diff --git a/examples/persona/persona.py b/examples/persona/persona.py deleted file mode 100644 index 7374c3af..00000000 --- a/examples/persona/persona.py +++ /dev/null @@ -1,55 +0,0 @@ -from flask import Flask, render_template, session, request, abort, g - -import requests - - -app = Flask(__name__) -app.config.update( - DEBUG=True, - SECRET_KEY='my development key', - PERSONA_JS='https://login.persona.org/include.js', - PERSONA_VERIFIER='https://verifier.login.persona.org/verify', -) -app.config.from_envvar('PERSONA_SETTINGS', silent=True) - - -@app.before_request -def get_current_user(): - g.user = None - email = session.get('email') - if email is not None: - g.user = email - - -@app.route('/') -def index(): - """Just a generic index page to show.""" - return render_template('index.html') - - -@app.route('/_auth/login', methods=['GET', 'POST']) -def login_handler(): - """This is used by the persona.js file to kick off the - verification securely from the server side. If all is okay - the email address is remembered on the server. - """ - resp = requests.post(app.config['PERSONA_VERIFIER'], data={ - 'assertion': request.form['assertion'], - 'audience': request.host_url, - }, verify=True) - if resp.ok: - verification_data = resp.json() - if verification_data['status'] == 'okay': - session['email'] = verification_data['email'] - return 'OK' - - abort(400) - - -@app.route('/_auth/logout', methods=['POST']) -def logout_handler(): - """This is what persona.js will call to sign the user - out again. - """ - session.clear() - return 'OK' diff --git a/examples/persona/static/persona.js b/examples/persona/static/persona.js deleted file mode 100644 index c1fcdb79..00000000 --- a/examples/persona/static/persona.js +++ /dev/null @@ -1,52 +0,0 @@ -$(function() { - /* convert the links into clickable buttons that go to the - persona service */ - $('a.signin').on('click', function() { - navigator.id.request({ - siteName: 'Flask Persona Example' - }); - return false; - }); - - $('a.signout').on('click', function() { - navigator.id.logout(); - return false; - }); - - /* watch persona state changes */ - navigator.id.watch({ - loggedInUser: $CURRENT_USER, - onlogin: function(assertion) { - /* because the login needs to verify the provided assertion - with the persona service which requires an HTTP request, - this could take a bit. To not confuse the user we show - a progress box */ - var box = $('
') - .hide() - .text('Please wait ...') - .appendTo('body') - .fadeIn('fast'); - $.ajax({ - type: 'POST', - url: $URL_ROOT + '_auth/login', - data: {assertion: assertion}, - success: function(res, status, xhr) { window.location.reload(); }, - error: function(xhr, status, err) { - box.remove(); - navigator.id.logout(); - alert('Login failure: ' + err); - } - }); - }, - onlogout: function() { - $.ajax({ - type: 'POST', - url: $URL_ROOT + '_auth/logout', - success: function(res, status, xhr) { window.location.reload(); }, - error: function(xhr, status, err) { - alert('Logout failure: ' + err); - } - }); - } - }); -}); diff --git a/examples/persona/static/spinner.png b/examples/persona/static/spinner.png deleted file mode 100644 index c9cd835849676056e750dcc08161f41b7aa7c13c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31201 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 a$v<*uh;o0K<$f#pRPSrvt5LFg`Tqb(kboKh diff --git a/examples/persona/static/style.css b/examples/persona/static/style.css deleted file mode 100644 index 6f0c5f15..00000000 --- a/examples/persona/static/style.css +++ /dev/null @@ -1,39 +0,0 @@ -html { - background: #eee; -} - -body { - font-family: 'Verdana', sans-serif; - font-size: 15px; - margin: 30px auto; - width: 720px; - background: white; - padding: 30px; -} - -h1 { - margin: 0; -} - -h1, h2, a { - color: #d00; -} - -div.authbar { - background: #eee; - padding: 0 15px; - margin: 10px -15px; - line-height: 25px; - height: 25px; - vertical-align: middle; -} - -div.signinprogress { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(255, 255, 255, 0.8) url(spinner.png) center center no-repeat; - font-size: 0; -} diff --git a/examples/persona/templates/index.html b/examples/persona/templates/index.html deleted file mode 100644 index bcccdfc1..00000000 --- a/examples/persona/templates/index.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "layout.html" %} -{% block title %}Welcome{% endblock %} -{% block body %} -

Welcome

-

- This is a small example application that shows how to integrate - Mozilla's persona signin service into a Flask application. -

- The advantage of persona over your own login system is that the - password is managed outside of your application and you get - a verified mail address as primary identifier for your user. -

- In this example nothing is actually stored on the server, it - just takes over the email address from the persona verifier - and stores it in a session. - {% if g.user %} -

- You are now logged in as {{ g.user }} - {% else %} -

- To sign in click the sign in button above. - {% endif %} -{% endblock %} diff --git a/examples/persona/templates/layout.html b/examples/persona/templates/layout.html deleted file mode 100644 index 3fe46620..00000000 --- a/examples/persona/templates/layout.html +++ /dev/null @@ -1,27 +0,0 @@ - -{% block title %}{% endblock %} | Flask Persona Example - - - - - - -

-

Mozilla Persona Example

-
- {% if g.user %} - Signed in as {{ g.user }} - (Sign out) - {% else %} - Not signed in. - {% endif %} -
-
-{% block body %}{% endblock %} From 4151970cee4efc6a9fb7257368a4664274429bec Mon Sep 17 00:00:00 2001 From: Shakib Hossain Date: Sat, 16 Jul 2016 21:59:44 +0600 Subject: [PATCH 011/153] Update allowed_file function in fileuploads.rst Update allowed_file function to accept lowercase and uppercase file extensions --- docs/patterns/fileuploads.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 95c0032f..8ab8c033 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -47,7 +47,7 @@ the file and redirects the user to the URL for the uploaded file:: def allowed_file(filename): return '.' in filename and \ - filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS + filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/', methods=['GET', 'POST']) def upload_file(): From 4800f050418ffac44a8f69468760f2fd1555ff23 Mon Sep 17 00:00:00 2001 From: ahmedakef Date: Thu, 28 Jul 2016 23:46:42 +0300 Subject: [PATCH 012/153] close
  • tag in lines (16,18) (#1951) i noticed that
  • tag haven't closed in lines 15,18 which is bad practice as if i put "some thing :

    some text

    " in the text-area all the other articles become

    so big and color blue --- examples/flaskr/flaskr/templates/show_entries.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/flaskr/flaskr/templates/show_entries.html b/examples/flaskr/flaskr/templates/show_entries.html index 9cbd3229..2f68b9d3 100644 --- a/examples/flaskr/flaskr/templates/show_entries.html +++ b/examples/flaskr/flaskr/templates/show_entries.html @@ -13,9 +13,9 @@ {% endif %}
      {% for entry in entries %} -
    • {{ entry.title }}

      {{ entry.text|safe }} +
    • {{ entry.title }}

      {{ entry.text|safe }}
    • {% else %} -
    • Unbelievable. No entries here so far +
    • Unbelievable. No entries here so far
    • {% endfor %}
    {% endblock %} From e5ea782c551458214b098df2c58c6d55281c2b70 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Fri, 29 Jul 2016 05:27:30 -0700 Subject: [PATCH 013/153] Remove unused Redbaron dependency (#1967) --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 91a80c19..28942d3e 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ commands = deps= pytest greenlet - redbaron lowest: Werkzeug==0.7 lowest: Jinja2==2.4 From 387add139b5351b161a9ee18b788b963495e9278 Mon Sep 17 00:00:00 2001 From: Auke Willem Oosterhoff Date: Wed, 3 Aug 2016 18:22:14 +0200 Subject: [PATCH 014/153] Use path of socket consistently accross document. (#1976) * #1975 Use location of socket consistently accross document. --- docs/deploying/uwsgi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index 183bdb69..fc991e72 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -29,7 +29,7 @@ Given a flask application in myapp.py, use the following command: .. sourcecode:: text - $ uwsgi -s /tmp/uwsgi.sock --manage-script-name --mount /yourapplication=myapp:app + $ uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app The ``--manage-script-name`` will move the handling of ``SCRIPT_NAME`` to uwsgi, since its smarter about that. It is used together with the ``--mount`` directive From 9608198047e02a9dcdb7900a7a5ada800e4f5655 Mon Sep 17 00:00:00 2001 From: Nate Prewitt Date: Fri, 12 Aug 2016 07:12:00 -0600 Subject: [PATCH 015/153] adding in try around __import__ to catch invalid files/paths (#1950) --- flask/cli.py | 8 +++++++- tests/test_cli.py | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index 58b6fb3a..9b8fa2cd 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -86,7 +86,13 @@ def locate_app(app_id): module = app_id app_obj = None - __import__(module) + try: + __import__(module) + except ImportError: + raise NoAppException('The file/path provided (%s) does not appear to ' + 'exist. Please verify the path is correct. If ' + 'app is not on PYTHONPATH, ensure the extension ' + 'is .py' % module) mod = sys.modules[module] if app_obj is None: app = find_best_app(mod) diff --git a/tests/test_cli.py b/tests/test_cli.py index 3f2cceab..d2bb61b9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -80,6 +80,8 @@ def test_locate_app(test_apps): assert locate_app("cliapp.app").name == "testapp" assert locate_app("cliapp.app:testapp").name == "testapp" assert locate_app("cliapp.multiapp:app1").name == "app1" + pytest.raises(NoAppException, locate_app, "notanpp.py") + pytest.raises(NoAppException, locate_app, "cliapp/app") pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") From 92d9c0122cc75c3760c6a6ae47f193a24e6ec711 Mon Sep 17 00:00:00 2001 From: teichopsia- Date: Fri, 19 Aug 2016 21:01:13 -0500 Subject: [PATCH 016/153] Update testing.rst (#1987) Python 3.4.2 TypeError: Type str doesn't support the buffer API --- docs/testing.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index fdf57937..0737936e 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -152,13 +152,13 @@ invalid credentials. Add this new test to the class:: def test_login_logout(self): rv = self.login('admin', 'default') - assert 'You were logged in' in rv.data + assert b'You were logged in' in rv.data rv = self.logout() - assert 'You were logged out' in rv.data + assert b'You were logged out' in rv.data rv = self.login('adminx', 'default') - assert 'Invalid username' in rv.data + assert b'Invalid username' in rv.data rv = self.login('admin', 'defaultx') - assert 'Invalid password' in rv.data + assert b'Invalid password' in rv.data Test Adding Messages -------------------- @@ -172,9 +172,9 @@ like this:: title='', text='HTML allowed here' ), follow_redirects=True) - assert 'No entries here so far' not in rv.data - assert '<Hello>' in rv.data - assert 'HTML allowed here' in rv.data + assert b'No entries here so far' not in rv.data + assert b'<Hello>' in rv.data + assert b'HTML allowed here' in rv.data Here we check that HTML is allowed in the text but not in the title, which is the intended behavior. From ee84b877cb17c91b8406974bfd3445de95aae9c1 Mon Sep 17 00:00:00 2001 From: SaturnR Date: Sat, 20 Aug 2016 19:43:10 +0400 Subject: [PATCH 017/153] Update for python3 (#1973) just updated print 'Initialized the database.' with print('Initialized the database.') to be python3 compliant --- docs/tutorial/dbinit.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/dbinit.rst b/docs/tutorial/dbinit.rst index 09997906..fbbcde00 100644 --- a/docs/tutorial/dbinit.rst +++ b/docs/tutorial/dbinit.rst @@ -35,7 +35,7 @@ just below the `connect_db` function in :file:`flaskr.py`:: def initdb_command(): """Initializes the database.""" init_db() - print 'Initialized the database.' + print('Initialized the database.') The ``app.cli.command()`` decorator registers a new command with the :command:`flask` script. When the command executes, Flask will automatically From 1832130c2e002d58a23896a13c2eabe5e90bd4fc Mon Sep 17 00:00:00 2001 From: Anton Sarukhanov Date: Sat, 20 Aug 2016 08:43:58 -0700 Subject: [PATCH 018/153] Add test for get_version (CLI) (#1884) --- tests/test_cli.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index d2bb61b9..18026a75 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -22,7 +22,7 @@ from flask import Flask, current_app from flask.cli import AppGroup, FlaskGroup, NoAppException, ScriptInfo, \ find_best_app, locate_app, with_appcontext, prepare_exec_for_file, \ - find_default_import_path + find_default_import_path, get_version def test_cli_name(test_apps): @@ -98,6 +98,21 @@ def test_find_default_import_path(test_apps, monkeypatch, tmpdir): assert find_default_import_path() == expect_rv +def test_get_version(test_apps, capsys): + """Test of get_version.""" + from flask import __version__ as flask_ver + from sys import version as py_ver + class MockCtx(object): + resilient_parsing = False + color = None + def exit(self): return + ctx = MockCtx() + get_version(ctx, None, "test") + out, err = capsys.readouterr() + assert flask_ver in out + assert py_ver in out + + def test_scriptinfo(test_apps): """Test of ScriptInfo.""" obj = ScriptInfo(app_import_path="cliapp.app:testapp") From 03104555cbe346a6cd92c95bcd6e398f4dd46759 Mon Sep 17 00:00:00 2001 From: Nathan Land Date: Fri, 3 Jun 2016 14:45:22 -0700 Subject: [PATCH 019/153] Add tests for flask.json.dump() and test that jsonify correctly converts uuids. --- tests/test_helpers.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 3ff5900b..f3b80325 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -12,6 +12,7 @@ import pytest import os +import uuid import datetime import flask from logging import StreamHandler @@ -99,6 +100,22 @@ class TestJSON(object): rv = flask.json.dumps(u'\N{SNOWMAN}') assert rv == u'"\u2603"' + def test_json_dump_to_file(self): + app = flask.Flask(__name__) + + test_data = {'lol': 'wut'} + + with app.app_context(): + t_fh = open('test_json_dump_file', 'w') + flask.json.dump(test_data, t_fh) + t_fh.close() + + t_fh = open('test_json_dump_file', 'r') + rv = flask.json.load(t_fh) + assert rv == test_data + t_fh.close() + os.remove('test_json_dump_file') + def test_jsonify_basic_types(self): """Test jsonify with basic types.""" # Should be able to use pytest parametrize on this, but I couldn't @@ -172,6 +189,18 @@ class TestJSON(object): assert rv.mimetype == 'application/json' assert flask.json.loads(rv.data)['x'] == http_date(d.timetuple()) + def test_jsonify_uuid_types(self): + """Test jsonify with uuid.UUID types""" + test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF'*4) + + app = flask.Flask(__name__) + c = app.test_client() + url = '/uuid_test' + app.add_url_rule(url, 'uuid_test', lambda val=test_uuid: flask.jsonify(x=val)) + rv = c.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data)['x'] == str(test_uuid) + def test_json_attr(self): app = flask.Flask(__name__) @app.route('/add', methods=['POST']) From 1584022d0b770053930fe27a1169f9ef7e43efc0 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 21 Aug 2016 08:47:12 -0700 Subject: [PATCH 020/153] clean up new json tests --- tests/test_helpers.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index f3b80325..610e18ea 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -19,7 +19,7 @@ from logging import StreamHandler from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date -from flask._compat import StringIO, text_type, PY2 +from flask._compat import StringIO, text_type def has_encoding(name): @@ -102,19 +102,14 @@ class TestJSON(object): def test_json_dump_to_file(self): app = flask.Flask(__name__) - - test_data = {'lol': 'wut'} + test_data = {'name': 'Flask'} + out = StringIO() with app.app_context(): - t_fh = open('test_json_dump_file', 'w') - flask.json.dump(test_data, t_fh) - t_fh.close() - - t_fh = open('test_json_dump_file', 'r') - rv = flask.json.load(t_fh) + flask.json.dump(test_data, out) + out.seek(0) + rv = flask.json.load(out) assert rv == test_data - t_fh.close() - os.remove('test_json_dump_file') def test_jsonify_basic_types(self): """Test jsonify with basic types.""" @@ -191,15 +186,20 @@ class TestJSON(object): def test_jsonify_uuid_types(self): """Test jsonify with uuid.UUID types""" + test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF'*4) app = flask.Flask(__name__) - c = app.test_client() url = '/uuid_test' - app.add_url_rule(url, 'uuid_test', lambda val=test_uuid: flask.jsonify(x=val)) + app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid)) + + c = app.test_client() rv = c.get(url) - assert rv.mimetype == 'application/json' - assert flask.json.loads(rv.data)['x'] == str(test_uuid) + + rv_x = flask.json.loads(rv.data)['x'] + assert rv_x == str(test_uuid) + rv_uuid = uuid.UUID(rv_x) + assert rv_uuid == test_uuid def test_json_attr(self): app = flask.Flask(__name__) From 5b867d25aa62e45646c9f4368424ad6c7d122a7a Mon Sep 17 00:00:00 2001 From: sanderl-mediamonks Date: Mon, 22 Aug 2016 11:49:52 +0200 Subject: [PATCH 021/153] Use the correct Celery result backend setting --- docs/patterns/celery.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/celery.rst b/docs/patterns/celery.rst index 52155f62..673d953b 100644 --- a/docs/patterns/celery.rst +++ b/docs/patterns/celery.rst @@ -36,7 +36,7 @@ This is all that is necessary to properly integrate Celery with Flask:: from celery import Celery def make_celery(app): - celery = Celery(app.import_name, backend=app.config['CELERY_BACKEND'], + celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'], broker=app.config['CELERY_BROKER_URL']) celery.conf.update(app.config) TaskBase = celery.Task From d42e548f07a47408574ed8816afae915f8d97e45 Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Mon, 22 Aug 2016 14:52:54 -0400 Subject: [PATCH 022/153] Update minitwit & improve testing for examples (#1954) * Update minitwit & improve testing for examples * Related to #1945 * Re-works minitwit to be installed and run as: pip install --editable . export FLASK_APP=minitwit.minitwit export FLASK_DEBUG=1 flask initdb flask run * added flaskr and minitwit to norecursedirs * tests not properly run when using pytest standards * see: http://stackoverflow.com/questions/38313171/configuring-pytest-with-installable-examples-in-a-project * Both flaskr and minitwit now follow pytest standards. * Tests can for them as `py.test` or `python setup.py test` * Update minitwit readme * updates the instructions for running * Fixes for updating the minitwit example - This reverts the changes to the *docs/* (I will file separate PR). - Running the app is now: `export FLASK_APP=minitwit` & `flask run` (After installing the app) * Remove unnecessary comma from flaskr/setup.py --- examples/flaskr/tests/context.py | 6 ------ examples/flaskr/tests/test_flaskr.py | 4 ++-- examples/minitwit/.gitignore | 2 ++ examples/minitwit/MANIFEST.in | 3 +++ examples/minitwit/README | 2 +- examples/minitwit/minitwit/__init__.py | 1 + examples/minitwit/{ => minitwit}/minitwit.py | 0 examples/minitwit/{ => minitwit}/schema.sql | 0 .../minitwit/{ => minitwit}/static/style.css | 0 .../{ => minitwit}/templates/layout.html | 0 .../minitwit/{ => minitwit}/templates/login.html | 0 .../{ => minitwit}/templates/register.html | 0 .../{ => minitwit}/templates/timeline.html | 0 examples/minitwit/setup.cfg | 2 ++ examples/minitwit/setup.py | 16 ++++++++++++++++ examples/minitwit/{ => tests}/test_minitwit.py | 2 +- setup.cfg | 2 +- 17 files changed, 29 insertions(+), 11 deletions(-) delete mode 100644 examples/flaskr/tests/context.py create mode 100644 examples/minitwit/.gitignore create mode 100644 examples/minitwit/MANIFEST.in create mode 100644 examples/minitwit/minitwit/__init__.py rename examples/minitwit/{ => minitwit}/minitwit.py (100%) rename examples/minitwit/{ => minitwit}/schema.sql (100%) rename examples/minitwit/{ => minitwit}/static/style.css (100%) rename examples/minitwit/{ => minitwit}/templates/layout.html (100%) rename examples/minitwit/{ => minitwit}/templates/login.html (100%) rename examples/minitwit/{ => minitwit}/templates/register.html (100%) rename examples/minitwit/{ => minitwit}/templates/timeline.html (100%) create mode 100644 examples/minitwit/setup.cfg create mode 100644 examples/minitwit/setup.py rename examples/minitwit/{ => tests}/test_minitwit.py (99%) diff --git a/examples/flaskr/tests/context.py b/examples/flaskr/tests/context.py deleted file mode 100644 index 3c773332..00000000 --- a/examples/flaskr/tests/context.py +++ /dev/null @@ -1,6 +0,0 @@ -import sys, os - -basedir = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, basedir + '/../') - -from flaskr import flaskr \ No newline at end of file diff --git a/examples/flaskr/tests/test_flaskr.py b/examples/flaskr/tests/test_flaskr.py index 4715c417..663e92e0 100644 --- a/examples/flaskr/tests/test_flaskr.py +++ b/examples/flaskr/tests/test_flaskr.py @@ -9,11 +9,11 @@ :license: BSD, see LICENSE for more details. """ -import pytest import os import tempfile +import pytest +from flaskr import flaskr -from context import flaskr @pytest.fixture def client(request): diff --git a/examples/minitwit/.gitignore b/examples/minitwit/.gitignore new file mode 100644 index 00000000..c3accd82 --- /dev/null +++ b/examples/minitwit/.gitignore @@ -0,0 +1,2 @@ +minitwit.db +.eggs/ diff --git a/examples/minitwit/MANIFEST.in b/examples/minitwit/MANIFEST.in new file mode 100644 index 00000000..973d6586 --- /dev/null +++ b/examples/minitwit/MANIFEST.in @@ -0,0 +1,3 @@ +graft minitwit/templates +graft minitwit/static +include minitwit/schema.sql \ No newline at end of file diff --git a/examples/minitwit/README b/examples/minitwit/README index a2a7f395..4561d836 100644 --- a/examples/minitwit/README +++ b/examples/minitwit/README @@ -31,5 +31,5 @@ ~ Is it tested? - You betcha. Run the `test_minitwit.py` file to + You betcha. Run the `python setup.py test` file to see the tests pass. diff --git a/examples/minitwit/minitwit/__init__.py b/examples/minitwit/minitwit/__init__.py new file mode 100644 index 00000000..0b8bd697 --- /dev/null +++ b/examples/minitwit/minitwit/__init__.py @@ -0,0 +1 @@ +from minitwit import app \ No newline at end of file diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit/minitwit.py similarity index 100% rename from examples/minitwit/minitwit.py rename to examples/minitwit/minitwit/minitwit.py diff --git a/examples/minitwit/schema.sql b/examples/minitwit/minitwit/schema.sql similarity index 100% rename from examples/minitwit/schema.sql rename to examples/minitwit/minitwit/schema.sql diff --git a/examples/minitwit/static/style.css b/examples/minitwit/minitwit/static/style.css similarity index 100% rename from examples/minitwit/static/style.css rename to examples/minitwit/minitwit/static/style.css diff --git a/examples/minitwit/templates/layout.html b/examples/minitwit/minitwit/templates/layout.html similarity index 100% rename from examples/minitwit/templates/layout.html rename to examples/minitwit/minitwit/templates/layout.html diff --git a/examples/minitwit/templates/login.html b/examples/minitwit/minitwit/templates/login.html similarity index 100% rename from examples/minitwit/templates/login.html rename to examples/minitwit/minitwit/templates/login.html diff --git a/examples/minitwit/templates/register.html b/examples/minitwit/minitwit/templates/register.html similarity index 100% rename from examples/minitwit/templates/register.html rename to examples/minitwit/minitwit/templates/register.html diff --git a/examples/minitwit/templates/timeline.html b/examples/minitwit/minitwit/templates/timeline.html similarity index 100% rename from examples/minitwit/templates/timeline.html rename to examples/minitwit/minitwit/templates/timeline.html diff --git a/examples/minitwit/setup.cfg b/examples/minitwit/setup.cfg new file mode 100644 index 00000000..b7e47898 --- /dev/null +++ b/examples/minitwit/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest diff --git a/examples/minitwit/setup.py b/examples/minitwit/setup.py new file mode 100644 index 00000000..1e580216 --- /dev/null +++ b/examples/minitwit/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup + +setup( + name='minitwit', + packages=['minitwit'], + include_package_data=True, + install_requires=[ + 'flask', + ], + setup_requires=[ + 'pytest-runner', + ], + tests_require=[ + 'pytest', + ], +) \ No newline at end of file diff --git a/examples/minitwit/test_minitwit.py b/examples/minitwit/tests/test_minitwit.py similarity index 99% rename from examples/minitwit/test_minitwit.py rename to examples/minitwit/tests/test_minitwit.py index bd58d4dc..50ca26d9 100644 --- a/examples/minitwit/test_minitwit.py +++ b/examples/minitwit/tests/test_minitwit.py @@ -9,9 +9,9 @@ :license: BSD, see LICENSE for more details. """ import os -import minitwit import tempfile import pytest +from minitwit import minitwit @pytest.fixture diff --git a/setup.cfg b/setup.cfg index 69feced4..47c225ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,4 +5,4 @@ release = egg_info -RDb '' universal = 1 [pytest] -norecursedirs = .* *.egg *.egg-info env* artwork docs +norecursedirs = .* *.egg *.egg-info env* artwork docs examples From 9657b517a8656c7dc1b5287b38d502430c84faa3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 25 Aug 2016 15:41:46 +0200 Subject: [PATCH 023/153] Disable logger propagation by default --- CHANGES | 1 + flask/logging.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 9e13bd71..1410a9ae 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,7 @@ Version 0.12 See pull request ``#1849``. - Make ``flask.safe_join`` able to join multiple paths like ``os.path.join`` (pull request ``#1730``). +- Disable logger propagation by default for the app logger. Version 0.11.2 -------------- diff --git a/flask/logging.py b/flask/logging.py index 5a1f149c..3f888a75 100644 --- a/flask/logging.py +++ b/flask/logging.py @@ -87,4 +87,8 @@ def create_logger(app): logger.__class__ = DebugLogger logger.addHandler(debug_handler) logger.addHandler(prod_handler) + + # Disable propagation by default + logger.propagate = False + return logger From a9757f8b1e60765f4e5ff77995b6fd1735ee6caf Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 26 Aug 2016 03:08:03 +0200 Subject: [PATCH 024/153] Properly remove f.name usage in send_file (#1988) * Properly remove f.name usage in send_file * Update changelogs * Fix tests --- CHANGES | 6 +++-- docs/upgrading.rst | 40 ++++++++++++++++++++++++++++- flask/helpers.py | 60 +++++++++++++++++++++++++++++-------------- tests/test_helpers.py | 31 ++++++++++++++-------- 4 files changed, 105 insertions(+), 32 deletions(-) diff --git a/CHANGES b/CHANGES index 9e13bd71..5273e09d 100644 --- a/CHANGES +++ b/CHANGES @@ -7,8 +7,10 @@ Version 0.12 ------------ - the cli command now responds to `--version`. -- Mimetype guessing for ``send_file`` has been removed, as per issue ``#104``. - See pull request ``#1849``. +- Mimetype guessing and ETag generation for file-like objects in ``send_file`` + has been removed, as per issue ``#104``. See pull request ``#1849``. +- Mimetype guessing in ``send_file`` now fails loudly and doesn't fall back to + ``application/octet-stream``. See pull request ``#1988``. - Make ``flask.safe_join`` able to join multiple paths like ``os.path.join`` (pull request ``#1730``). diff --git a/docs/upgrading.rst b/docs/upgrading.rst index a85fb0fa..6b933c59 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -19,7 +19,45 @@ providing the ``--upgrade`` parameter:: $ pip install --upgrade Flask -.. _upgrading-to-10: +.. _upgrading-to-012: + +Version 0.12 +------------ + +Changes to send_file +```````````````````` + +The ``filename`` is no longer automatically inferred from file-like objects. +This means that the following code will no longer automatically have +``X-Sendfile`` support, etag generation or MIME-type guessing:: + + response = send_file(open('/path/to/file.txt')) + +Any of the following is functionally equivalent:: + + fname = '/path/to/file.txt' + + # Just pass the filepath directly + response = send_file(fname) + + # Set the MIME-type and ETag explicitly + response = send_file(open(fname), mimetype='text/plain') + response.set_etag(...) + + # Set `attachment_filename` for MIME-type guessing + # ETag still needs to be manually set + response = send_file(open(fname), attachment_filename=fname) + response.set_etag(...) + +The reason for this is that some file-like objects have a invalid or even +misleading ``name`` attribute. Silently swallowing errors in such cases was not +a satisfying solution. + +Additionally the default of falling back to ``application/octet-stream`` has +been removed. If Flask can't guess one or the user didn't provide one, the +function fails. + +.. _upgrading-to-011: Version 0.11 ------------ diff --git a/flask/helpers.py b/flask/helpers.py index 4129ed30..e8422f7a 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -437,7 +437,14 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, to ``True`` to directly emit an ``X-Sendfile`` header. This however requires support of the underlying webserver for ``X-Sendfile``. - You must explicitly provide the mimetype for the filename or file object. + By default it will try to guess the mimetype for you, but you can + also explicitly provide one. For extra security you probably want + to send certain files as attachment (HTML for instance). The mimetype + guessing requires a `filename` or an `attachment_filename` to be + provided. + + ETags will also be attached automatically if a `filename` is provided. You + can turn this off by setting `add_etags=False`. Please never pass filenames to this function from user sources; you should use :func:`send_from_directory` instead. @@ -458,9 +465,13 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, cache_timeout pulls its default from application config, when None. .. versionchanged:: 0.12 - mimetype guessing and etag support removed for file objects. - If no mimetype or attachment_filename is provided, application/octet-stream - will be used. + The filename is no longer automatically inferred from file objects. If + you want to use automatic mimetype and etag support, pass a filepath via + `filename_or_fp` or `attachment_filename`. + + .. versionchanged:: 0.12 + The `attachment_filename` is preferred over `filename` for MIME-type + detection. :param filename_or_fp: the filename of the file to send in `latin-1`. This is relative to the :attr:`~Flask.root_path` @@ -470,8 +481,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, back to the traditional method. Make sure that the file pointer is positioned at the start of data to send before calling :func:`send_file`. - :param mimetype: the mimetype of the file if provided, otherwise - auto detection happens. + :param mimetype: the mimetype of the file if provided. If a file path is + given, auto detection happens as fallback, otherwise an + error will be raised. :param as_attachment: set to ``True`` if you want to send this file with a ``Content-Disposition: attachment`` header. :param attachment_filename: the filename for the attachment if it @@ -490,26 +502,36 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, mtime = None if isinstance(filename_or_fp, string_types): filename = filename_or_fp - file = None - else: - file = filename_or_fp - filename = getattr(file, 'name', None) - - if filename is not None: if not os.path.isabs(filename): filename = os.path.join(current_app.root_path, filename) - if mimetype is None and (filename or attachment_filename): - mimetype = mimetypes.guess_type(filename or attachment_filename)[0] + file = None + if attachment_filename is None: + attachment_filename = os.path.basename(filename) + else: + file = filename_or_fp + filename = None + if mimetype is None: - mimetype = 'application/octet-stream' + if attachment_filename is not None: + mimetype = mimetypes.guess_type(attachment_filename)[0] + + if mimetype is None: + if attachment_filename is not None: + raise ValueError( + 'Unable to infer MIME-type from filename {!r}, please ' + 'pass one explicitly.'.format(mimetype_filename) + ) + raise ValueError( + 'Unable to infer MIME-type because no filename is available. ' + 'Please set either `attachment_filename`, pass a filepath to ' + '`filename_or_fp` or set your own MIME-type via `mimetype`.' + ) headers = Headers() if as_attachment: if attachment_filename is None: - if filename is None: - raise TypeError('filename unavailable, required for ' - 'sending as attachment') - attachment_filename = os.path.basename(filename) + raise TypeError('filename unavailable, required for ' + 'sending as attachment') headers.add('Content-Disposition', 'attachment', filename=attachment_filename) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 610e18ea..0f9a8e27 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -384,18 +384,30 @@ class TestSendfile(object): @app.route('/') def index(): - return flask.send_file(StringIO("party like it's"), last_modified=last_modified) + return flask.send_file(StringIO("party like it's"), + last_modified=last_modified, + mimetype='text/plain') c = app.test_client() rv = c.get('/') assert rv.last_modified == last_modified + def test_send_file_object_without_mimetype(self): + app = flask.Flask(__name__) + + with app.test_request_context(): + with pytest.raises(ValueError) as excinfo: + flask.send_file(StringIO("LOL")) + + assert 'Unable to infer MIME-type' in str(excinfo) + assert 'no filename is available' in str(excinfo) + def test_send_file_object(self): app = flask.Flask(__name__) with app.test_request_context(): with open(os.path.join(app.root_path, 'static/index.html'), mode='rb') as f: - rv = flask.send_file(f) + rv = flask.send_file(f, mimetype='text/html') rv.direct_passthrough = False with app.open_resource('static/index.html') as f: assert rv.data == f.read() @@ -406,17 +418,15 @@ class TestSendfile(object): with app.test_request_context(): with open(os.path.join(app.root_path, 'static/index.html')) as f: - rv = flask.send_file(f) + rv = flask.send_file(f, mimetype='text/html') assert rv.mimetype == 'text/html' - assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') + assert 'x-sendfile' not in rv.headers rv.close() app.use_x_sendfile = False with app.test_request_context(): f = StringIO('Test') - rv = flask.send_file(f) + rv = flask.send_file(f, mimetype='application/octet-stream') rv.direct_passthrough = False assert rv.data == b'Test' assert rv.mimetype == 'application/octet-stream' @@ -429,7 +439,7 @@ class TestSendfile(object): return getattr(self._io, name) f = PyStringIO('Test') f.name = 'test.txt' - rv = flask.send_file(f) + rv = flask.send_file(f, attachment_filename=f.name) rv.direct_passthrough = False assert rv.data == b'Test' assert rv.mimetype == 'text/plain' @@ -446,7 +456,7 @@ class TestSendfile(object): with app.test_request_context(): f = StringIO('Test') - rv = flask.send_file(f) + rv = flask.send_file(f, mimetype='text/html') assert 'x-sendfile' not in rv.headers rv.close() @@ -454,7 +464,8 @@ class TestSendfile(object): app = flask.Flask(__name__) with app.test_request_context(): with open(os.path.join(app.root_path, 'static/index.html')) as f: - rv = flask.send_file(f, as_attachment=True) + rv = flask.send_file(f, as_attachment=True, + attachment_filename='index.html') value, options = \ parse_options_header(rv.headers['Content-Disposition']) assert value == 'attachment' From 2a5061282bf17ce21f0601adb463bc3756ff1e4f Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 27 Aug 2016 14:32:53 +0200 Subject: [PATCH 025/153] Only passthrough_errors if PROPAGATE_EXCEPTIONS See pallets/werkzeug#954 --- flask/app.py | 3 ++- flask/cli.py | 2 +- tests/test_basic.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/flask/app.py b/flask/app.py index dac7fe26..ecd94aa8 100644 --- a/flask/app.py +++ b/flask/app.py @@ -838,7 +838,8 @@ class Flask(_PackageBoundObject): self.debug = bool(debug) options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) - options.setdefault('passthrough_errors', True) + options.setdefault('passthrough_errors', + self.config['PROPAGATE_EXCEPTIONS']) try: run_simple(host, port, self, **options) finally: diff --git a/flask/cli.py b/flask/cli.py index 9b8fa2cd..cd301d79 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -430,7 +430,7 @@ def run_command(info, host, port, reload, debugger, eager_loading, run_simple(host, port, app, use_reloader=reload, use_debugger=debugger, threaded=with_threads, - passthrough_errors=True) + passthrough_errors=app.config['PROPAGATE_EXCEPTIONS']) @click.command('shell', short_help='Runs a shell in the app context.') diff --git a/tests/test_basic.py b/tests/test_basic.py index 55687359..2af7a514 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1268,8 +1268,8 @@ def test_werkzeug_passthrough_errors(monkeypatch, debug, use_debugger, monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) app.config['PROPAGATE_EXCEPTIONS'] = propagate_exceptions app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader) - # make sure werkzeug always passes errors through - assert rv['passthrough_errors'] + # make sure werkzeug passes errors through if PROPAGATE_EXCEPTIONS + assert rv['passthrough_errors'] == propagate_exceptions def test_max_content_length(): From faded8c5c79ba48b0d7529fdf34a2ce79089332e Mon Sep 17 00:00:00 2001 From: dawran6 Date: Sun, 28 Aug 2016 15:06:53 -0700 Subject: [PATCH 026/153] sessions documentation (client side vs server side) #434 (#1888) Mention the existence of Flask extentions that handle server-side sessions. Attempt to improve the reading flow. --- docs/quickstart.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 433e4e08..c822db26 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -817,6 +817,9 @@ values do not persist across requests, cookies are indeed enabled, and you are not getting a clear error message, check the size of the cookie in your page responses compared to the size supported by web browsers. +Besides the default client-side based sessions, if you want to handle +sessions on the server-side instead, there are several +Flask extensions that support this. Message Flashing ---------------- From 0f2c793e14d5f22b8eaabe89679f22462ac0684d Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Mon, 29 Aug 2016 18:26:20 -0400 Subject: [PATCH 027/153] Spelling (#1998) * spelling: cacheability * spelling: conceptually * spelling: javascript * spelling: reset * spelling: raised * comma: instead..., they... --- CHANGES | 2 +- docs/config.rst | 2 +- docs/errorhandling.rst | 2 +- docs/tutorial/folders.rst | 2 +- flask/app.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 5273e09d..4ea6323b 100644 --- a/CHANGES +++ b/CHANGES @@ -327,7 +327,7 @@ Released on September 29th 2011, codename Rakija - Applications now not only have a root path where the resources and modules are located but also an instance path which is the designated place to drop files that are modified at runtime (uploads etc.). Also this is - conceptionally only instance depending and outside version control so it's + conceptually only instance depending and outside version control so it's the perfect place to put configuration files etc. For more information see :ref:`instance-folders`. - Added the ``APPLICATION_ROOT`` configuration variable. diff --git a/docs/config.rst b/docs/config.rst index 4958d471..89fa0924 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -177,7 +177,7 @@ The following configuration values are used internally by Flask: behavior by changing this variable. This is not recommended but might give you a performance improvement on the - cost of cachability. + cost of cacheability. ``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default) jsonify responses will be pretty printed if they are not requested by an diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 2dc7fafe..a4d5f781 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -77,7 +77,7 @@ You might want to show custom error pages to the user when an error occurs. This can be done by registering error handlers. Error handlers are normal :ref:`views` but instead of being registered for -routes they are registered for exceptions that are rised while trying to +routes, they are registered for exceptions that are raised while trying to do something else. Registering diff --git a/docs/tutorial/folders.rst b/docs/tutorial/folders.rst index 4e117d1f..ba62b3b7 100644 --- a/docs/tutorial/folders.rst +++ b/docs/tutorial/folders.rst @@ -19,7 +19,7 @@ creating the database schema as well as the main module. As a quick side note, the files inside of the :file:`static` folder are available to users of the application via HTTP. This is the place where CSS and -Javascript files go. Inside the :file:`templates` folder, Flask will look for +JavaScript files go. Inside the :file:`templates` folder, Flask will look for `Jinja2`_ templates. You will see examples of this later on. For now you should continue with :ref:`tutorial-schema`. diff --git a/flask/app.py b/flask/app.py index ecd94aa8..774d5234 100644 --- a/flask/app.py +++ b/flask/app.py @@ -844,7 +844,7 @@ class Flask(_PackageBoundObject): run_simple(host, port, self, **options) finally: # reset the first request information if the development server - # resetted normally. This makes it possible to restart the server + # reset normally. This makes it possible to restart the server # without reloader and that stuff from an interactive shell. self._got_first_request = False From 8d501150d70605fa499684cf9a41a7767cd1d975 Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Wed, 31 Aug 2016 12:37:36 -0400 Subject: [PATCH 028/153] Better workflow for flaskr and other basic apps (#2000) - adds `from flaskr import app` to top-level in flaskr module - effect is that `export FLASK_APP=flaskr` works over the more verbose `export FLASK_APP=flaskr.flask` - see the readme for how to run - all tests are passing with `py.test` or `python setup.py test` (in venv) --- examples/flaskr/README | 2 +- examples/flaskr/flaskr/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/flaskr/README b/examples/flaskr/README index 3cb021e7..90860ff2 100644 --- a/examples/flaskr/README +++ b/examples/flaskr/README @@ -19,7 +19,7 @@ 3. Instruct flask to use the right application - export FLASK_APP=flaskr.flaskr + export FLASK_APP=flaskr 4. initialize the database with this command: diff --git a/examples/flaskr/flaskr/__init__.py b/examples/flaskr/flaskr/__init__.py index e69de29b..14a36539 100644 --- a/examples/flaskr/flaskr/__init__.py +++ b/examples/flaskr/flaskr/__init__.py @@ -0,0 +1 @@ +from flaskr import app \ No newline at end of file From d7c096eb3578f7432059098efe8fc8ad5852e0dd Mon Sep 17 00:00:00 2001 From: PHeanEX Date: Wed, 31 Aug 2016 22:05:12 +0200 Subject: [PATCH 029/153] Fix small grammar error (Of/Or) (#2001) --- docs/errorhandling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index a4d5f781..3bda5f15 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -51,7 +51,7 @@ And then add this to your Flask app:: from raven.contrib.flask import Sentry sentry = Sentry(app, dsn='YOUR_DSN_HERE') -Of if you are using factories you can also init it later:: +Or if you are using factories you can also init it later:: from raven.contrib.flask import Sentry sentry = Sentry(dsn='YOUR_DSN_HERE') From ae649aacaf82d60b4f8d510088df4c9985315ed1 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 5 Sep 2016 03:28:05 +0400 Subject: [PATCH 030/153] Fix error in send_file helper (#2003) * Fix error in send_file (mimetype_filename is not defined) * fix formatting error message in send_file --- flask/helpers.py | 4 ++-- tests/test_helpers.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index e8422f7a..56a91dd8 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -518,8 +518,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if mimetype is None: if attachment_filename is not None: raise ValueError( - 'Unable to infer MIME-type from filename {!r}, please ' - 'pass one explicitly.'.format(mimetype_filename) + 'Unable to infer MIME-type from filename {0!r}, please ' + 'pass one explicitly.'.format(attachment_filename) ) raise ValueError( 'Unable to infer MIME-type because no filename is available. ' diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 0f9a8e27..fc85625e 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -398,10 +398,14 @@ class TestSendfile(object): with app.test_request_context(): with pytest.raises(ValueError) as excinfo: flask.send_file(StringIO("LOL")) - assert 'Unable to infer MIME-type' in str(excinfo) assert 'no filename is available' in str(excinfo) + with app.test_request_context(): + with pytest.raises(ValueError) as excinfo: + flask.send_file(StringIO("LOL"), attachment_filename='filename') + assert "Unable to infer MIME-type from filename 'filename'" in str(excinfo) + def test_send_file_object(self): app = flask.Flask(__name__) From e42c5b3d9484a07eb1de5eeb7b847368ebb028aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afonso=20Queir=C3=B3s?= Date: Mon, 5 Sep 2016 16:57:00 +0200 Subject: [PATCH 031/153] Correcting Custom Test Client class docs (#2004) --- flask/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index 774d5234..bfc0efc2 100644 --- a/flask/app.py +++ b/flask/app.py @@ -878,9 +878,9 @@ class Flask(_PackageBoundObject): from flask.testing import FlaskClient class CustomClient(FlaskClient): - def __init__(self, authentication=None, *args, **kwargs): - FlaskClient.__init__(*args, **kwargs) - self._authentication = authentication + def __init__(self, *args, **kwargs): + self._authentication = kwargs.pop("authentication") + super(CustomClient,self).__init__( *args, **kwargs) app.test_client_class = CustomClient client = app.test_client(authentication='Basic ....') From ec9e9c2f6e20c20a19ee76a9eb9694ebb1bf7cd8 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 6 Sep 2016 22:32:34 +0200 Subject: [PATCH 032/153] Don't passthrough_errors unless instructed. (#2006) Fix #2005 Revert #1679 and #1996 --- flask/app.py | 2 -- flask/cli.py | 3 +-- tests/test_basic.py | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/flask/app.py b/flask/app.py index bfc0efc2..deedc83a 100644 --- a/flask/app.py +++ b/flask/app.py @@ -838,8 +838,6 @@ class Flask(_PackageBoundObject): self.debug = bool(debug) options.setdefault('use_reloader', self.debug) options.setdefault('use_debugger', self.debug) - options.setdefault('passthrough_errors', - self.config['PROPAGATE_EXCEPTIONS']) try: run_simple(host, port, self, **options) finally: diff --git a/flask/cli.py b/flask/cli.py index cd301d79..28818515 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -429,8 +429,7 @@ def run_command(info, host, port, reload, debugger, eager_loading, print(' * Forcing debug mode %s' % (debug and 'on' or 'off')) run_simple(host, port, app, use_reloader=reload, - use_debugger=debugger, threaded=with_threads, - passthrough_errors=app.config['PROPAGATE_EXCEPTIONS']) + use_debugger=debugger, threaded=with_threads) @click.command('shell', short_help='Runs a shell in the app context.') diff --git a/tests/test_basic.py b/tests/test_basic.py index 2af7a514..33f6ec07 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1268,8 +1268,6 @@ def test_werkzeug_passthrough_errors(monkeypatch, debug, use_debugger, monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) app.config['PROPAGATE_EXCEPTIONS'] = propagate_exceptions app.run(debug=debug, use_debugger=use_debugger, use_reloader=use_reloader) - # make sure werkzeug passes errors through if PROPAGATE_EXCEPTIONS - assert rv['passthrough_errors'] == propagate_exceptions def test_max_content_length(): From 394d5d57c813ebb82eb13c61de854846d6416523 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 7 Sep 2016 18:18:53 +0200 Subject: [PATCH 033/153] Changelog for #2006 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 4ea6323b..76907082 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Version 0.12 ``application/octet-stream``. See pull request ``#1988``. - Make ``flask.safe_join`` able to join multiple paths like ``os.path.join`` (pull request ``#1730``). +- Revert a behavior change that made the dev server crash instead of returning + a Internal Server Error (pull request ``#2006``). Version 0.11.2 -------------- From a7fbfb387f7a8c34a674fcc3f5b5f9ca68ada4f5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 8 Sep 2016 11:55:59 +0300 Subject: [PATCH 034/153] Corrected after response for error handlers Before this change after request functions were not correctly invoked for error handlers. --- CHANGES | 2 ++ flask/app.py | 29 +++++++++++++++++++++++++---- tests/test_basic.py | 23 +++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 76907082..ac53fbd5 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,8 @@ Version 0.12 (pull request ``#1730``). - Revert a behavior change that made the dev server crash instead of returning a Internal Server Error (pull request ``#2006``). +- Correctly invoke response handlers for both regular request dispatching as + well as error handlers. Version 0.11.2 -------------- diff --git a/flask/app.py b/flask/app.py index deedc83a..40fedda6 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1555,7 +1555,7 @@ class Flask(_PackageBoundObject): self.log_exception((exc_type, exc_value, tb)) if handler is None: return InternalServerError() - return handler(e) + return self.finalize_request(handler(e), from_error_handler=True) def log_exception(self, exc_info): """Logs an exception. This is called by :meth:`handle_exception` @@ -1623,9 +1623,30 @@ class Flask(_PackageBoundObject): rv = self.dispatch_request() except Exception as e: rv = self.handle_user_exception(e) + return self.finalize_request(rv) + + def finalize_request(self, rv, from_error_handler=False): + """Given the return value from a view function this finalizes + the request by converting it into a repsonse and invoking the + postprocessing functions. This is invoked for both normal + request dispatching as well as error handlers. + + Because this means that it might be called as a result of a + failure a special safe mode is available which can be enabled + with the `from_error_handler` flag. If enabled failures in + response processing will be logged and otherwise ignored. + + :internal: + """ response = self.make_response(rv) - response = self.process_response(response) - request_finished.send(self, response=response) + try: + response = self.process_response(response) + request_finished.send(self, response=response) + except Exception: + if not from_error_handler: + raise + self.logger.exception('Request finalizing failed with an ' + 'error while handling an error') return response def try_trigger_before_first_request_functions(self): @@ -1972,7 +1993,7 @@ class Flask(_PackageBoundObject): response = self.full_dispatch_request() except Exception as e: error = e - response = self.make_response(self.handle_exception(e)) + response = self.handle_exception(e) return response(environ, start_response) finally: if self.should_ignore_error(error): diff --git a/tests/test_basic.py b/tests/test_basic.py index 33f6ec07..be3d5edd 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -768,6 +768,29 @@ def test_error_handling(): assert b'forbidden' == rv.data +def test_error_handling_processing(): + app = flask.Flask(__name__) + app.config['LOGGER_HANDLER_POLICY'] = 'never' + + @app.errorhandler(500) + def internal_server_error(e): + return 'internal server error', 500 + + @app.route('/') + def broken_func(): + 1 // 0 + + @app.after_request + def after_request(resp): + resp.mimetype = 'text/x-special' + return resp + + with app.test_client() as c: + resp = c.get('/') + assert resp.mimetype == 'text/x-special' + assert resp.data == b'internal server error' + + def test_before_request_and_routing_errors(): app = flask.Flask(__name__) From 6ed4a18712cb823583fc282a748544cff6b1d2ac Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Thu, 8 Sep 2016 09:19:48 -0400 Subject: [PATCH 035/153] Clean up tutorial docs for installable app pattern with flaskr (#2002) * Clean up tutorial docs for installable app pattern - reading sequentially through the tutorial works. - fixes references to `export FLASK_APP=flaskr.flaskr` * Fixes titles for each section of flaskr tutorial * Revert grammar * Emphasize the Packaging Guide - adds more general packaging resource - removes the emphasis put on setuptools * rephrase and remove note admonitions - expanded on few points - removed note blocks, they are unneccessary * Remove note about reinstalling to update cli - I had mistakenly thought it was necessary to re-install the app to update the cli. - the `--editable` flag detects the change and the cli updates without issue. --- docs/tutorial/css.rst | 2 +- docs/tutorial/dbcon.rst | 2 +- docs/tutorial/index.rst | 2 +- .../{setuptools.rst => packaging.rst} | 47 ++++++++++++------ docs/tutorial/setup.rst | 2 +- docs/tutorial/templates.rst | 2 +- docs/tutorial/testing.rst | 48 +++++++++++-------- docs/tutorial/views.rst | 2 +- 8 files changed, 67 insertions(+), 40 deletions(-) rename docs/tutorial/{setuptools.rst => packaging.rst} (61%) diff --git a/docs/tutorial/css.rst b/docs/tutorial/css.rst index ea461a89..56414657 100644 --- a/docs/tutorial/css.rst +++ b/docs/tutorial/css.rst @@ -1,6 +1,6 @@ .. _tutorial-css: -Step 9: Adding Style +Step 8: Adding Style ==================== Now that everything else works, it's time to add some style to the diff --git a/docs/tutorial/dbcon.rst b/docs/tutorial/dbcon.rst index 9f4428b9..2dd3d7be 100644 --- a/docs/tutorial/dbcon.rst +++ b/docs/tutorial/dbcon.rst @@ -3,7 +3,7 @@ Step 4: Database Connections ---------------------------- -You now have a function for establishing a database connection with +You currently have a function for establishing a database connection with `connect_db`, but by itself, it is not particularly useful. Creating and closing database connections all the time is very inefficient, so you will need to keep it around for longer. Because database connections diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index ccd4e7d2..f0a583e0 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -24,7 +24,7 @@ the `example source`_. folders schema setup - setuptools + packaging dbcon dbinit views diff --git a/docs/tutorial/setuptools.rst b/docs/tutorial/packaging.rst similarity index 61% rename from docs/tutorial/setuptools.rst rename to docs/tutorial/packaging.rst index 306d94d3..8db6531e 100644 --- a/docs/tutorial/setuptools.rst +++ b/docs/tutorial/packaging.rst @@ -1,7 +1,7 @@ -.. _tutorial-setuptools: +.. _tutorial-packaging: -Step 3: Installing flaskr with setuptools -========================================= +Step 3: Installing flaskr as a Package +====================================== Flask is now shipped with built-in support for `Click`_. Click provides Flask with enhanced and extensible command line utilities. Later in this @@ -9,17 +9,21 @@ tutorial you will see exactly how to extend the ``flask`` command line interface (CLI). A useful pattern to manage a Flask application is to install your app -using `setuptools`_. This involves creating a :file:`setup.py` -in the projects root directory. You also need to add an empty -:file:`__init__.py` file to make the :file:`flaskr/flaskr` directory -a package. The code structure at this point should be:: +following the `Python Packaging Guide`_. Presently this involves +creating two new files; :file:`setup.py` and :file:`MANIFEST.in` in the +projects root directory. You also need to add an :file:`__init__.py` +file to make the :file:`flaskr/flaskr` directory a package. After these +changes, your code structure should be:: /flaskr /flaskr __init__.py /static /templates + flaskr.py + schema.sql setup.py + MANIFEST.in The content of the ``setup.py`` file for ``flaskr`` is: @@ -46,22 +50,37 @@ following lines:: graft flaskr/static include flaskr/schema.sql +To simplify locating the application, add the following import statement +into this file, :file:`flaskr/__init__.py`: + +.. sourcecode:: python + + from flaskr import app + +This import statement brings the application instance into the top-level +of the application package. When it is time to run the application, the +Flask development server needs the location of the app instance. This +import statement simplifies the location process. Without it the export +statement a few steps below would need to be +``export FLASK_APP=flaskr.flaskr``. + At this point you should be able to install the application. As usual, it is recommended to install your Flask application within a `virtualenv`_. With that said, go ahead and install the application with:: pip install --editable . -.. note:: The above installation command assumes that it is run within the - projects root directory, `flaskr/`. Also, the `editable` flag allows - editing source code without having to reinstall the Flask app each time - you make changes. +The above installation command assumes that it is run within the projects +root directory, `flaskr/`. The `editable` flag allows editing +source code without having to reinstall the Flask app each time you make +changes. The flaskr app is now installed in your virtualenv (see output +of ``pip freeze``). With that out of the way, you should be able to start up the application. Do this with the following commands:: - export FLASK_APP=flaskr.flaskr - export FLASK_DEBUG=1 + export FLASK_APP=flaskr + export FLASK_DEBUG=true flask run (In case you are on Windows you need to use `set` instead of `export`). @@ -85,5 +104,5 @@ but first, you should get the database working. Continue with :ref:`tutorial-dbcon`. .. _Click: http://click.pocoo.org -.. _setuptools: https://setuptools.readthedocs.io +.. _Python Packaging Guide: https://packaging.python.org .. _virtualenv: https://virtualenv.pypa.io diff --git a/docs/tutorial/setup.rst b/docs/tutorial/setup.rst index 78b6390a..4bedb54c 100644 --- a/docs/tutorial/setup.rst +++ b/docs/tutorial/setup.rst @@ -94,4 +94,4 @@ tuples. In the next section you will see how to run the application. -Continue with :ref:`tutorial-setuptools`. +Continue with :ref:`tutorial-packaging`. diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index d6558233..269e8df1 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -1,6 +1,6 @@ .. _tutorial-templates: -Step 8: The Templates +Step 7: The Templates ===================== Now it is time to start working on the templates. As you may have diff --git a/docs/tutorial/testing.rst b/docs/tutorial/testing.rst index c5ecf7dd..dcf36594 100644 --- a/docs/tutorial/testing.rst +++ b/docs/tutorial/testing.rst @@ -9,10 +9,10 @@ modifications in the future. The application above is used as a basic example of how to perform unit testing in the :ref:`testing` section of the documentation. Go there to see how easy it is to test Flask applications. -Adding Tests to flaskr -====================== +Adding tests to flaskr +---------------------- -Assuming you have seen the testing section above and have either written +Assuming you have seen the :ref:`testing` section and have either written your own tests for ``flaskr`` or have followed along with the examples provided, you might be wondering about ways to organize the project. @@ -24,30 +24,38 @@ One possible and recommended project structure is:: static/ templates/ tests/ - context.py test_flaskr.py setup.py MANIFEST.in -For now go ahead a create the :file:`tests/` directory as well as the -:file:`context.py` and :file:`test_flaskr.py` files, if you haven't -already. The context file is used as an import helper. The contents -of that file are:: +For now go ahead a create the :file:`tests/` directory as well as the +:file:`test_flaskr.py` file. - import sys, os +Running the tests +----------------- - basedir = os.path.dirname(os.path.abspath(__file__)) - sys.path.insert(0, basedir + '/../') +At this point you can run the tests. Here ``pytest`` will be used. - from flaskr import flaskr +.. note:: Make sure that ``pytest`` is installed in the same virtualenv + as flaskr. Otherwise ``pytest`` test will not be able to import the + required components to test the application:: -Testing + Setuptools -==================== + pip install -e . + pip install pytest -One way to handle testing is to integrate it with ``setuptools``. All it -requires is adding a couple of lines to the :file:`setup.py` file and -creating a new file :file:`setup.cfg`. Go ahead and update the -:file:`setup.py` to contain:: +Run and watch the tests pass, within the top-level :file:`flaskr/` +directory as:: + + py.test + +Testing + setuptools +-------------------- + +One way to handle testing is to integrate it with ``setuptools``. Here +that requires adding a couple of lines to the :file:`setup.py` file and +creating a new file :file:`setup.cfg`. One benefit of running the tests +this way is that you do not have to install ``pytest``. Go ahead and +update the :file:`setup.py` file to contain:: from setuptools import setup @@ -58,7 +66,6 @@ creating a new file :file:`setup.cfg`. Go ahead and update the install_requires=[ 'flask', ], - ) setup_requires=[ 'pytest-runner', ], @@ -66,6 +73,7 @@ creating a new file :file:`setup.cfg`. Go ahead and update the 'pytest', ], ) + Now create :file:`setup.cfg` in the project root (alongside :file:`setup.py`):: @@ -85,4 +93,4 @@ found, run, and hopefully pass. This is one possible way to run and manage testing. Here ``pytest`` is used, but there are other options such as ``nose``. Integrating testing with ``setuptools`` is convenient because it is not necessary to actually -download ``pytest`` or any other testing framework one might use. \ No newline at end of file +download ``pytest`` or any other testing framework one might use. diff --git a/docs/tutorial/views.rst b/docs/tutorial/views.rst index d9838073..4364d973 100644 --- a/docs/tutorial/views.rst +++ b/docs/tutorial/views.rst @@ -1,6 +1,6 @@ .. _tutorial-views: -Step 7: The View Functions +Step 6: The View Functions ========================== Now that the database connections are working, you can start writing the From d34d87809565ad1fe4cd2301eacdd2ab06f1b66a Mon Sep 17 00:00:00 2001 From: Akbar Ibrahim Date: Thu, 8 Sep 2016 21:04:51 +0530 Subject: [PATCH 036/153] Fixed error in errorhandler doc string. (#2014) --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index deedc83a..9d14bbe3 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1115,7 +1115,7 @@ class Flask(_PackageBoundObject): @setupmethod def errorhandler(self, code_or_exception): - """A decorator that is used to register a function give a given + """A decorator that is used to register a function given an error code. Example:: @app.errorhandler(404) From 09e6254efc85ee73ab8ea071f982b7bc600af268 Mon Sep 17 00:00:00 2001 From: Andrew Arendt Date: Thu, 8 Sep 2016 12:24:07 -0500 Subject: [PATCH 037/153] fixed deprecated syntax in setup.cfg (#2015) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 47c225ea..34414b3e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,5 +4,5 @@ release = egg_info -RDb '' [wheel] universal = 1 -[pytest] +[tool:pytest] norecursedirs = .* *.egg *.egg-info env* artwork docs examples From 830632714cbaaf66e7b4a29602b6a908cb1b28f6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 10 Sep 2016 03:33:53 +0300 Subject: [PATCH 038/153] Do not error for unknown files if send_file sends an actual file --- flask/helpers.py | 3 ++- tests/test_helpers.py | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 56a91dd8..4034112e 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -513,7 +513,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if mimetype is None: if attachment_filename is not None: - mimetype = mimetypes.guess_type(attachment_filename)[0] + mimetype = mimetypes.guess_type(attachment_filename)[0] \ + or 'application/octet-stream' if mimetype is None: if attachment_filename is not None: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index fc85625e..e1469007 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -402,9 +402,7 @@ class TestSendfile(object): assert 'no filename is available' in str(excinfo) with app.test_request_context(): - with pytest.raises(ValueError) as excinfo: - flask.send_file(StringIO("LOL"), attachment_filename='filename') - assert "Unable to infer MIME-type from filename 'filename'" in str(excinfo) + flask.send_file(StringIO("LOL"), attachment_filename='filename') def test_send_file_object(self): app = flask.Flask(__name__) From 578aa5805a5e1b2820bf80cefd608a598eb2887a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 11 Sep 2016 16:57:43 +0300 Subject: [PATCH 039/153] Killed now dead code --- flask/helpers.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 4034112e..05361626 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -517,11 +517,6 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, or 'application/octet-stream' if mimetype is None: - if attachment_filename is not None: - raise ValueError( - 'Unable to infer MIME-type from filename {0!r}, please ' - 'pass one explicitly.'.format(attachment_filename) - ) raise ValueError( 'Unable to infer MIME-type because no filename is available. ' 'Please set either `attachment_filename`, pass a filepath to ' From f484a724a486bfce8a2c2da32d75f427f782e59d Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Sun, 11 Sep 2016 11:53:35 -0400 Subject: [PATCH 040/153] Address #1980 (#2021) * Distinguish between directories and files * Convert larger apps to make use of setup.py - replaces runserver.py with setup.py - example now runs with recommended structure * Fixes a typo and formats the added paragraph --- docs/patterns/packages.rst | 47 +++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index af51717d..1cd77974 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -8,9 +8,9 @@ module. That is quite simple. Imagine a small application looks like this:: /yourapplication - /yourapplication.py + yourapplication.py /static - /style.css + style.css /templates layout.html index.html @@ -29,9 +29,9 @@ You should then end up with something like that:: /yourapplication /yourapplication - /__init__.py + __init__.py /static - /style.css + style.css /templates layout.html index.html @@ -41,11 +41,36 @@ You should then end up with something like that:: But how do you run your application now? The naive ``python yourapplication/__init__.py`` will not work. Let's just say that Python does not want modules in packages to be the startup file. But that is not -a big problem, just add a new file called :file:`runserver.py` next to the inner +a big problem, just add a new file called :file:`setup.py` next to the inner :file:`yourapplication` folder with the following contents:: - from yourapplication import app - app.run(debug=True) + from setuptools import setup + + setup( + name='yourapplication', + packages=['yourapplication'], + include_package_data=True, + install_requires=[ + 'flask', + ], + ) + +In order to run the application you need to export an environment variable +that tells Flask where to find the application instance:: + + export FLASK_APP=yourapplication + +If you are outside of the project directory make sure to provide the exact +path to your application directory. Similiarly you can turn on "debug +mode" with this environment variable:: + + export FLASK_DEBUG=true + +In order to install and run the application you need to issue the following +commands:: + + pip install -e . + flask run What did we gain from this? Now we can restructure the application a bit into multiple modules. The only thing you have to remember is the @@ -77,12 +102,12 @@ And this is what :file:`views.py` would look like:: You should then end up with something like that:: /yourapplication - /runserver.py + setup.py /yourapplication - /__init__.py - /views.py + __init__.py + views.py /static - /style.css + style.css /templates layout.html index.html From c78bcdf25c7fa0ffe75b2b42baedeba961ad233d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 11 Sep 2016 21:28:30 +0300 Subject: [PATCH 041/153] Updated upgrade docs --- docs/upgrading.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 6b933c59..41b70f03 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -54,8 +54,8 @@ misleading ``name`` attribute. Silently swallowing errors in such cases was not a satisfying solution. Additionally the default of falling back to ``application/octet-stream`` has -been removed. If Flask can't guess one or the user didn't provide one, the -function fails. +been restricted. If Flask can't guess one or the user didn't provide one, the +function fails if no filename information was provided. .. _upgrading-to-011: From 675805b2a43e2466d90777bf67fa73ae40ec3f8d Mon Sep 17 00:00:00 2001 From: Pablo Marti Date: Mon, 12 Sep 2016 08:41:09 +0100 Subject: [PATCH 042/153] Fix typo in docs Also added one missing comma for readability --- flask/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/app.py b/flask/app.py index ee8a3fba..90aedf1d 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1627,13 +1627,13 @@ class Flask(_PackageBoundObject): def finalize_request(self, rv, from_error_handler=False): """Given the return value from a view function this finalizes - the request by converting it into a repsonse and invoking the + the request by converting it into a response and invoking the postprocessing functions. This is invoked for both normal request dispatching as well as error handlers. Because this means that it might be called as a result of a failure a special safe mode is available which can be enabled - with the `from_error_handler` flag. If enabled failures in + with the `from_error_handler` flag. If enabled, failures in response processing will be logged and otherwise ignored. :internal: From 1759c209452396209d518da6e7ff85373d00fed5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 12 Sep 2016 21:55:17 +0300 Subject: [PATCH 043/153] Set merge strategy for CHANGES --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..8383fff9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGES merge=union From 06dd928b8d06724ef774f6da6775e7765b2bcc2d Mon Sep 17 00:00:00 2001 From: Andrew Arendt Date: Wed, 14 Sep 2016 13:03:21 -0500 Subject: [PATCH 044/153] Remove nonsense from cli docs --- docs/cli.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 10f5b34c..7ddf50f3 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -61,9 +61,7 @@ Debug Flag The :command:`flask` script can also be instructed to enable the debug mode of the application automatically by exporting ``FLASK_DEBUG``. If -set to ``1`` debug is enabled or ``0`` disables it. - -Or with a filename:: +set to ``1`` debug is enabled or ``0`` disables it:: export FLASK_DEBUG=1 From f5388fd5b2e7f535500894a5cd3884155dcde30d Mon Sep 17 00:00:00 2001 From: Bruno Thalmann Date: Sun, 18 Sep 2016 14:10:00 +0200 Subject: [PATCH 045/153] Removed unused import. (#2026) --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 05361626..1febbc5c 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -39,7 +39,7 @@ from jinja2 import FileSystemLoader from .signals import message_flashed from .globals import session, _request_ctx_stack, _app_ctx_stack, \ current_app, request -from ._compat import string_types, text_type, PY2 +from ._compat import string_types, text_type # sentinel From 7b9b5580ee2f2325876683b046744bebfa652726 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 18 Sep 2016 15:47:52 +0200 Subject: [PATCH 046/153] Use abort docs from Werkzeug Fix #1960 --- docs/api.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e72c9ace..d77da3de 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -316,13 +316,7 @@ Useful Functions and Classes .. autofunction:: url_for -.. function:: abort(code) - - Raises an :exc:`~werkzeug.exceptions.HTTPException` for the given - status code. For example to abort request handling with a page not - found exception, you would call ``abort(404)``. - - :param code: the HTTP error code. +.. autofunction:: abort .. autofunction:: redirect From 074563185c7e293584e3d8c0b57dfe8ff1d8694e Mon Sep 17 00:00:00 2001 From: Benjamin Dopplinger Date: Mon, 19 Sep 2016 13:24:46 +1000 Subject: [PATCH 047/153] Fix typo in MethodView doc (#2028) --- flask/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/views.py b/flask/views.py index 6e249180..83394c1f 100644 --- a/flask/views.py +++ b/flask/views.py @@ -123,7 +123,7 @@ class MethodViewType(type): class MethodView(with_metaclass(MethodViewType, View)): """Like a regular class-based view but that dispatches requests to particular methods. For instance if you implement a method called - :meth:`get` it means you will response to ``'GET'`` requests and + :meth:`get` it means it will respond to ``'GET'`` requests and the :meth:`dispatch_request` implementation will automatically forward your request to that. Also :attr:`options` is set for you automatically:: From c60864226ffaccb25486d528f42837afb81ab5a1 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 19 Sep 2016 23:29:52 +0200 Subject: [PATCH 048/153] Avoid always-false statement See https://github.com/pallets/flask/pull/1849/files#r79371299 --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 1febbc5c..fd072ba1 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -559,7 +559,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, rv.cache_control.max_age = cache_timeout rv.expires = int(time() + cache_timeout) - if add_etags and filename is not None and file is None: + if add_etags and filename is not None: from warnings import warn try: From 114257ed58ac5311049e5c468668d42f6844aabf Mon Sep 17 00:00:00 2001 From: Douglas Thor Date: Sat, 24 Sep 2016 04:07:19 -0700 Subject: [PATCH 049/153] Updated mod_wsgi.rst to point to new mod_wsgi repo (#2038) --- docs/deploying/mod_wsgi.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index b06a1904..0f4af6c3 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -130,12 +130,12 @@ to httpd 2.4 syntax Require all granted -For more information consult the `mod_wsgi wiki`_. +For more information consult the `mod_wsgi documentation`_. -.. _mod_wsgi: http://code.google.com/p/modwsgi/ -.. _installation instructions: http://code.google.com/p/modwsgi/wiki/QuickInstallationGuide +.. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi +.. _installation instructions: http://modwsgi.readthedocs.io/en/develop/installation.html .. _virtual python: https://pypi.python.org/pypi/virtualenv -.. _mod_wsgi wiki: http://code.google.com/p/modwsgi/w/list +.. _mod_wsgi documentation: http://modwsgi.readthedocs.io/en/develop/index.html Troubleshooting --------------- From 541f5439e0972f0b9ae04d5e3d2bd2c5d204a449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=98=8E?= Date: Mon, 26 Sep 2016 00:25:54 +0800 Subject: [PATCH 050/153] Fix unbound error (#2039) --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 90aedf1d..59c77a15 100644 --- a/flask/app.py +++ b/flask/app.py @@ -519,7 +519,7 @@ class Flask(_PackageBoundObject): #: def to_python(self, value): #: return value.split(',') #: def to_url(self, values): - #: return ','.join(BaseConverter.to_url(value) + #: return ','.join(super(ListConverter, self).to_url(value) #: for value in values) #: #: app = Flask(__name__) From f2b945d586ae530f56b294326cae51fd8dec2fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Charles?= Date: Mon, 26 Sep 2016 12:43:46 +0200 Subject: [PATCH 051/153] make use of range requests if available in werkzeug (#2031) * make use of range requests if available in werkzeug * different logic for testing werkzeug functionality --- CHANGES | 1 + flask/helpers.py | 36 ++++++++++++++++++------ tests/test_helpers.py | 65 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index cec86584..5a1f5a33 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Version 0.12 - Correctly invoke response handlers for both regular request dispatching as well as error handlers. - Disable logger propagation by default for the app logger. +- Add support for range requests in ``send_file``. Version 0.11.2 -------------- diff --git a/flask/helpers.py b/flask/helpers.py index fd072ba1..c6c2cddc 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -25,8 +25,9 @@ try: except ImportError: from urlparse import quote as url_quote -from werkzeug.datastructures import Headers -from werkzeug.exceptions import BadRequest, NotFound +from werkzeug.datastructures import Headers, Range +from werkzeug.exceptions import BadRequest, NotFound, \ + RequestedRangeNotSatisfiable # this was moved in 0.7 try: @@ -446,6 +447,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, ETags will also be attached automatically if a `filename` is provided. You can turn this off by setting `add_etags=False`. + If `conditional=True` and `filename` is provided, this method will try to + upgrade the response stream to support range requests. This will allow + the request to be answered with partial content response. + Please never pass filenames to this function from user sources; you should use :func:`send_from_directory` instead. @@ -500,6 +505,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, If a file was passed, this overrides its mtime. """ mtime = None + fsize = None if isinstance(filename_or_fp, string_types): filename = filename_or_fp if not os.path.isabs(filename): @@ -535,13 +541,15 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if file is not None: file.close() headers['X-Sendfile'] = filename - headers['Content-Length'] = os.path.getsize(filename) + fsize = os.path.getsize(filename) + headers['Content-Length'] = fsize data = None else: if file is None: file = open(filename, 'rb') mtime = os.path.getmtime(filename) - headers['Content-Length'] = os.path.getsize(filename) + fsize = os.path.getsize(filename) + headers['Content-Length'] = fsize data = wrap_file(request.environ, file) rv = current_app.response_class(data, mimetype=mimetype, headers=headers, @@ -575,12 +583,22 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, warn('Access %s failed, maybe it does not exist, so ignore etags in ' 'headers' % filename, stacklevel=2) - if conditional: + if conditional: + if callable(getattr(Range, 'to_content_range_header', None)): + # Werkzeug supports Range Requests + # Remove this test when support for Werkzeug <0.12 is dropped + try: + rv = rv.make_conditional(request, accept_ranges=True, + complete_length=fsize) + except RequestedRangeNotSatisfiable: + file.close() + raise + else: rv = rv.make_conditional(request) - # make sure we don't send x-sendfile for servers that - # ignore the 304 status code for x-sendfile. - if rv.status_code == 304: - rv.headers.pop('x-sendfile', None) + # make sure we don't send x-sendfile for servers that + # ignore the 304 status code for x-sendfile. + if rv.status_code == 304: + rv.headers.pop('x-sendfile', None) return rv diff --git a/tests/test_helpers.py b/tests/test_helpers.py index e1469007..8348331b 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -14,8 +14,10 @@ import pytest import os import uuid import datetime + import flask from logging import StreamHandler +from werkzeug.datastructures import Range from werkzeug.exceptions import BadRequest, NotFound from werkzeug.http import parse_cache_control_header, parse_options_header from werkzeug.http import http_date @@ -462,6 +464,69 @@ class TestSendfile(object): assert 'x-sendfile' not in rv.headers rv.close() + @pytest.mark.skipif( + not callable(getattr(Range, 'to_content_range_header', None)), + reason="not implement within werkzeug" + ) + def test_send_file_range_request(self): + app = flask.Flask(__name__) + + @app.route('/') + def index(): + return flask.send_file('static/index.html', conditional=True) + + c = app.test_client() + + rv = c.get('/', headers={'Range': 'bytes=4-15'}) + assert rv.status_code == 206 + with app.open_resource('static/index.html') as f: + assert rv.data == f.read()[4:16] + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=4-'}) + assert rv.status_code == 206 + with app.open_resource('static/index.html') as f: + assert rv.data == f.read()[4:] + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=4-1000'}) + assert rv.status_code == 206 + with app.open_resource('static/index.html') as f: + assert rv.data == f.read()[4:] + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=-10'}) + assert rv.status_code == 206 + with app.open_resource('static/index.html') as f: + assert rv.data == f.read()[-10:] + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=1000-'}) + assert rv.status_code == 416 + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=-'}) + assert rv.status_code == 416 + rv.close() + + rv = c.get('/', headers={'Range': 'somethingsomething'}) + assert rv.status_code == 416 + rv.close() + + last_modified = datetime.datetime.fromtimestamp(os.path.getmtime( + os.path.join(app.root_path, 'static/index.html'))).replace( + microsecond=0) + + rv = c.get('/', headers={'Range': 'bytes=4-15', + 'If-Range': http_date(last_modified)}) + assert rv.status_code == 206 + rv.close() + + rv = c.get('/', headers={'Range': 'bytes=4-15', 'If-Range': http_date( + datetime.datetime(1999, 1, 1))}) + assert rv.status_code == 200 + rv.close() + def test_attachment(self): app = flask.Flask(__name__) with app.test_request_context(): From d544a46a7de1c238329031165affc7c9fb3bfeae Mon Sep 17 00:00:00 2001 From: Michael Recachinas Date: Sat, 1 Oct 2016 12:45:22 -0400 Subject: [PATCH 052/153] Remove `-a/--app` from Quickstart documentation (#2046) * Remove `-a/--app` from Quickstart documentation As mentioned in #2009, simplifying the CLI saw the removal of the `-a/--app` flag. Therefore, the only way to specify the module to import is by setting `FLASK_APP`. * Remove misleading `either` from CLI help The CLI help details how to run the application, but still uses the phrasing "either through the `FLASK_APP`...". This likely is an artifact from when `-a/--app` was still present in the CLI. --- docs/quickstart.rst | 8 ++++---- flask/cli.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index c822db26..b444e080 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -102,10 +102,10 @@ docs to see the alternative method for running a server. Invalid Import Name ``````````````````` -The ``-a`` argument to :command:`flask` is the name of the module to -import. In case that module is incorrectly named you will get an import -error upon start (or if debug is enabled when you navigate to the -application). It will tell you what it tried to import and why it failed. +The ``FLASK_APP`` environment variable is the name of the module to import at +:command:`flask run`. In case that module is incorrectly named you will get an +import error upon start (or if debug is enabled when you navigate to the +application). It will tell you what it tried to import and why it failed. The most common reason is a typo or because you did not actually create an ``app`` object. diff --git a/flask/cli.py b/flask/cli.py index 28818515..6c8cf32d 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -469,7 +469,7 @@ def shell_command(): cli = FlaskGroup(help="""\ This shell command acts as general utility script for Flask applications. -It loads the application configured (either through the FLASK_APP environment +It loads the application configured (through the FLASK_APP environment variable) and then provides commands either provided by the application or Flask itself. From bcd54327611eff696b03c4472d8ee75f5540eca8 Mon Sep 17 00:00:00 2001 From: Hassam Date: Sat, 8 Oct 2016 13:34:56 -0500 Subject: [PATCH 053/153] Fix #2051: Fix flaskr import in flaskr/__init__.py (#2052) --- examples/flaskr/flaskr/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/flaskr/flaskr/__init__.py b/examples/flaskr/flaskr/__init__.py index 14a36539..37a7b5cc 100644 --- a/examples/flaskr/flaskr/__init__.py +++ b/examples/flaskr/flaskr/__init__.py @@ -1 +1 @@ -from flaskr import app \ No newline at end of file +from flaskr.flaskr import app \ No newline at end of file From 3f6ddf9cfd87959ba8221049a2e35a4ebf7a6fea Mon Sep 17 00:00:00 2001 From: Michael Recachinas Date: Wed, 12 Oct 2016 02:54:25 -0400 Subject: [PATCH 054/153] Default environ (#2047) * Add init to FlaskClient This addresses #1467. The init in the subclass can now take in `environ_base`, which will then get passed to `make_test_environ_builder` and to `EnvironBuilder` via keyword args. This should provide the default environment capability on `app.test_client()` init. * Add kwarg `environ_base` to `make_test_environ_builder` call This change now passes `environ_base` from either `kwargs` in `FlaskClient.open` or `FlaskClient.environ_base` if passed into the init. * Fix assignment reference typo * Add default `environ_base` to `FlaskClient.__init__` * Set default kwargs for `environ_base` in `FlaskClient.open` * Remove specific environ_base kwarg since its in kwargs * Add docstring to FlaskClient detailing environ_base * Document app.test_client default environ in CHANGES * Re-word environ_base changes in FlaskClient docstring * Add client.environ_base tests * Mention preset default environ in `app.test_client` * Add versionchanged directive to docstring in FlaskClient --- CHANGES | 2 ++ flask/testing.py | 14 ++++++++++++++ tests/test_testing.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/CHANGES b/CHANGES index 5a1f5a33..13ce156c 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,8 @@ Version 0.12 well as error handlers. - Disable logger propagation by default for the app logger. - Add support for range requests in ``send_file``. +- ``app.test_client`` includes preset default environment, which can now be + directly set, instead of per ``client.get``. Version 0.11.2 -------------- diff --git a/flask/testing.py b/flask/testing.py index 8eacf58b..31600245 100644 --- a/flask/testing.py +++ b/flask/testing.py @@ -10,6 +10,7 @@ :license: BSD, see LICENSE for more details. """ +import werkzeug from contextlib import contextmanager from werkzeug.test import Client, EnvironBuilder from flask import _request_ctx_stack @@ -43,11 +44,23 @@ class FlaskClient(Client): information about how to use this class refer to :class:`werkzeug.test.Client`. + .. versionchanged:: 0.12 + `app.test_client()` includes preset default environment, which can be + set after instantiation of the `app.test_client()` object in + `client.environ_base`. + Basic usage is outlined in the :ref:`testing` chapter. """ preserve_context = False + def __init__(self, *args, **kwargs): + super(FlaskClient, self).__init__(*args, **kwargs) + self.environ_base = { + "REMOTE_ADDR": "127.0.0.1", + "HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__ + } + @contextmanager def session_transaction(self, *args, **kwargs): """When used in combination with a ``with`` statement this opens a @@ -101,6 +114,7 @@ class FlaskClient(Client): def open(self, *args, **kwargs): kwargs.setdefault('environ_overrides', {}) \ ['flask._preserve_context'] = self.preserve_context + kwargs.setdefault('environ_base', self.environ_base) as_tuple = kwargs.pop('as_tuple', False) buffered = kwargs.pop('buffered', False) diff --git a/tests/test_testing.py b/tests/test_testing.py index 7bb99e79..9d353904 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -11,6 +11,7 @@ import pytest import flask +import werkzeug from flask._compat import text_type @@ -43,6 +44,40 @@ def test_environ_defaults(): rv = c.get('/') assert rv.data == b'http://localhost/' +def test_environ_base_default(): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + flask.g.user_agent = flask.request.headers["User-Agent"] + return flask.request.remote_addr + + with app.test_client() as c: + rv = c.get('/') + assert rv.data == b'127.0.0.1' + assert flask.g.user_agent == 'werkzeug/' + werkzeug.__version__ + +def test_environ_base_modified(): + app = flask.Flask(__name__) + app.testing = True + @app.route('/') + def index(): + flask.g.user_agent = flask.request.headers["User-Agent"] + return flask.request.remote_addr + + with app.test_client() as c: + c.environ_base['REMOTE_ADDR'] = '0.0.0.0' + c.environ_base['HTTP_USER_AGENT'] = 'Foo' + rv = c.get('/') + assert rv.data == b'0.0.0.0' + assert flask.g.user_agent == 'Foo' + + c.environ_base['REMOTE_ADDR'] = '0.0.0.1' + c.environ_base['HTTP_USER_AGENT'] = 'Bar' + rv = c.get('/') + assert rv.data == b'0.0.0.1' + assert flask.g.user_agent == 'Bar' + def test_redirect_keep_session(): app = flask.Flask(__name__) app.secret_key = 'testing' From 4f23ea06c59bbda73b17ea4a6bd873f0de58a822 Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 12 Oct 2016 12:14:49 -0700 Subject: [PATCH 055/153] Windows venv is Scripts, capital S closes #2056 --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 91d95270..6f833eac 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -72,7 +72,7 @@ corresponding environment. On OS X and Linux, do the following:: If you are a Windows user, the following command is for you:: - $ venv\scripts\activate + $ venv\Scripts\activate Either way, you should now be using your virtualenv (notice how the prompt of your shell has changed to show the active environment). From b789ca35d52cb8dea4e0c1a9f17aa93d82a37351 Mon Sep 17 00:00:00 2001 From: Cody Date: Fri, 14 Oct 2016 04:13:42 -0400 Subject: [PATCH 056/153] add 'caution' section to docs, workaround for zero-padded file modes (#2057) Fix #2029 --- CONTRIBUTING.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ca7b4af2..d9cd2214 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -87,3 +87,28 @@ Generate a HTML report can be done using this command:: Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io +Caution +======= +pushing +------- +This repository contains several zero-padded file modes that may cause issues when pushing this repository to git hosts other than github. Fixing this is destructive to the commit history, so we suggest ignoring these warnings. If it fails to push and you're using a self-hosted git service like Gitlab, you can turn off repository checks in the admin panel. + + +cloning +------- +The zero-padded file modes files above can cause issues while cloning, too. If you have + +:: + + [fetch] + fsckobjects = true + +or + +:: + + [receive] + fsckObjects = true + + +set in your git configuration file, cloning this repository will fail. The only solution is to set both of the above settings to false while cloning, and then setting them back to true after the cloning is finished. From 8c205c077d7ab4e0da085943e71bac46343bba3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionu=C8=9B=20Cioc=C3=AErlan?= Date: Mon, 24 Oct 2016 14:20:54 +0300 Subject: [PATCH 057/153] Fix grammar in docs --- docs/becomingbig.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/becomingbig.rst b/docs/becomingbig.rst index df470a76..0facbfee 100644 --- a/docs/becomingbig.rst +++ b/docs/becomingbig.rst @@ -12,7 +12,7 @@ Flask started in part to demonstrate how to build your own framework on top of existing well-used tools Werkzeug (WSGI) and Jinja (templating), and as it developed, it became useful to a wide audience. As you grow your codebase, don't just use Flask -- understand it. Read the source. Flask's code is -written to be read; it's documentation is published so you can use its internal +written to be read; its documentation is published so you can use its internal APIs. Flask sticks to documented APIs in upstream libraries, and documents its internal utilities so that you can find the hook points needed for your project. From 02cab299c392d1e06e3e3a688dec31e25e184028 Mon Sep 17 00:00:00 2001 From: Kyle Lawlor Date: Sun, 30 Oct 2016 09:34:49 -0400 Subject: [PATCH 058/153] Fixes import statement in flaskr (#2068) - `from flaskr.flaskr import app` in flaskr/__init__.py causes an import error with Python 2 - The relative import now used works for py2 and py3 --- docs/tutorial/packaging.rst | 2 +- examples/flaskr/flaskr/__init__.py | 2 +- examples/flaskr/setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tutorial/packaging.rst b/docs/tutorial/packaging.rst index 8db6531e..18f5c9b3 100644 --- a/docs/tutorial/packaging.rst +++ b/docs/tutorial/packaging.rst @@ -55,7 +55,7 @@ into this file, :file:`flaskr/__init__.py`: .. sourcecode:: python - from flaskr import app + from .flaskr import app This import statement brings the application instance into the top-level of the application package. When it is time to run the application, the diff --git a/examples/flaskr/flaskr/__init__.py b/examples/flaskr/flaskr/__init__.py index 14a36539..3f782479 100644 --- a/examples/flaskr/flaskr/__init__.py +++ b/examples/flaskr/flaskr/__init__.py @@ -1 +1 @@ -from flaskr import app \ No newline at end of file +from .flaskr import app \ No newline at end of file diff --git a/examples/flaskr/setup.cfg b/examples/flaskr/setup.cfg index b7e47898..db50667a 100644 --- a/examples/flaskr/setup.cfg +++ b/examples/flaskr/setup.cfg @@ -1,2 +1,2 @@ -[aliases] +[tool:pytest] test=pytest From 24e731e9ff659bbc89f4c54113303ee10def8f6b Mon Sep 17 00:00:00 2001 From: Clenimar Filemon Date: Mon, 31 Oct 2016 13:41:38 -0300 Subject: [PATCH 059/153] Update docstring for errorhandler() (#2070) --- flask/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 59c77a15..68de2b90 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1153,7 +1153,8 @@ class Flask(_PackageBoundObject): that do not necessarily have to be a subclass of the :class:`~werkzeug.exceptions.HTTPException` class. - :param code: the code as integer for the handler + :param code_or_exception: the code as integer for the handler, or + an arbitrary exception """ def decorator(f): self._register_error_handler(None, code_or_exception, f) From fbcba68f5fb05299b459236d2d501e33b79af3c6 Mon Sep 17 00:00:00 2001 From: Alex Kahan Date: Mon, 31 Oct 2016 18:10:27 -0400 Subject: [PATCH 060/153] Adding coverage generation to tox (#2071) * Adding coverage generation to tox * Removing test directory from coverage command * Adding back to pytest command --- .gitignore | 6 ++++++ tox.ini | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9bf4f063..fb9baf35 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ _mailinglist .tox .cache/ .idea/ + +# Coverage reports +htmlcov +.coverage +.coverage.* +*,cover diff --git a/tox.ini b/tox.ini index 28942d3e..57725d0a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,11 +4,12 @@ envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35 [testenv] +usedevelop=true commands = - py.test [] - + py.test [] --cov=flask --cov-report html deps= pytest + pytest-cov greenlet lowest: Werkzeug==0.7 From 3e0cb3d3be163bee9df89d3107e5d471a5b81850 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Tue, 1 Nov 2016 14:35:17 +0000 Subject: [PATCH 061/153] Remove busy-work. (#2072) It is entirely sufficient to walk the MRO of the exception class, no need to check for classes re-appearing later on, no need to add the MRO of any superclass. * Python refuses point-blank to create a class with a circular MRO. * All classes in a superclass MRO *already* appear in the MRO of the derived type. Re-adding the contents of a superclass MRO is doing double work. --- flask/app.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/flask/app.py b/flask/app.py index 68de2b90..942992dc 100644 --- a/flask/app.py +++ b/flask/app.py @@ -14,7 +14,6 @@ from threading import Lock from datetime import timedelta from itertools import chain from functools import update_wrapper -from collections import deque from werkzeug.datastructures import ImmutableDict from werkzeug.routing import Map, Rule, RequestRedirect, BuildError @@ -1437,24 +1436,13 @@ class Flask(_PackageBoundObject): def find_handler(handler_map): if not handler_map: return - queue = deque(exc_class.__mro__) - # Protect from geniuses who might create circular references in - # __mro__ - done = set() - - while queue: - cls = queue.popleft() - if cls in done: - continue - done.add(cls) + for cls in exc_class.__mro__: handler = handler_map.get(cls) if handler is not None: # cache for next time exc_class is raised handler_map[exc_class] = handler return handler - queue.extend(cls.__mro__) - # try blueprint handlers handler = find_handler(self.error_handler_spec .get(request.blueprint, {}) From d631385f024a6e7563ed44e6803659da54c0ac51 Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Tue, 1 Nov 2016 13:11:53 -0700 Subject: [PATCH 062/153] Add license_file to setup.cfg metadata (#2024) Without this, the LICENSE file is never included in the built wheels: this makes it harder for users to comply with the license. With this addition a file LICENSE.txt will be created in the `xxx.dist-info` directory with the content of the `license_file` file, e.g. the top level LICENSE. --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index 34414b3e..0e97b0ba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,5 +4,8 @@ release = egg_info -RDb '' [wheel] universal = 1 +[metadata] +license_file = LICENSE + [tool:pytest] norecursedirs = .* *.egg *.egg-info env* artwork docs examples From 562656af0ac8f723ed7febbf0d31b7d9bc759520 Mon Sep 17 00:00:00 2001 From: Clenimar Filemon Date: Tue, 1 Nov 2016 22:52:32 -0300 Subject: [PATCH 063/153] Capitalize occurrences of 'flask' (#2067) --- flask/__init__.py | 2 +- flask/cli.py | 4 ++-- flask/sessions.py | 2 +- flask/signals.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flask/__init__.py b/flask/__init__.py index 509b944f..b1360e84 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -40,7 +40,7 @@ from .signals import signals_available, template_rendered, request_started, \ # it. from . import json -# This was the only thing that flask used to export at one point and it had +# This was the only thing that Flask used to export at one point and it had # a more generic name. jsonify = json.jsonify diff --git a/flask/cli.py b/flask/cli.py index 6c8cf32d..da988e80 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -131,9 +131,9 @@ version_option = click.Option(['--version'], is_flag=True, is_eager=True) class DispatchingApp(object): - """Special application that dispatches to a flask application which + """Special application that dispatches to a Flask application which is imported by name in a background thread. If an error happens - it is is recorded and shows as part of the WSGI handling which in case + it is recorded and shown as part of the WSGI handling which in case of the Werkzeug debugger means that it shows up in the browser. """ diff --git a/flask/sessions.py b/flask/sessions.py index b9120712..4d67658a 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -168,7 +168,7 @@ class SessionInterface(object): null_session_class = NullSession #: A flag that indicates if the session interface is pickle based. - #: This can be used by flask extensions to make a decision in regards + #: This can be used by Flask extensions to make a decision in regards #: to how to deal with the session object. #: #: .. versionadded:: 0.10 diff --git a/flask/signals.py b/flask/signals.py index c9b8a210..dd52cdb5 100644 --- a/flask/signals.py +++ b/flask/signals.py @@ -37,7 +37,7 @@ except ImportError: temporarily_connected_to = connected_to = _fail del _fail -# The namespace for code signals. If you are not flask code, do +# The namespace for code signals. If you are not Flask code, do # not put signals in here. Create your own namespace instead. _signals = Namespace() From 816939e907685e9ad254db7027702dc0e22795ed Mon Sep 17 00:00:00 2001 From: Shandy Brown Date: Tue, 1 Nov 2016 18:52:54 -0700 Subject: [PATCH 064/153] Correct grammar (#2061) --- docs/patterns/sqlalchemy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index 40e048e0..e8215317 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -135,7 +135,7 @@ Here is an example :file:`database.py` module for your application:: def init_db(): metadata.create_all(bind=engine) -As for the declarative approach you need to close the session after +As in the declarative approach, you need to close the session after each request or application context shutdown. Put this into your application module:: From 49613fe775bf09af891fef9772df5352afeb878f Mon Sep 17 00:00:00 2001 From: Tery Lim Date: Wed, 2 Nov 2016 15:58:22 +1300 Subject: [PATCH 065/153] Update errorhandling.rst (#2074) --- docs/errorhandling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 3bda5f15..18493c67 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -216,7 +216,7 @@ A formatter can be instantiated with a format string. Note that tracebacks are appended to the log entry automatically. You don't have to do that in the log formatter format string. -Here some example setups: +Here are some example setups: Email ````` From bbe09aba26d1b41090659dd595b042c1fda59a22 Mon Sep 17 00:00:00 2001 From: Tery Lim Date: Wed, 2 Nov 2016 17:04:20 +1300 Subject: [PATCH 066/153] Update errorhandling.rst (#2075) Fix LogRecord class reference. --- docs/errorhandling.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 18493c67..64c0f8b3 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -276,8 +276,9 @@ that this list is not complete, consult the official documentation of the | ``%(lineno)d`` | Source line number where the logging call was | | | issued (if available). | +------------------+----------------------------------------------------+ -| ``%(asctime)s`` | Human-readable time when the LogRecord` was | -| | created. By default this is of the form | +| ``%(asctime)s`` | Human-readable time when the | +| | :class:`~logging.LogRecord` was created. | +| | By default this is of the form | | | ``"2003-07-08 16:49:45,896"`` (the numbers after | | | the comma are millisecond portion of the time). | | | This can be changed by subclassing the formatter | From f31af2351b3fb7a2ee0a747d25409579ed9405a7 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 2 Nov 2016 17:56:59 +0100 Subject: [PATCH 067/153] Use tox from make test --- Makefile | 7 ++----- setup.cfg | 2 +- test-requirements.txt | 2 +- tox.ini | 6 +++++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 9bcdebc2..f76c2099 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,8 @@ all: clean-pyc test test: - pip install -r test-requirements.txt -q - FLASK_DEBUG= py.test tests examples - -tox-test: - tox + pip install -r test-requirements.txt + tox -e py-release audit: python setup.py audit diff --git a/setup.cfg b/setup.cfg index 0e97b0ba..cd282e48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,4 +8,4 @@ universal = 1 license_file = LICENSE [tool:pytest] -norecursedirs = .* *.egg *.egg-info env* artwork docs examples +norecursedirs = .* *.egg *.egg-info env* artwork docs diff --git a/test-requirements.txt b/test-requirements.txt index e079f8a6..053148f8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1 @@ -pytest +tox diff --git a/tox.ini b/tox.ini index 57725d0a..406de5dd 100644 --- a/tox.ini +++ b/tox.ini @@ -4,9 +4,13 @@ envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35 [testenv] +passenv = LANG usedevelop=true commands = - py.test [] --cov=flask --cov-report html + # We need to install those after Flask is installed. + pip install -e examples/flaskr + pip install -e examples/minitwit + py.test --cov=flask --cov-report html [] deps= pytest pytest-cov From 279a925ab8e89304d96465c5cc63a80697659305 Mon Sep 17 00:00:00 2001 From: Alex Kahan Date: Thu, 3 Nov 2016 13:11:24 -0400 Subject: [PATCH 068/153] Parameterizing test (#2073) --- tests/test_helpers.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 8348331b..69fbaf3b 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -113,20 +113,17 @@ class TestJSON(object): rv = flask.json.load(out) assert rv == test_data - def test_jsonify_basic_types(self): + @pytest.mark.parametrize('test_value', [0, -1, 1, 23, 3.14, 's', "longer string", True, False, None]) + def test_jsonify_basic_types(self, test_value): """Test jsonify with basic types.""" - # Should be able to use pytest parametrize on this, but I couldn't - # figure out the correct syntax - # https://pytest.org/latest/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions - test_data = (0, 1, 23, 3.14, 's', "longer string", True, False,) app = flask.Flask(__name__) c = app.test_client() - for i, d in enumerate(test_data): - url = '/jsonify_basic_types{0}'.format(i) - app.add_url_rule(url, str(i), lambda x=d: flask.jsonify(x)) - rv = c.get(url) - assert rv.mimetype == 'application/json' - assert flask.json.loads(rv.data) == d + + url = '/jsonify_basic_types' + app.add_url_rule(url, url, lambda x=test_value: flask.jsonify(x)) + rv = c.get(url) + assert rv.mimetype == 'application/json' + assert flask.json.loads(rv.data) == test_value def test_jsonify_dicts(self): """Test jsonify with dicts and kwargs unpacking.""" @@ -170,12 +167,10 @@ class TestJSON(object): def test_jsonify_date_types(self): """Test jsonify with datetime.date and datetime.datetime types.""" - test_dates = ( datetime.datetime(1973, 3, 11, 6, 30, 45), datetime.date(1975, 1, 5) ) - app = flask.Flask(__name__) c = app.test_client() @@ -189,8 +184,7 @@ class TestJSON(object): def test_jsonify_uuid_types(self): """Test jsonify with uuid.UUID types""" - test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF'*4) - + test_uuid = uuid.UUID(bytes=b'\xDE\xAD\xBE\xEF' * 4) app = flask.Flask(__name__) url = '/uuid_test' app.add_url_rule(url, url, lambda: flask.jsonify(x=test_uuid)) From 9e309d1b0cbcdbd80598d15b9039e21b22063fdc Mon Sep 17 00:00:00 2001 From: Giles Thomas Date: Mon, 7 Nov 2016 18:10:02 +0000 Subject: [PATCH 069/153] Added a link to instructions for PythonAnywhere (#2081) --- docs/deploying/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 5d88cf72..95e96bf2 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -23,6 +23,7 @@ Hosted options - `Deploying Flask on Google App Engine `_ - `Sharing your Localhost Server with Localtunnel `_ - `Deploying on Azure (IIS) `_ +- `Deploying on PythonAnywhere `_ Self-hosted options ------------------- From 8d03e0376493df4a62ed4c2919b19e6f440dbb69 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Tue, 15 Nov 2016 11:57:09 +0100 Subject: [PATCH 070/153] Fix rST rendering of env var (#2085) This was broken in https://github.com/pallets/flask/commit/ad011bc32d7b9160354efafcd43e20f7042a6a13#diff-fd40cf2be7711772de9d8316da038cceR263 --- docs/config.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 89fa0924..6d37c1e8 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -262,7 +262,7 @@ So a common pattern is this:: This first loads the configuration from the `yourapplication.default_settings` module and then overrides the values -with the contents of the file the :envvar:``YOURAPPLICATION_SETTINGS`` +with the contents of the file the :envvar:`YOURAPPLICATION_SETTINGS` environment variable points to. This environment variable can be set on Linux or OS X with the export command in the shell before starting the server:: From 0aa7ebd1e921029d9454687b3c65899545503843 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 15 Nov 2016 11:56:51 +0100 Subject: [PATCH 071/153] Fix import error --- examples/minitwit/minitwit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/minitwit/minitwit/__init__.py b/examples/minitwit/minitwit/__init__.py index 0b8bd697..96c81aec 100644 --- a/examples/minitwit/minitwit/__init__.py +++ b/examples/minitwit/minitwit/__init__.py @@ -1 +1 @@ -from minitwit import app \ No newline at end of file +from .minitwit import app From 7f0240cabc40f4314118b58531ff967ee1ddbe2a Mon Sep 17 00:00:00 2001 From: ezramorris Date: Thu, 17 Nov 2016 14:01:30 +0000 Subject: [PATCH 072/153] Add link to AWS EB Flask tutorial --- docs/deploying/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 95e96bf2..20e71762 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -21,6 +21,7 @@ Hosted options - `Deploying Flask on OpenShift `_ - `Deploying Flask on Webfaction `_ - `Deploying Flask on Google App Engine `_ +- `Deploying Flask on AWS Elastic Beanstalk `_ - `Sharing your Localhost Server with Localtunnel `_ - `Deploying on Azure (IIS) `_ - `Deploying on PythonAnywhere `_ From 5d4e95735ecdbe9232e4d9785855ca29352e8076 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Mon, 19 Dec 2016 14:37:34 +0100 Subject: [PATCH 073/153] Remove wrong comma (#2116) --- docs/patterns/appfactories.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index dc9660ae..c118a273 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -6,7 +6,7 @@ Application Factories If you are already using packages and blueprints for your application (:ref:`blueprints`) there are a couple of really nice ways to further improve the experience. A common pattern is creating the application object when -the blueprint is imported. But if you move the creation of this object, +the blueprint is imported. But if you move the creation of this object into a function, you can then create multiple instances of this app later. So why would you want to do this? From 41d4c60e0b2d2e613cfa1afb9f638eb360bc44dd Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Wed, 21 Dec 2016 21:06:48 +0100 Subject: [PATCH 074/153] Style the flask command consistently (#2120) It's done like this in other parts of this doc. --- docs/cli.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 7ddf50f3..2ca0e83e 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -139,8 +139,8 @@ This could be a file named :file:`autoapp.py` with these contents:: from yourapplication import create_app app = create_app(os.environ['YOURAPPLICATION_CONFIG']) -Once this has happened you can make the flask command automatically pick -it up:: +Once this has happened you can make the :command:`flask` command automatically +pick it up:: export YOURAPPLICATION_CONFIG=/path/to/config.cfg export FLASK_APP=/path/to/autoapp.py From 755401d5dccac50d3bbc925ee4fbb2bff6384818 Mon Sep 17 00:00:00 2001 From: Hopsken Date: Thu, 22 Dec 2016 04:07:09 +0800 Subject: [PATCH 075/153] Update README for minitwit (#2119) add step 2 to run minitwit --- examples/minitwit/README | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/minitwit/README b/examples/minitwit/README index 4561d836..b9bc5ea2 100644 --- a/examples/minitwit/README +++ b/examples/minitwit/README @@ -14,15 +14,19 @@ export an MINITWIT_SETTINGS environment variable pointing to a configuration file. - 2. tell flask about the right application: + 2. install the app from the root of the project directory + + pip install --editable . + + 3. tell flask about the right application: export FLASK_APP=minitwit - 2. fire up a shell and run this: + 4. fire up a shell and run this: flask initdb - 3. now you can run minitwit: + 5. now you can run minitwit: flask run From 46efe748055ddbac074757cd28ac26516712aca2 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Wed, 21 Dec 2016 12:07:57 -0800 Subject: [PATCH 076/153] use dict instead of if/else logic (#2093) --- flask/sessions.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/flask/sessions.py b/flask/sessions.py index 4d67658a..525ff246 100644 --- a/flask/sessions.py +++ b/flask/sessions.py @@ -84,21 +84,25 @@ class TaggedJSONSerializer(object): def dumps(self, value): return json.dumps(_tag(value), separators=(',', ':')) + LOADS_MAP = { + ' t': tuple, + ' u': uuid.UUID, + ' b': b64decode, + ' m': Markup, + ' d': parse_date, + } + def loads(self, value): def object_hook(obj): if len(obj) != 1: return obj the_key, the_value = next(iteritems(obj)) - if the_key == ' t': - return tuple(the_value) - elif the_key == ' u': - return uuid.UUID(the_value) - elif the_key == ' b': - return b64decode(the_value) - elif the_key == ' m': - return Markup(the_value) - elif the_key == ' d': - return parse_date(the_value) + # Check the key for a corresponding function + return_function = self.LOADS_MAP.get(the_key) + if return_function: + # Pass the value to the function + return return_function(the_value) + # Didn't find a function for this object return obj return json.loads(value, object_hook=object_hook) From cb001d3ad0caec4460eabfb977784a07554c1d4c Mon Sep 17 00:00:00 2001 From: Jiri Kuncar Date: Wed, 21 Dec 2016 21:08:38 +0100 Subject: [PATCH 077/153] Ignore cache on request.get_json(cache=False) call (#2089) * Test cache argument of Request.get_json * Ignore cache on request.get_json(cache=False) call Removes usage of `_cached_json` property when `get_json` is called with disabled cache argument. (closes #2087) --- flask/wrappers.py | 3 ++- tests/test_helpers.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/flask/wrappers.py b/flask/wrappers.py index d1d7ba7d..04bdcb5d 100644 --- a/flask/wrappers.py +++ b/flask/wrappers.py @@ -137,7 +137,8 @@ class Request(RequestBase): on the request. """ rv = getattr(self, '_cached_json', _missing) - if rv is not _missing: + # We return cached JSON only when the cache is enabled. + if cache and rv is not _missing: return rv if not (force or self.is_json): diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 69fbaf3b..3e2ea8cd 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -35,6 +35,14 @@ def has_encoding(name): class TestJSON(object): + def test_ignore_cached_json(self): + app = flask.Flask(__name__) + with app.test_request_context('/', method='POST', data='malformed', + content_type='application/json'): + assert flask.request.get_json(silent=True, cache=True) is None + with pytest.raises(BadRequest): + flask.request.get_json(silent=False, cache=False) + def test_post_empty_json_adds_exception_to_response_content_in_debug(self): app = flask.Flask(__name__) app.config['DEBUG'] = True From 09f5dcfdc678b96c05f0e432036fbdef2bcbb017 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 21 Dec 2016 21:19:53 +0100 Subject: [PATCH 078/153] Version 0.12 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 13ce156c..73e78128 100644 --- a/CHANGES +++ b/CHANGES @@ -6,6 +6,8 @@ Here you can see the full list of changes between each Flask release. Version 0.12 ------------ +Released on December 21st 2016, codename Punsch. + - the cli command now responds to `--version`. - Mimetype guessing and ETag generation for file-like objects in ``send_file`` has been removed, as per issue ``#104``. See pull request ``#1849``. From 1a37924e5a9a9a94e4bcc17f4b6f0dfbc52b354a Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 21 Dec 2016 21:22:08 +0100 Subject: [PATCH 079/153] Bump version number to 0.12 --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index b1360e84..69f29fb4 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.11.2-dev' +__version__ = '0.12' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From b10c4c69c89553f977b30b4b81c625518a52b040 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Wed, 21 Dec 2016 21:22:26 +0100 Subject: [PATCH 080/153] Bump to dev version --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 69f29fb4..bb6c4c18 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12' +__version__ = '0.13-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From afb013e2606a1ff4ab7a3d6004b1e01a5ecd3815 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 25 Dec 2016 16:33:55 +0100 Subject: [PATCH 081/153] Changelog stub for 0.12.1 --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 73e78128..317bd489 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,11 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.12.1 +-------------- + +Bugfix release, unreleased + Version 0.12 ------------ From 8da8bd1f62c616b93f5ffe55c5b6e6baac7eed6a Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sun, 25 Dec 2016 16:34:22 +0100 Subject: [PATCH 082/153] Bump to dev 0.12.1 --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 69f29fb4..3cef3b43 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12' +__version__ = '0.12.1-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From 5512e424b67cab9bbf5345f66f7d46410788da4d Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 26 Dec 2016 03:50:47 +0100 Subject: [PATCH 083/153] Fix config.from_pyfile on Python 3 (#2123) * Fix config.from_pyfile on Python 3 Fix #2118 * Support Python 2.6 * Fix tests on Python 2 --- CHANGES | 3 +++ flask/config.py | 2 +- tests/test_config.py | 22 ++++++++++++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 317bd489..a0a423d1 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ Version 0.12.1 Bugfix release, unreleased +- Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix + ``#2118``. + Version 0.12 ------------ diff --git a/flask/config.py b/flask/config.py index 36e8a123..697add71 100644 --- a/flask/config.py +++ b/flask/config.py @@ -126,7 +126,7 @@ class Config(dict): d = types.ModuleType('config') d.__file__ = filename try: - with open(filename) as config_file: + with open(filename, mode='rb') as config_file: exec(compile(config_file.read(), filename, 'exec'), d.__dict__) except IOError as e: if silent and e.errno in (errno.ENOENT, errno.EISDIR): diff --git a/tests/test_config.py b/tests/test_config.py index 333a5cff..5c98db98 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -7,11 +7,14 @@ :license: BSD, see LICENSE for more details. """ -import pytest -import os from datetime import timedelta +import os +import textwrap + import flask +from flask._compat import PY2 +import pytest # config keys used for the TestConfig @@ -187,3 +190,18 @@ def test_get_namespace(): assert 2 == len(bar_options) assert 'bar stuff 1' == bar_options['BAR_STUFF_1'] assert 'bar stuff 2' == bar_options['BAR_STUFF_2'] + + +@pytest.mark.parametrize('encoding', ['utf-8', 'iso-8859-15', 'latin-1']) +def test_from_pyfile_weird_encoding(tmpdir, encoding): + f = tmpdir.join('my_config.py') + f.write_binary(textwrap.dedent(u''' + # -*- coding: {0} -*- + TEST_VALUE = "föö" + '''.format(encoding)).encode(encoding)) + app = flask.Flask(__name__) + app.config.from_pyfile(str(f)) + value = app.config['TEST_VALUE'] + if PY2: + value = value.decode(encoding) + assert value == u'föö' From d907eda370e7dadd24d0cbd27ee1ad2273ac1626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Lipt=C3=A1k?= Date: Wed, 28 Dec 2016 10:11:33 -0500 Subject: [PATCH 084/153] Update Flask-SQLAlchemy link (#2126) --- docs/patterns/sqlalchemy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index e8215317..9c985cc6 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -22,7 +22,7 @@ if you want to get started quickly. You can download `Flask-SQLAlchemy`_ from `PyPI `_. -.. _Flask-SQLAlchemy: http://pythonhosted.org/Flask-SQLAlchemy/ +.. _Flask-SQLAlchemy: http://flask-sqlalchemy.pocoo.org/ Declarative From 055708866b4b0c914d83179e5fdf8d8f97840b5f Mon Sep 17 00:00:00 2001 From: wgwz Date: Fri, 30 Dec 2016 12:43:31 -0500 Subject: [PATCH 085/153] Adds the largerapp from the docs as an example --- examples/largerapp/setup.py | 10 ++++++++++ examples/largerapp/yourapplication/__init__.py | 4 ++++ examples/largerapp/yourapplication/static/style.css | 0 .../largerapp/yourapplication/templates/index.html | 0 .../largerapp/yourapplication/templates/layout.html | 0 .../largerapp/yourapplication/templates/login.html | 0 examples/largerapp/yourapplication/views.py | 5 +++++ 7 files changed, 19 insertions(+) create mode 100644 examples/largerapp/setup.py create mode 100644 examples/largerapp/yourapplication/__init__.py create mode 100644 examples/largerapp/yourapplication/static/style.css create mode 100644 examples/largerapp/yourapplication/templates/index.html create mode 100644 examples/largerapp/yourapplication/templates/layout.html create mode 100644 examples/largerapp/yourapplication/templates/login.html create mode 100644 examples/largerapp/yourapplication/views.py diff --git a/examples/largerapp/setup.py b/examples/largerapp/setup.py new file mode 100644 index 00000000..eaf00f07 --- /dev/null +++ b/examples/largerapp/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup + +setup( + name='yourapplication', + packages=['yourapplication'], + include_package_data=True, + install_requires=[ + 'flask', + ], +) diff --git a/examples/largerapp/yourapplication/__init__.py b/examples/largerapp/yourapplication/__init__.py new file mode 100644 index 00000000..089d2937 --- /dev/null +++ b/examples/largerapp/yourapplication/__init__.py @@ -0,0 +1,4 @@ +from flask import Flask +app = Flask(__name__) + +import yourapplication.views \ No newline at end of file diff --git a/examples/largerapp/yourapplication/static/style.css b/examples/largerapp/yourapplication/static/style.css new file mode 100644 index 00000000..e69de29b diff --git a/examples/largerapp/yourapplication/templates/index.html b/examples/largerapp/yourapplication/templates/index.html new file mode 100644 index 00000000..e69de29b diff --git a/examples/largerapp/yourapplication/templates/layout.html b/examples/largerapp/yourapplication/templates/layout.html new file mode 100644 index 00000000..e69de29b diff --git a/examples/largerapp/yourapplication/templates/login.html b/examples/largerapp/yourapplication/templates/login.html new file mode 100644 index 00000000..e69de29b diff --git a/examples/largerapp/yourapplication/views.py b/examples/largerapp/yourapplication/views.py new file mode 100644 index 00000000..b112328e --- /dev/null +++ b/examples/largerapp/yourapplication/views.py @@ -0,0 +1,5 @@ +from yourapplication import app + +@app.route('/') +def index(): + return 'Hello World!' \ No newline at end of file From 767779920b503bfe03a59e897affe95388aad718 Mon Sep 17 00:00:00 2001 From: wgwz Date: Fri, 30 Dec 2016 12:50:13 -0500 Subject: [PATCH 086/153] Add reference to largerapp src in docs --- docs/patterns/packages.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index 1cd77974..d1780ca8 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -114,6 +114,10 @@ You should then end up with something like that:: login.html ... +If you find yourself stuck on something, feel free +to take a look at the source code for this example. +You'll find it located under ``flask/examples/largerapp``. + .. admonition:: Circular Imports Every Python programmer hates them, and yet we just added some: From 09a93c6a71877b85984afe00387a5b8c05cd2912 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 30 Dec 2016 22:28:43 +0100 Subject: [PATCH 087/153] Init 0.13 changelog --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index a0a423d1..91d4813b 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,11 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.13 +------------ + +Major release, unreleased + Version 0.12.1 -------------- From 74a0eee5a1dcc924f1e0b7b9488b72cb354fe4b9 Mon Sep 17 00:00:00 2001 From: Paul Brown Date: Fri, 30 Dec 2016 15:02:08 -0600 Subject: [PATCH 088/153] prevent NoAppException when ImportError occurs within imported module --- flask/cli.py | 14 ++++++++++---- tests/test_apps/cliapp/importerrorapp.py | 7 +++++++ tests/test_cli.py | 1 + 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 tests/test_apps/cliapp/importerrorapp.py diff --git a/flask/cli.py b/flask/cli.py index da988e80..074ee768 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -89,10 +89,16 @@ def locate_app(app_id): try: __import__(module) except ImportError: - raise NoAppException('The file/path provided (%s) does not appear to ' - 'exist. Please verify the path is correct. If ' - 'app is not on PYTHONPATH, ensure the extension ' - 'is .py' % module) + # Reraise the ImportError if it occurred within the imported module. + # Determine this by checking whether the trace has a depth > 1. + if sys.exc_info()[-1].tb_next: + raise + else: + raise NoAppException('The file/path provided (%s) does not appear' + ' to exist. Please verify the path is ' + 'correct. If app is not on PYTHONPATH, ' + 'ensure the extension is .py' % module) + mod = sys.modules[module] if app_obj is None: app = find_best_app(mod) diff --git a/tests/test_apps/cliapp/importerrorapp.py b/tests/test_apps/cliapp/importerrorapp.py new file mode 100644 index 00000000..fb87c9b1 --- /dev/null +++ b/tests/test_apps/cliapp/importerrorapp.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import, print_function + +from flask import Flask + +raise ImportError() + +testapp = Flask('testapp') diff --git a/tests/test_cli.py b/tests/test_cli.py index 18026a75..313a34d2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -83,6 +83,7 @@ def test_locate_app(test_apps): pytest.raises(NoAppException, locate_app, "notanpp.py") pytest.raises(NoAppException, locate_app, "cliapp/app") pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") + pytest.raises(ImportError, locate_app, "cliapp.importerrorapp") def test_find_default_import_path(test_apps, monkeypatch, tmpdir): From b2b61ecca99e844ddfcce0f6ec485191137cc77a Mon Sep 17 00:00:00 2001 From: Paul Brown Date: Fri, 30 Dec 2016 15:40:30 -0600 Subject: [PATCH 089/153] update change log --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index a0a423d1..03194421 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ Version 0.12.1 Bugfix release, unreleased +- Prevent `flask run` from showing a NoAppException when an ImportError occurs + within the imported application module. - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix ``#2118``. From 7ddad080d30648f557ac4a7cf587a4785cbfcc7a Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 31 Dec 2016 16:31:44 +0100 Subject: [PATCH 090/153] Inherit Werkzeug docs (#2135) Fix #2132 --- docs/api.rst | 58 +--------------------------------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index d77da3de..7da28dc8 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -30,61 +30,12 @@ Incoming Request Data .. autoclass:: Request :members: - - .. attribute:: form - - A :class:`~werkzeug.datastructures.MultiDict` with the parsed form data from ``POST`` - or ``PUT`` requests. Please keep in mind that file uploads will not - end up here, but instead in the :attr:`files` attribute. - - .. attribute:: args - - A :class:`~werkzeug.datastructures.MultiDict` with the parsed contents of the query - string. (The part in the URL after the question mark). - - .. attribute:: values - - A :class:`~werkzeug.datastructures.CombinedMultiDict` with the contents of both - :attr:`form` and :attr:`args`. - - .. attribute:: cookies - - A :class:`dict` with the contents of all cookies transmitted with - the request. - - .. attribute:: stream - - If the incoming form data was not encoded with a known mimetype - the data is stored unmodified in this stream for consumption. Most - of the time it is a better idea to use :attr:`data` which will give - you that data as a string. The stream only returns the data once. - - .. attribute:: headers - - The incoming request headers as a dictionary like object. - - .. attribute:: data - - Contains the incoming request data as string in case it came with - a mimetype Flask does not handle. - - .. attribute:: files - - A :class:`~werkzeug.datastructures.MultiDict` with files uploaded as part of a - ``POST`` or ``PUT`` request. Each file is stored as - :class:`~werkzeug.datastructures.FileStorage` object. It basically behaves like a - standard file object you know from Python, with the difference that - it also has a :meth:`~werkzeug.datastructures.FileStorage.save` function that can - store the file on the filesystem. + :inherited-members: .. attribute:: environ The underlying WSGI environment. - .. attribute:: method - - The current request method (``POST``, ``GET`` etc.) - .. attribute:: path .. attribute:: full_path .. attribute:: script_root @@ -114,13 +65,6 @@ Incoming Request Data `url_root` ``u'http://www.example.com/myapplication/'`` ============= ====================================================== - .. attribute:: is_xhr - - ``True`` if the request was triggered via a JavaScript - `XMLHttpRequest`. This only works with libraries that support the - ``X-Requested-With`` header and set it to `XMLHttpRequest`. - Libraries that do that are prototype, jQuery and Mochikit and - probably some more. .. class:: request From cc9b3c60da48c41ffc5043a5d06854dc0c9cd2f1 Mon Sep 17 00:00:00 2001 From: wgwz Date: Sat, 31 Dec 2016 12:08:25 -0500 Subject: [PATCH 091/153] Moves largerapp into patterns dir and add test - also adds this pattern into tox for testing --- examples/{ => patterns}/largerapp/setup.py | 0 examples/patterns/largerapp/tests/test_largerapp.py | 12 ++++++++++++ .../largerapp/yourapplication/__init__.py | 0 .../largerapp/yourapplication/static/style.css | 0 .../largerapp/yourapplication/templates/index.html | 0 .../largerapp/yourapplication/templates/layout.html | 0 .../largerapp/yourapplication/templates/login.html | 0 .../largerapp/yourapplication/views.py | 0 tox.ini | 1 + 9 files changed, 13 insertions(+) rename examples/{ => patterns}/largerapp/setup.py (100%) create mode 100644 examples/patterns/largerapp/tests/test_largerapp.py rename examples/{ => patterns}/largerapp/yourapplication/__init__.py (100%) rename examples/{ => patterns}/largerapp/yourapplication/static/style.css (100%) rename examples/{ => patterns}/largerapp/yourapplication/templates/index.html (100%) rename examples/{ => patterns}/largerapp/yourapplication/templates/layout.html (100%) rename examples/{ => patterns}/largerapp/yourapplication/templates/login.html (100%) rename examples/{ => patterns}/largerapp/yourapplication/views.py (100%) diff --git a/examples/largerapp/setup.py b/examples/patterns/largerapp/setup.py similarity index 100% rename from examples/largerapp/setup.py rename to examples/patterns/largerapp/setup.py diff --git a/examples/patterns/largerapp/tests/test_largerapp.py b/examples/patterns/largerapp/tests/test_largerapp.py new file mode 100644 index 00000000..acd33c5c --- /dev/null +++ b/examples/patterns/largerapp/tests/test_largerapp.py @@ -0,0 +1,12 @@ +from yourapplication import app +import pytest + +@pytest.fixture +def client(request): + app.config['TESTING'] = True + client = app.test_client() + return client + +def test_index(client): + rv = client.get('/') + assert b"Hello World!" in rv.data \ No newline at end of file diff --git a/examples/largerapp/yourapplication/__init__.py b/examples/patterns/largerapp/yourapplication/__init__.py similarity index 100% rename from examples/largerapp/yourapplication/__init__.py rename to examples/patterns/largerapp/yourapplication/__init__.py diff --git a/examples/largerapp/yourapplication/static/style.css b/examples/patterns/largerapp/yourapplication/static/style.css similarity index 100% rename from examples/largerapp/yourapplication/static/style.css rename to examples/patterns/largerapp/yourapplication/static/style.css diff --git a/examples/largerapp/yourapplication/templates/index.html b/examples/patterns/largerapp/yourapplication/templates/index.html similarity index 100% rename from examples/largerapp/yourapplication/templates/index.html rename to examples/patterns/largerapp/yourapplication/templates/index.html diff --git a/examples/largerapp/yourapplication/templates/layout.html b/examples/patterns/largerapp/yourapplication/templates/layout.html similarity index 100% rename from examples/largerapp/yourapplication/templates/layout.html rename to examples/patterns/largerapp/yourapplication/templates/layout.html diff --git a/examples/largerapp/yourapplication/templates/login.html b/examples/patterns/largerapp/yourapplication/templates/login.html similarity index 100% rename from examples/largerapp/yourapplication/templates/login.html rename to examples/patterns/largerapp/yourapplication/templates/login.html diff --git a/examples/largerapp/yourapplication/views.py b/examples/patterns/largerapp/yourapplication/views.py similarity index 100% rename from examples/largerapp/yourapplication/views.py rename to examples/patterns/largerapp/yourapplication/views.py diff --git a/tox.ini b/tox.ini index 406de5dd..c070a629 100644 --- a/tox.ini +++ b/tox.ini @@ -10,6 +10,7 @@ commands = # We need to install those after Flask is installed. pip install -e examples/flaskr pip install -e examples/minitwit + pip install -e examples/patterns/largerapp py.test --cov=flask --cov-report html [] deps= pytest From 750d01b30bdfa7feec4e23f6f15e959bcb046987 Mon Sep 17 00:00:00 2001 From: wgwz Date: Sat, 31 Dec 2016 12:37:39 -0500 Subject: [PATCH 092/153] Remove unneccessary arg in client fixture --- examples/patterns/largerapp/tests/test_largerapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/patterns/largerapp/tests/test_largerapp.py b/examples/patterns/largerapp/tests/test_largerapp.py index acd33c5c..6bc0531e 100644 --- a/examples/patterns/largerapp/tests/test_largerapp.py +++ b/examples/patterns/largerapp/tests/test_largerapp.py @@ -2,7 +2,7 @@ from yourapplication import app import pytest @pytest.fixture -def client(request): +def client(): app.config['TESTING'] = True client = app.test_client() return client From 6cf3aa2973472ba6b99675e749f639839468a64e Mon Sep 17 00:00:00 2001 From: wgwz Date: Sat, 31 Dec 2016 18:51:00 -0500 Subject: [PATCH 093/153] Provides a link to the examples src - moved the link towards the top for better visibility --- docs/patterns/packages.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index d1780ca8..1bb84f8c 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -17,6 +17,10 @@ this:: login.html ... +If you find yourself stuck on something, feel free +to take a look at the source code for this example. +You'll find `the full src for this example here`_. + Simple Packages --------------- @@ -114,10 +118,6 @@ You should then end up with something like that:: login.html ... -If you find yourself stuck on something, feel free -to take a look at the source code for this example. -You'll find it located under ``flask/examples/largerapp``. - .. admonition:: Circular Imports Every Python programmer hates them, and yet we just added some: @@ -134,6 +134,7 @@ You'll find it located under ``flask/examples/largerapp``. .. _working-with-modules: +.. _the full src for this example here: https://github.com/pallets/flask/tree/master/examples/patterns/largerapp Working with Blueprints ----------------------- From e474e244f841ad09d70dc5e3430cd6f18cbe2d0e Mon Sep 17 00:00:00 2001 From: Bryce Guinta Date: Sun, 1 Jan 2017 19:51:21 -0700 Subject: [PATCH 094/153] Fix fastcgi lighttpd example documentation. (#2138) Add a trailing slash to the dummy path in the fastcgi lighttpd setup documentation. Omitting a trailing slash leads to unintended behavior. --- docs/deploying/fastcgi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index c0beae0c..efae5163 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -144,7 +144,7 @@ A basic FastCGI configuration for lighttpd looks like that:: ) alias.url = ( - "/static/" => "/path/to/your/static" + "/static/" => "/path/to/your/static/" ) url.rewrite-once = ( From fffe46cf8782de78a1f94ab5270cb35a3b27d700 Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Tue, 10 Jan 2017 13:12:18 +0100 Subject: [PATCH 095/153] Do not suggest deprecated flask.ext.* --- docs/extensiondev.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index d73d6019..c9a72094 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -29,12 +29,6 @@ be something like "Flask-SimpleXML". Make sure to include the name This is how users can then register dependencies to your extension in their :file:`setup.py` files. -Flask sets up a redirect package called :data:`flask.ext` where users -should import the extensions from. If you for instance have a package -called ``flask_something`` users would import it as -``flask.ext.something``. This is done to transition from the old -namespace packages. See :ref:`ext-import-transition` for more details. - But what do extensions look like themselves? An extension has to ensure that it works with multiple Flask application instances at once. This is a requirement because many people will use patterns like the @@ -393,8 +387,6 @@ extension to be approved you have to follow these guidelines: Python 2.7 -.. _ext-import-transition: - Extension Import Transition --------------------------- From 9f0db546899dbff4eb1f4b796c6166bc25f6f5d4 Mon Sep 17 00:00:00 2001 From: Andrew Arendt Date: Tue, 10 Jan 2017 11:20:53 -0600 Subject: [PATCH 096/153] Added python3.6 support for tests --- .travis.yml | 6 +++++- tests/test_basic.py | 2 +- tests/test_ext.py | 4 ++-- tox.ini | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0f99a7e8..32247c58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ python: - "3.3" - "3.4" - "3.5" + - "3.6" env: - REQUIREMENTS=lowest @@ -32,7 +33,10 @@ matrix: env: REQUIREMENTS=lowest - python: "3.5" env: REQUIREMENTS=lowest-simplejson - + - python: "3.6" + env: REQUIREMENTS=lowest + - python: "3.6" + env: REQUIREMENTS=lowest-simplejson install: - pip install tox diff --git a/tests/test_basic.py b/tests/test_basic.py index be3d5edd..a099c904 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -333,7 +333,7 @@ def test_session_expiration(): client = app.test_client() rv = client.get('/') assert 'set-cookie' in rv.headers - match = re.search(r'\bexpires=([^;]+)(?i)', rv.headers['set-cookie']) + match = re.search(r'(?i)\bexpires=([^;]+)', rv.headers['set-cookie']) expires = parse_date(match.group()) expected = datetime.utcnow() + app.permanent_session_lifetime assert expires.year == expected.year diff --git a/tests/test_ext.py b/tests/test_ext.py index d336e404..ebb5f02d 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -179,8 +179,8 @@ def test_flaskext_broken_package_no_module_caching(flaskext_broken): def test_no_error_swallowing(flaskext_broken): with pytest.raises(ImportError) as excinfo: import flask.ext.broken - - assert excinfo.type is ImportError + # python3.6 raises a subclass of ImportError: 'ModuleNotFoundError' + assert issubclass(excinfo.type, ImportError) if PY2: message = 'No module named missing_module' else: diff --git a/tox.ini b/tox.ini index 406de5dd..1ffdd5da 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35}-{release,devel}{,-simplejson} +envlist = {py26,py27,pypy}-{lowest,release,devel}{,-simplejson}, {py33,py34,py35,py36}-{release,devel}{,-simplejson} From fffd9bb6507ef517a40b125114560ade7a406e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Nov=C3=BD?= Date: Fri, 13 Jan 2017 10:54:55 +0100 Subject: [PATCH 097/153] Use SOURCE_DATE_EPOCH for copyright year to make build reproducible Details: https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal --- docs/conf.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b37427a8..81106a3a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,10 +11,13 @@ # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import print_function -from datetime import datetime import os import sys import pkg_resources +import time +import datetime + +BUILD_DATE = datetime.datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -49,7 +52,7 @@ master_doc = 'index' # General information about the project. project = u'Flask' -copyright = u'2010 - {0}, Armin Ronacher'.format(datetime.utcnow().year) +copyright = u'2010 - {0}, Armin Ronacher'.format(BUILD_DATE.year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From b57c88e154a2f90b93467b67cd889966ca47724f Mon Sep 17 00:00:00 2001 From: Dennis Chen Date: Sat, 14 Jan 2017 12:58:45 -0800 Subject: [PATCH 098/153] Fix Request Reference (#2151) Points flask.Request to appropriate place in the documentation. --- docs/quickstart.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b444e080..749a1f75 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -538,16 +538,16 @@ The Request Object `````````````````` The request object is documented in the API section and we will not cover -it here in detail (see :class:`~flask.request`). Here is a broad overview of +it here in detail (see :class:`~flask.Request`). Here is a broad overview of some of the most common operations. First of all you have to import it from the ``flask`` module:: from flask import request The current request method is available by using the -:attr:`~flask.request.method` attribute. To access form data (data +:attr:`~flask.Request.method` attribute. To access form data (data transmitted in a ``POST`` or ``PUT`` request) you can use the -:attr:`~flask.request.form` attribute. Here is a full example of the two +:attr:`~flask.Request.form` attribute. Here is a full example of the two attributes mentioned above:: @app.route('/login', methods=['POST', 'GET']) @@ -570,7 +570,7 @@ error page is shown instead. So for many situations you don't have to deal with that problem. To access parameters submitted in the URL (``?key=value``) you can use the -:attr:`~flask.request.args` attribute:: +:attr:`~flask.Request.args` attribute:: searchword = request.args.get('key', '') @@ -579,7 +579,7 @@ We recommend accessing URL parameters with `get` or by catching the bad request page in that case is not user friendly. For a full list of methods and attributes of the request object, head over -to the :class:`~flask.request` documentation. +to the :class:`~flask.Request` documentation. File Uploads From c31e0ade9efc464abc074d28a8a76fb89d8c62d0 Mon Sep 17 00:00:00 2001 From: Kim Blomqvist Date: Tue, 17 Jan 2017 17:15:51 +0200 Subject: [PATCH 099/153] Disable debug when FLASK_DEBUG=False (#2155) Convert FLASK_DEBUG envvar to lower before test if in tuple --- flask/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index c6c2cddc..2f446327 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -58,7 +58,7 @@ def get_debug_flag(default=None): val = os.environ.get('FLASK_DEBUG') if not val: return default - return val not in ('0', 'false', 'no') + return val.lower() not in ('0', 'false', 'no') def _endpoint_from_view_func(view_func): From 84e1e61f8e3b92bda504dafc502e78a350a3983b Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Tue, 17 Jan 2017 11:20:07 -0800 Subject: [PATCH 100/153] Update docs that request is an object, not a class (#2154) Cleanup sphinx formatting to show that `request` is an object, not a class. The actual class name is `Request`. Based on discussion [here](https://github.com/pallets/flask/pull/2151#issuecomment-272699147). --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 7da28dc8..b5009907 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -66,7 +66,7 @@ Incoming Request Data ============= ====================================================== -.. class:: request +.. attribute:: request To access incoming request data, you can use the global `request` object. Flask parses incoming request data for you and gives you From eb6e8e40b257f4d9459abb23df9fe55f3aa2bcc5 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Tue, 17 Jan 2017 13:22:16 -0800 Subject: [PATCH 101/153] use SERVER_NAME to set host and port in app.run() (#2152) --- flask/app.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/flask/app.py b/flask/app.py index 942992dc..92ee8772 100644 --- a/flask/app.py +++ b/flask/app.py @@ -825,14 +825,14 @@ class Flask(_PackageBoundObject): information. """ from werkzeug.serving import run_simple - if host is None: - host = '127.0.0.1' - if port is None: - server_name = self.config['SERVER_NAME'] - if server_name and ':' in server_name: - port = int(server_name.rsplit(':', 1)[1]) - else: - port = 5000 + _host = '127.0.0.1' + _port = 5000 + server_name = self.config.get("SERVER_NAME") + sn_host, sn_port = None, None + if server_name: + sn_host, _, sn_port = server_name.partition(':') + host = host or sn_host or _host + port = int(port or sn_port or _port) if debug is not None: self.debug = bool(debug) options.setdefault('use_reloader', self.debug) From 7af6c7fa97c024f00469a6fbd5264d5cfa436898 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 17 Jan 2017 14:08:33 -0800 Subject: [PATCH 102/153] add test and changelog for SERVER_NAME app.run default ref #2152 --- CHANGES | 2 ++ tests/test_basic.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGES b/CHANGES index b096b0fe..157e92e3 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,8 @@ Bugfix release, unreleased within the imported application module. - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix ``#2118``. +- Use the``SERVER_NAME`` config if it is present as default values for + ``app.run``. ``#2109``, ``#2152`` Version 0.12 ------------ diff --git a/tests/test_basic.py b/tests/test_basic.py index a099c904..6341234b 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1681,3 +1681,20 @@ def test_run_server_port(monkeypatch): hostname, port = 'localhost', 8000 app.run(hostname, port, debug=True) assert rv['result'] == 'running on %s:%s ...' % (hostname, port) + + +@pytest.mark.parametrize('host,port,expect_host,expect_port', ( + (None, None, 'pocoo.org', 8080), + ('localhost', None, 'localhost', 8080), + (None, 80, 'pocoo.org', 80), + ('localhost', 80, 'localhost', 80), +)) +def test_run_from_config(monkeypatch, host, port, expect_host, expect_port): + def run_simple_mock(hostname, port, *args, **kwargs): + assert hostname == expect_host + assert port == expect_port + + monkeypatch.setattr(werkzeug.serving, 'run_simple', run_simple_mock) + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'pocoo.org:8080' + app.run(host, port) From 02b5eadc43f73920e0c322450c4f3413a9f87e9e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 29 Jan 2017 12:26:52 +0100 Subject: [PATCH 103/153] Convert Flask.run into a noop when run from the CLI --- CHANGES | 4 ++++ flask/app.py | 7 +++++++ flask/cli.py | 7 +++++++ flask/debughelpers.py | 12 ++++++++++++ 4 files changed, 30 insertions(+) diff --git a/CHANGES b/CHANGES index 157e92e3..62bb2004 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,10 @@ Version 0.13 Major release, unreleased +- Make `app.run()` into a noop if a Flask application is run from the + development server on the command line. This avoids some behavior that + was confusing to debug for newcomers. + Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index 92ee8772..27918d01 100644 --- a/flask/app.py +++ b/flask/app.py @@ -824,6 +824,13 @@ class Flask(_PackageBoundObject): :func:`werkzeug.serving.run_simple` for more information. """ + # Change this into a no-op if the server is invoked from the + # command line. Have a look at cli.py for more information. + if os.environ.get('FLASK_RUN_FROM_CLI_SERVER') == '1': + from .debughelpers import explain_ignored_app_run + explain_ignored_app_run() + return + from werkzeug.serving import run_simple _host = '127.0.0.1' _port = 5000 diff --git a/flask/cli.py b/flask/cli.py index 074ee768..bde5a13b 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -412,6 +412,13 @@ def run_command(info, host, port, reload, debugger, eager_loading, """ from werkzeug.serving import run_simple + # Set a global flag that indicates that we were invoked from the + # command line interface provided server command. This is detected + # by Flask.run to make the call into a no-op. This is necessary to + # avoid ugly errors when the script that is loaded here also attempts + # to start a server. + os.environ['FLASK_RUN_FROM_CLI_SERVER'] = '1' + debug = get_debug_flag() if reload is None: reload = bool(debug) diff --git a/flask/debughelpers.py b/flask/debughelpers.py index 90710dd3..9e44fe69 100644 --- a/flask/debughelpers.py +++ b/flask/debughelpers.py @@ -8,6 +8,9 @@ :copyright: (c) 2015 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import os +from warnings import warn + from ._compat import implements_to_string, text_type from .app import Flask from .blueprints import Blueprint @@ -153,3 +156,12 @@ def explain_template_loading_attempts(app, template, attempts): info.append(' See http://flask.pocoo.org/docs/blueprints/#templates') app.logger.info('\n'.join(info)) + + +def explain_ignored_app_run(): + if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': + warn(Warning('Silently ignoring app.run() because the ' + 'application is run from the flask command line ' + 'executable. Consider putting app.run() behind an ' + 'if __name__ == "__main__" guard to silence this ' + 'warning.'), stacklevel=3) From 8ac70e1719c249aca58e057a121357507c8134a9 Mon Sep 17 00:00:00 2001 From: Swan Htet Aung Date: Thu, 9 Feb 2017 18:01:12 +0630 Subject: [PATCH 104/153] Update 4.4.3 HTTP Methods Example Otherwise it produces `ValueError: View function did not return a response`. --- docs/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 749a1f75..b619185d 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -306,9 +306,9 @@ can be changed by providing the ``methods`` argument to the @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': - do_the_login() + return do_the_login() else: - show_the_login_form() + return show_the_login_form() If ``GET`` is present, ``HEAD`` will be added automatically for you. You don't have to deal with that. It will also make sure that ``HEAD`` requests From 83d2ba9cf8de77a61b1967baf6cf32499a0aad5b Mon Sep 17 00:00:00 2001 From: vojtekb Date: Thu, 9 Feb 2017 18:34:16 +0100 Subject: [PATCH 105/153] py.test => pytest (#2173) py.test => pytest --- CONTRIBUTING.rst | 10 +++++----- README | 4 ++-- docs/tutorial/testing.rst | 2 +- setup.cfg | 2 +- tox.ini | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d9cd2214..1c9c8912 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -38,7 +38,7 @@ Running the testsuite You probably want to set up a `virtualenv `_. -The minimal requirement for running the testsuite is ``py.test``. You can +The minimal requirement for running the testsuite is ``pytest``. You can install it with:: pip install pytest @@ -54,9 +54,9 @@ Install Flask as an editable package using the current source:: Then you can run the testsuite with:: - py.test + pytest -With only py.test 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 on. Travis is set up to run the full testsuite when you submit your pull request anyways. @@ -79,11 +79,11 @@ plugin. This assumes you have already run the testsuite (see previous section): After this has been installed, you can output a report to the command line using this command:: - py.test --cov=flask tests/ + pytest --cov=flask tests/ Generate a HTML report can be done using this command:: - py.test --cov-report html --cov=flask tests/ + pytest --cov-report html --cov=flask tests/ Full docs on ``coverage.py`` are here: https://coverage.readthedocs.io diff --git a/README b/README index baea6b24..75c5e7b1 100644 --- a/README +++ b/README @@ -33,9 +33,9 @@ Good that you're asking. The tests are in the tests/ folder. To run the tests use the - `py.test` testing tool: + `pytest` testing tool: - $ py.test + $ pytest Details on contributing can be found in CONTRIBUTING.rst diff --git a/docs/tutorial/testing.rst b/docs/tutorial/testing.rst index dcf36594..26099375 100644 --- a/docs/tutorial/testing.rst +++ b/docs/tutorial/testing.rst @@ -46,7 +46,7 @@ At this point you can run the tests. Here ``pytest`` will be used. Run and watch the tests pass, within the top-level :file:`flaskr/` directory as:: - py.test + pytest Testing + setuptools -------------------- diff --git a/setup.cfg b/setup.cfg index cd282e48..0e97b0ba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,4 +8,4 @@ universal = 1 license_file = LICENSE [tool:pytest] -norecursedirs = .* *.egg *.egg-info env* artwork docs +norecursedirs = .* *.egg *.egg-info env* artwork docs examples diff --git a/tox.ini b/tox.ini index 8d5a0f43..764b4030 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ commands = pip install -e examples/flaskr pip install -e examples/minitwit pip install -e examples/patterns/largerapp - py.test --cov=flask --cov-report html [] + pytest --cov=flask --cov-report html [] deps= pytest pytest-cov From 4f0ab34559d187d6550827e5f3de68860f1a964f Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Thu, 9 Feb 2017 18:35:21 +0100 Subject: [PATCH 106/153] Remove examples dir again --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 0e97b0ba..cd282e48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,4 +8,4 @@ universal = 1 license_file = LICENSE [tool:pytest] -norecursedirs = .* *.egg *.egg-info env* artwork docs examples +norecursedirs = .* *.egg *.egg-info env* artwork docs From 742ea5221d521ec017d5a9fa1270ce366bdc9304 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Fri, 10 Feb 2017 03:19:59 -0800 Subject: [PATCH 107/153] bdist_wheel replaces wheel (#2179) https://packaging.python.org/distributing/#universal-wheels --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index cd282e48..781de592 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [aliases] release = egg_info -RDb '' -[wheel] +[bdist_wheel] universal = 1 [metadata] From 2ad2aadb88fe0c3f1132ee9b398d651fb69826d6 Mon Sep 17 00:00:00 2001 From: Jeff Widman Date: Sat, 11 Feb 2017 01:43:11 -0800 Subject: [PATCH 108/153] Migrate various docs links to https (#2180) Also fixed a few outdated links --- CONTRIBUTING.rst | 2 +- docs/_templates/sidebarintro.html | 6 +++--- docs/conf.py | 2 +- docs/deploying/fastcgi.rst | 6 +++--- docs/deploying/index.rst | 2 +- docs/deploying/mod_wsgi.rst | 8 ++++---- docs/deploying/uwsgi.rst | 4 ++-- docs/errorhandling.rst | 4 ++-- docs/extensiondev.rst | 2 +- docs/installation.rst | 2 +- docs/patterns/appfactories.rst | 2 +- docs/patterns/favicon.rst | 2 +- docs/patterns/fileuploads.rst | 2 +- docs/patterns/sqlalchemy.rst | 6 +++--- docs/patterns/sqlite3.rst | 14 +++++++------- docs/patterns/wtforms.rst | 2 +- docs/quickstart.rst | 8 ++++---- docs/security.rst | 2 +- docs/tutorial/introduction.rst | 2 +- docs/upgrading.rst | 2 +- examples/minitwit/minitwit/minitwit.py | 2 +- setup.py | 4 ++-- 22 files changed, 43 insertions(+), 43 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 1c9c8912..66766512 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -28,7 +28,7 @@ Submitting patches clearly under which circumstances the bug happens. Make sure the test fails without your patch. -- Try to follow `PEP8 `_, but you +- Try to follow `PEP8 `_, but you may ignore the line-length-limit if following it would make the code uglier. diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index ec1608fd..71fcd73b 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -16,7 +16,7 @@

    Useful Links

    diff --git a/docs/conf.py b/docs/conf.py index 81106a3a..8682dd8c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -260,7 +260,7 @@ intersphinx_mapping = { 'werkzeug': ('http://werkzeug.pocoo.org/docs/', None), 'click': ('http://click.pocoo.org/', None), 'jinja': ('http://jinja.pocoo.org/docs/', None), - 'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None), + 'sqlalchemy': ('https://docs.sqlalchemy.org/en/latest/', None), 'wtforms': ('https://wtforms.readthedocs.io/en/latest/', None), 'blinker': ('https://pythonhosted.org/blinker/', None) } diff --git a/docs/deploying/fastcgi.rst b/docs/deploying/fastcgi.rst index efae5163..5ca2a084 100644 --- a/docs/deploying/fastcgi.rst +++ b/docs/deploying/fastcgi.rst @@ -159,7 +159,7 @@ work in the URL root you have to work around a lighttpd bug with the Make sure to apply it only if you are mounting the application the URL root. Also, see the Lighty docs for more information on `FastCGI and Python -`_ (note that +`_ (note that explicitly passing a socket to run() is no longer necessary). Configuring nginx @@ -234,7 +234,7 @@ python path. Common problems are: web server. - Different python interpreters being used. -.. _nginx: http://nginx.org/ -.. _lighttpd: http://www.lighttpd.net/ +.. _nginx: https://nginx.org/ +.. _lighttpd: https://www.lighttpd.net/ .. _cherokee: http://cherokee-project.com/ .. _flup: https://pypi.python.org/pypi/flup diff --git a/docs/deploying/index.rst b/docs/deploying/index.rst index 20e71762..6950e47a 100644 --- a/docs/deploying/index.rst +++ b/docs/deploying/index.rst @@ -21,7 +21,7 @@ Hosted options - `Deploying Flask on OpenShift `_ - `Deploying Flask on Webfaction `_ - `Deploying Flask on Google App Engine `_ -- `Deploying Flask on AWS Elastic Beanstalk `_ +- `Deploying Flask on AWS Elastic Beanstalk `_ - `Sharing your Localhost Server with Localtunnel `_ - `Deploying on Azure (IIS) `_ - `Deploying on PythonAnywhere `_ diff --git a/docs/deploying/mod_wsgi.rst b/docs/deploying/mod_wsgi.rst index 0f4af6c3..ca694b7d 100644 --- a/docs/deploying/mod_wsgi.rst +++ b/docs/deploying/mod_wsgi.rst @@ -13,7 +13,7 @@ If you are using the `Apache`_ webserver, consider using `mod_wsgi`_. not called because this will always start a local WSGI server which we do not want if we deploy that application to mod_wsgi. -.. _Apache: http://httpd.apache.org/ +.. _Apache: https://httpd.apache.org/ Installing `mod_wsgi` --------------------- @@ -114,7 +114,7 @@ refuse to run with the above configuration. On a Windows system, eliminate those Note: There have been some changes in access control configuration for `Apache 2.4`_. -.. _Apache 2.4: http://httpd.apache.org/docs/trunk/upgrading.html +.. _Apache 2.4: https://httpd.apache.org/docs/trunk/upgrading.html Most notably, the syntax for directory permissions has changed from httpd 2.2 @@ -133,9 +133,9 @@ to httpd 2.4 syntax For more information consult the `mod_wsgi documentation`_. .. _mod_wsgi: https://github.com/GrahamDumpleton/mod_wsgi -.. _installation instructions: http://modwsgi.readthedocs.io/en/develop/installation.html +.. _installation instructions: https://modwsgi.readthedocs.io/en/develop/installation.html .. _virtual python: https://pypi.python.org/pypi/virtualenv -.. _mod_wsgi documentation: http://modwsgi.readthedocs.io/en/develop/index.html +.. _mod_wsgi documentation: https://modwsgi.readthedocs.io/en/develop/index.html Troubleshooting --------------- diff --git a/docs/deploying/uwsgi.rst b/docs/deploying/uwsgi.rst index fc991e72..50c85fb2 100644 --- a/docs/deploying/uwsgi.rst +++ b/docs/deploying/uwsgi.rst @@ -66,7 +66,7 @@ to have it in the URL root its a bit simpler:: uwsgi_pass unix:/tmp/yourapplication.sock; } -.. _nginx: http://nginx.org/ -.. _lighttpd: http://www.lighttpd.net/ +.. _nginx: https://nginx.org/ +.. _lighttpd: https://www.lighttpd.net/ .. _cherokee: http://cherokee-project.com/ .. _uwsgi: http://projects.unbit.it/uwsgi/ diff --git a/docs/errorhandling.rst b/docs/errorhandling.rst index 64c0f8b3..2791fec3 100644 --- a/docs/errorhandling.rst +++ b/docs/errorhandling.rst @@ -34,7 +34,7 @@ Error Logging Tools Sending error mails, even if just for critical ones, can become overwhelming if enough users are hitting the error and log files are typically never looked at. This is why we recommend using `Sentry -`_ for dealing with application errors. It's +`_ for dealing with application errors. It's available as an Open Source project `on GitHub `__ and is also available as a `hosted version `_ which you can try for free. Sentry @@ -89,7 +89,7 @@ Register error handlers using :meth:`~flask.Flask.errorhandler` or @app.errorhandler(werkzeug.exceptions.BadRequest) def handle_bad_request(e): return 'bad request!' - + app.register_error_handler(400, lambda e: 'bad request!') Those two ways are equivalent, but the first one is more clear and leaves diff --git a/docs/extensiondev.rst b/docs/extensiondev.rst index c9a72094..9ae6e6f1 100644 --- a/docs/extensiondev.rst +++ b/docs/extensiondev.rst @@ -405,6 +405,6 @@ schema. The ``flask.ext.foo`` compatibility alias is still in Flask 0.11 but is now deprecated -- you should use ``flask_foo``. -.. _OAuth extension: http://pythonhosted.org/Flask-OAuth/ +.. _OAuth extension: https://pythonhosted.org/Flask-OAuth/ .. _mailinglist: http://flask.pocoo.org/mailinglist/ .. _IRC channel: http://flask.pocoo.org/community/irc/ diff --git a/docs/installation.rst b/docs/installation.rst index 6f833eac..96c363f5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -112,7 +112,7 @@ 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:: - $ git clone http://github.com/pallets/flask.git + $ git clone https://github.com/pallets/flask.git Initialized empty Git repository in ~/dev/flask/.git/ $ cd flask $ virtualenv venv diff --git a/docs/patterns/appfactories.rst b/docs/patterns/appfactories.rst index c118a273..fdbde504 100644 --- a/docs/patterns/appfactories.rst +++ b/docs/patterns/appfactories.rst @@ -60,7 +60,7 @@ Factories & Extensions It's preferable to create your extensions and app factories so that the extension object does not initially get bound to the application. -Using `Flask-SQLAlchemy `_, +Using `Flask-SQLAlchemy `_, as an example, you should not do something along those lines:: def create_app(config_filename): diff --git a/docs/patterns/favicon.rst b/docs/patterns/favicon.rst index acdee24b..21ea767f 100644 --- a/docs/patterns/favicon.rst +++ b/docs/patterns/favicon.rst @@ -49,5 +49,5 @@ web server's documentation. See also -------- -* The `Favicon `_ article on +* The `Favicon `_ article on Wikipedia diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 8ab8c033..dc3820be 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -181,4 +181,4 @@ applications dealing with uploads, there is also a Flask extension called blacklisting of extensions and more. .. _jQuery: https://jquery.com/ -.. _Flask-Uploads: http://pythonhosted.org/Flask-Uploads/ +.. _Flask-Uploads: https://pythonhosted.org/Flask-Uploads/ diff --git a/docs/patterns/sqlalchemy.rst b/docs/patterns/sqlalchemy.rst index 9c985cc6..8785a6e2 100644 --- a/docs/patterns/sqlalchemy.rst +++ b/docs/patterns/sqlalchemy.rst @@ -108,9 +108,9 @@ Querying is simple as well: >>> User.query.filter(User.name == 'admin').first() -.. _SQLAlchemy: http://www.sqlalchemy.org/ +.. _SQLAlchemy: https://www.sqlalchemy.org/ .. _declarative: - http://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/ + https://docs.sqlalchemy.org/en/latest/orm/extensions/declarative/ Manual Object Relational Mapping -------------------------------- @@ -215,4 +215,4 @@ You can also pass strings of SQL statements to the (1, u'admin', u'admin@localhost') For more information about SQLAlchemy, head over to the -`website `_. +`website `_. diff --git a/docs/patterns/sqlite3.rst b/docs/patterns/sqlite3.rst index 66a7c4c4..15f38ea7 100644 --- a/docs/patterns/sqlite3.rst +++ b/docs/patterns/sqlite3.rst @@ -3,8 +3,8 @@ Using SQLite 3 with Flask ========================= -In Flask you can easily implement the opening of database connections on -demand and closing them when the context dies (usually at the end of the +In Flask you can easily implement the opening of database connections on +demand and closing them when the context dies (usually at the end of the request). Here is a simple example of how you can use SQLite 3 with Flask:: @@ -71,7 +71,7 @@ Now in each request handling function you can access `g.db` to get the current open database connection. To simplify working with SQLite, a row factory function is useful. It is executed for every result returned from the database to convert the result. For instance, in order to get -dictionaries instead of tuples, this could be inserted into the ``get_db`` +dictionaries instead of tuples, this could be inserted into the ``get_db`` function we created above:: def make_dicts(cursor, row): @@ -102,15 +102,15 @@ This would use Row objects rather than dicts to return the results of queries. T Additionally, it is a good idea to provide a query function that combines getting the cursor, executing and fetching the results:: - + def query_db(query, args=(), one=False): cur = get_db().execute(query, args) rv = cur.fetchall() cur.close() return (rv[0] if rv else None) if one else rv -This handy little function, in combination with a row factory, makes -working with the database much more pleasant than it is by just using the +This handy little function, in combination with a row factory, makes +working with the database much more pleasant than it is by just using the raw cursor and connection objects. Here is how you can use it:: @@ -131,7 +131,7 @@ To pass variable parts to the SQL statement, use a question mark in the statement and pass in the arguments as a list. Never directly add them to the SQL statement with string formatting because this makes it possible to attack the application using `SQL Injections -`_. +`_. Initial Schemas --------------- diff --git a/docs/patterns/wtforms.rst b/docs/patterns/wtforms.rst index 2649cad6..0e53de17 100644 --- a/docs/patterns/wtforms.rst +++ b/docs/patterns/wtforms.rst @@ -19,7 +19,7 @@ forms. fun. You can get it from `PyPI `_. -.. _Flask-WTF: http://pythonhosted.org/Flask-WTF/ +.. _Flask-WTF: https://flask-wtf.readthedocs.io/en/stable/ The Forms --------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b619185d..7ce8a90f 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -102,9 +102,9 @@ docs to see the alternative method for running a server. Invalid Import Name ``````````````````` -The ``FLASK_APP`` environment variable is the name of the module to import at -:command:`flask run`. In case that module is incorrectly named you will get an -import error upon start (or if debug is enabled when you navigate to the +The ``FLASK_APP`` environment variable is the name of the module to import at +:command:`flask run`. In case that module is incorrectly named you will get an +import error upon start (or if debug is enabled when you navigate to the application). It will tell you what it tried to import and why it failed. The most common reason is a typo or because you did not actually create an @@ -367,7 +367,7 @@ 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: http://www.ietf.org/rfc/rfc2068.txt +.. _HTTP RFC: https://www.ietf.org/rfc/rfc2068.txt Static Files ------------ diff --git a/docs/security.rst b/docs/security.rst index 587bd4ef..ad0d1244 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -15,7 +15,7 @@ it JavaScript) into the context of a website. To remedy this, developers have to properly escape text so that it cannot include arbitrary HTML tags. For more information on that have a look at the Wikipedia article on `Cross-Site Scripting -`_. +`_. Flask configures Jinja2 to automatically escape all values unless explicitly told otherwise. This should rule out all XSS problems caused diff --git a/docs/tutorial/introduction.rst b/docs/tutorial/introduction.rst index dd46628b..1abe597f 100644 --- a/docs/tutorial/introduction.rst +++ b/docs/tutorial/introduction.rst @@ -31,4 +31,4 @@ Here a screenshot of the final application: Continue with :ref:`tutorial-folders`. -.. _SQLAlchemy: http://www.sqlalchemy.org/ +.. _SQLAlchemy: https://www.sqlalchemy.org/ diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 41b70f03..436b0430 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -143,7 +143,7 @@ when there is no request context yet but an application context. The old ``flask.Flask.request_globals_class`` attribute was renamed to :attr:`flask.Flask.app_ctx_globals_class`. -.. _Flask-OldSessions: http://pythonhosted.org/Flask-OldSessions/ +.. _Flask-OldSessions: https://pythonhosted.org/Flask-OldSessions/ Version 0.9 ----------- diff --git a/examples/minitwit/minitwit/minitwit.py b/examples/minitwit/minitwit/minitwit.py index bbc3b483..69840267 100644 --- a/examples/minitwit/minitwit/minitwit.py +++ b/examples/minitwit/minitwit/minitwit.py @@ -85,7 +85,7 @@ def format_datetime(timestamp): def gravatar_url(email, size=80): """Return the gravatar image for the given email address.""" - return 'http://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \ + return 'https://www.gravatar.com/avatar/%s?d=identicon&s=%d' % \ (md5(email.strip().lower().encode('utf-8')).hexdigest(), size) diff --git a/setup.py b/setup.py index 983f7611..08995073 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ Links * `website `_ * `documentation `_ * `development version - `_ + `_ """ import re @@ -59,7 +59,7 @@ with open('flask/__init__.py', 'rb') as f: setup( name='Flask', version=version, - url='http://github.com/pallets/flask/', + url='https://github.com/pallets/flask/', license='BSD', author='Armin Ronacher', author_email='armin.ronacher@active-4.com', From 4b25b483b8551ad69d2d33b27b3bee9be1506cc1 Mon Sep 17 00:00:00 2001 From: Nick Ficano Date: Wed, 15 Feb 2017 11:55:56 -0500 Subject: [PATCH 109/153] Fix typo in file header (jsonimpl => json) --- flask/json.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flask/json.py b/flask/json.py index 16e0c295..19b337c3 100644 --- a/flask/json.py +++ b/flask/json.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ - flask.jsonimpl - ~~~~~~~~~~~~~~ + flask.json + ~~~~~~~~~~ Implementation helpers for the JSON support in Flask. From aadce6bc34a6844a5c28b974181ef05441f29357 Mon Sep 17 00:00:00 2001 From: Timothy John Perisho Eccleston Date: Sat, 18 Feb 2017 00:41:58 -0600 Subject: [PATCH 110/153] Fix typo in docs/tutorial/templates.rst (#2186) --- docs/tutorial/templates.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index 269e8df1..4cb7db7f 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -59,7 +59,7 @@ show_entries.html This template extends the :file:`layout.html` template from above to display the messages. Note that the ``for`` loop iterates over the messages we passed in with the :func:`~flask.render_template` function. Notice that the form is -configured to to submit to the `add_entry` view function and use ``POST`` as +configured to submit to the `add_entry` view function and use ``POST`` as HTTP method: .. sourcecode:: html+jinja From 17b4fa9b870bd1a498033d025d47a95167080377 Mon Sep 17 00:00:00 2001 From: Sebastian Kalinowski Date: Tue, 28 Feb 2017 06:05:09 +0100 Subject: [PATCH 111/153] Remove extra HTML tag from fileupload docs (#2141) --- docs/patterns/fileuploads.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index dc3820be..8bf2287c 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -72,8 +72,8 @@ the file and redirects the user to the URL for the uploaded file:: Upload new File

    Upload new File

    -

    - + +

    ''' From f04ea7f2ba85cfb4f1a62988dd6340041d1d57a9 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Sat, 4 Mar 2017 18:29:04 +0800 Subject: [PATCH 112/153] Add tips for debug config with flask cli (#2196) * Add tips for debug config with flask cli `app.debug` and `app.config['DEBUG']` are not compatible with the `flask` script. * Grammar fix * Grammar fix --- docs/config.rst | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 6d37c1e8..c36cc852 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -44,6 +44,21 @@ method:: SECRET_KEY='...' ) +.. admonition:: Debug Mode with the ``flask`` Script + + If you use the :command:`flask` script to start a local development + server, to enable the debug mode, you need to export the ``FLASK_DEBUG`` + environment variable before running the server:: + + $ export FLASK_DEBUG=1 + $ flask run + + (On Windows you need to use ``set`` instead of ``export``). + + ``app.debug`` and ``app.config['DEBUG']`` are not compatible with +   the :command:`flask` script. They only worked when using ``Flask.run()`` + method. + Builtin Configuration Values ---------------------------- @@ -52,7 +67,8 @@ The following configuration values are used internally by Flask: .. tabularcolumns:: |p{6.5cm}|p{8.5cm}| ================================= ========================================= -``DEBUG`` enable/disable debug mode +``DEBUG`` enable/disable debug mode when using + ``Flask.run()`` method to start server ``TESTING`` enable/disable testing mode ``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the propagation of exceptions. If not set or From b780d771efba06ab11341f8b7df046058c864825 Mon Sep 17 00:00:00 2001 From: Adrian Date: Sat, 4 Mar 2017 22:32:23 +0100 Subject: [PATCH 113/153] Fix typo --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 62bb2004..2c0a4869 100644 --- a/CHANGES +++ b/CHANGES @@ -21,7 +21,7 @@ Bugfix release, unreleased within the imported application module. - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix ``#2118``. -- Use the``SERVER_NAME`` config if it is present as default values for +- Use the ``SERVER_NAME`` config if it is present as default values for ``app.run``. ``#2109``, ``#2152`` Version 0.12 From 822697fe1f216b88bdb67e645f82b3d6aa3ae4b2 Mon Sep 17 00:00:00 2001 From: Elton Law Date: Sun, 5 Mar 2017 07:07:49 -0500 Subject: [PATCH 114/153] Close
  • tag in tutorial (#2199) Change was merged in the example code but wasn't changed in the docs. https://github.com/pallets/flask/commit/c54d67adee6f99113f525b376da4af27c3001321 --- docs/tutorial/templates.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/templates.rst b/docs/tutorial/templates.rst index 4cb7db7f..4f7977e8 100644 --- a/docs/tutorial/templates.rst +++ b/docs/tutorial/templates.rst @@ -79,9 +79,9 @@ HTTP method: {% endif %}
      {% for entry in entries %} -
    • {{ entry.title }}

      {{ entry.text|safe }} +
    • {{ entry.title }}

      {{ entry.text|safe }}
    • {% else %} -
    • Unbelievable. No entries here so far +
    • Unbelievable. No entries here so far
    • {% endfor %}
    {% endblock %} From 3eb41b73090139a132e0dbc09717012372187362 Mon Sep 17 00:00:00 2001 From: Static Date: Mon, 6 Mar 2017 07:05:59 -0600 Subject: [PATCH 115/153] Fix typos/grammar in docs (#2201) --- docs/conf.py | 2 +- docs/patterns/fileuploads.rst | 2 +- docs/styleguide.rst | 2 +- docs/upgrading.rst | 4 ++-- flask/app.py | 4 ++-- scripts/flask-07-upgrade.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8682dd8c..f53d72fa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -234,7 +234,7 @@ latex_additional_files = ['flaskstyle.sty', 'logo.pdf'] # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' -# The unique identifier of the text. This can be a ISBN number +# The unique identifier of the text. This can be an ISBN number # or the project homepage. #epub_identifier = '' diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 8bf2287c..3a42d325 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -58,7 +58,7 @@ the file and redirects the user to the URL for the uploaded file:: return redirect(request.url) file = request.files['file'] # if user does not select file, browser also - # submit a empty part without filename + # submit an empty part without filename if file.filename == '': flash('No selected file') return redirect(request.url) diff --git a/docs/styleguide.rst b/docs/styleguide.rst index e03e4ef5..390d5668 100644 --- a/docs/styleguide.rst +++ b/docs/styleguide.rst @@ -167,7 +167,7 @@ Docstring conventions: """ Module header: - The module header consists of an utf-8 encoding declaration (if non + The module header consists of a utf-8 encoding declaration (if non ASCII letters are used, but it is recommended all the time) and a standard docstring:: diff --git a/docs/upgrading.rst b/docs/upgrading.rst index 436b0430..af2383c0 100644 --- a/docs/upgrading.rst +++ b/docs/upgrading.rst @@ -49,7 +49,7 @@ Any of the following is functionally equivalent:: response = send_file(open(fname), attachment_filename=fname) response.set_etag(...) -The reason for this is that some file-like objects have a invalid or even +The reason for this is that some file-like objects have an invalid or even misleading ``name`` attribute. Silently swallowing errors in such cases was not a satisfying solution. @@ -198,7 +198,7 @@ applications with Flask. Because we want to make upgrading as easy as possible we tried to counter the problems arising from these changes by providing a script that can ease the transition. -The script scans your whole application and generates an unified diff with +The script scans your whole application and generates a unified diff with changes it assumes are safe to apply. However as this is an automated tool it won't be able to find all use cases and it might miss some. We internally spread a lot of deprecation warnings all over the place to make diff --git a/flask/app.py b/flask/app.py index 27918d01..7745ace6 100644 --- a/flask/app.py +++ b/flask/app.py @@ -391,7 +391,7 @@ class Flask(_PackageBoundObject): #: is the class for the instance check and the second the error handler #: function. #: - #: To register a error handler, use the :meth:`errorhandler` + #: To register an error handler, use the :meth:`errorhandler` #: decorator. self.error_handler_spec = {None: self._error_handlers} @@ -1354,7 +1354,7 @@ class Flask(_PackageBoundObject): will have to surround the execution of these code by try/except statements and log occurring errors. - When a teardown function was called because of a exception it will + When a teardown function was called because of an exception it will be passed an error object. The return values of teardown functions are ignored. diff --git a/scripts/flask-07-upgrade.py b/scripts/flask-07-upgrade.py index 7fbdd49c..18e1a14b 100644 --- a/scripts/flask-07-upgrade.py +++ b/scripts/flask-07-upgrade.py @@ -5,7 +5,7 @@ ~~~~~~~~~~~~~~~~ This command line script scans a whole application tree and attempts to - output an unified diff with all the changes that are necessary to easily + output a unified diff with all the changes that are necessary to easily upgrade the application to 0.7 and to not yield deprecation warnings. This will also attempt to find `after_request` functions that don't modify From 9c5f048ad26716d909b6e2ee1a7e47c07e0f5dd8 Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Tue, 7 Mar 2017 10:09:46 +0900 Subject: [PATCH 116/153] Don't rely on X-Requested-With for pretty print json response (#2193) * Don't rely on X-Requested-With for pretty print json response * Fix test cases for pretty print json patch * Fix gramma error in docs for pretty print json config * Add changelog for JSONIFY_PRETTYPRINT_REGULAR --- CHANGES | 3 +++ docs/config.rst | 8 +++----- flask/app.py | 2 +- flask/json.py | 2 +- tests/test_basic.py | 2 +- tests/test_helpers.py | 2 ++ 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 2c0a4869..7c50d0c7 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,9 @@ Major release, unreleased - Make `app.run()` into a noop if a Flask application is run from the development server on the command line. This avoids some behavior that was confusing to debug for newcomers. +- Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() + method returns compressed response by default, and pretty response in + debug mode. Version 0.12.1 -------------- diff --git a/docs/config.rst b/docs/config.rst index c36cc852..75ce239a 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -194,11 +194,9 @@ The following configuration values are used internally by Flask: This is not recommended but might give you a performance improvement on the cost of cacheability. -``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` (the default) - jsonify responses will be pretty printed - if they are not requested by an - XMLHttpRequest object (controlled by - the ``X-Requested-With`` header) +``JSONIFY_PRETTYPRINT_REGULAR`` If this is set to ``True`` or the Flask app + is running in debug mode, jsonify responses + will be pretty printed. ``JSONIFY_MIMETYPE`` MIME type used for jsonify responses. ``TEMPLATES_AUTO_RELOAD`` Whether to check for modifications of the template source and reload it diff --git a/flask/app.py b/flask/app.py index 7745ace6..bc38d9ea 100644 --- a/flask/app.py +++ b/flask/app.py @@ -314,7 +314,7 @@ class Flask(_PackageBoundObject): 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, - 'JSONIFY_PRETTYPRINT_REGULAR': True, + 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, }) diff --git a/flask/json.py b/flask/json.py index 19b337c3..0ba9d717 100644 --- a/flask/json.py +++ b/flask/json.py @@ -248,7 +248,7 @@ def jsonify(*args, **kwargs): indent = None separators = (',', ':') - if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] and not request.is_xhr: + if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] or current_app.debug: indent = 2 separators = (', ', ': ') diff --git a/tests/test_basic.py b/tests/test_basic.py index 6341234b..942eb0f6 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -995,7 +995,7 @@ def test_make_response_with_response_instance(): rv = flask.make_response( flask.jsonify({'msg': 'W00t'}), 400) assert rv.status_code == 400 - assert rv.data == b'{\n "msg": "W00t"\n}\n' + assert rv.data == b'{"msg":"W00t"}\n' assert rv.mimetype == 'application/json' rv = flask.make_response( diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 3e2ea8cd..fd448fb8 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -289,6 +289,8 @@ class TestJSON(object): def test_json_key_sorting(self): app = flask.Flask(__name__) app.testing = True + app.debug = True + assert app.config['JSON_SORT_KEYS'] == True d = dict.fromkeys(range(20), 'foo') From 42dfa1fce33d26517f6d2e1794ecd3aef70128eb Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 8 Mar 2017 11:26:38 -0800 Subject: [PATCH 117/153] Fix broken link (#2202) --- docs/patterns/distribute.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/distribute.rst b/docs/patterns/distribute.rst index 72cc25d6..f4a07579 100644 --- a/docs/patterns/distribute.rst +++ b/docs/patterns/distribute.rst @@ -174,4 +174,4 @@ the code without having to run ``install`` again after each change. .. _pip: https://pypi.python.org/pypi/pip -.. _Setuptools: https://pythonhosted.org/setuptools +.. _Setuptools: https://pypi.python.org/pypi/setuptools From 0dae045fe734f754cec8013a2f58d23d62407052 Mon Sep 17 00:00:00 2001 From: John Bodley Date: Sat, 11 Mar 2017 09:59:34 -0800 Subject: [PATCH 118/153] Document run() host defaulting to SERVER_NAME --- flask/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index bc38d9ea..c8540b5f 100644 --- a/flask/app.py +++ b/flask/app.py @@ -813,7 +813,8 @@ class Flask(_PackageBoundObject): :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to have the server available externally as well. Defaults to - ``'127.0.0.1'``. + ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config + variable if present. :param port: the port of the webserver. Defaults to ``5000`` or the port defined in the ``SERVER_NAME`` config variable if present. From 7664e605ba251509ed0e94dcb05fba71732ce44b Mon Sep 17 00:00:00 2001 From: Jan Ferko Date: Mon, 13 Mar 2017 13:58:24 +0100 Subject: [PATCH 119/153] Use print function in quickstart (#2204) Example in URL Building section uses `print` statement instead of `print` function, which causes syntax error when example is run on Python 3. --- docs/quickstart.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 7ce8a90f..09365496 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -264,10 +264,10 @@ some examples:: ... 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') + ... print(url_for('index')) + ... print(url_for('login')) + ... print(url_for('login', next='/')) + ... print(url_for('profile', username='John Doe')) ... / /login From ae6617e85dfe3120e1d264ad9ecc7510215e8f7f Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Thu, 16 Mar 2017 14:37:58 +0100 Subject: [PATCH 120/153] Print a stacktrace on CLI error (closes #2208) --- flask/cli.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flask/cli.py b/flask/cli.py index bde5a13b..8f8fac03 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -11,6 +11,7 @@ import os import sys +import traceback from threading import Lock, Thread from functools import update_wrapper @@ -368,6 +369,9 @@ class FlaskGroup(AppGroup): # want the help page to break if the app does not exist. # If someone attempts to use the command we try to create # the app again and this will give us the error. + # However, we will not do so silently because that would confuse + # users. + traceback.print_exc() pass return sorted(rv) From c53aa3affeef354ebae0a46845f2432a85da9d95 Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Thu, 16 Mar 2017 14:42:09 +0100 Subject: [PATCH 121/153] Remove useless pass --- flask/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index 8f8fac03..8db7e07e 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -372,7 +372,6 @@ class FlaskGroup(AppGroup): # However, we will not do so silently because that would confuse # users. traceback.print_exc() - pass return sorted(rv) def main(self, *args, **kwargs): From edd6e3154d49189d3b7627e254010f604d6bb4fb Mon Sep 17 00:00:00 2001 From: Sven-Hendrik Haase Date: Thu, 16 Mar 2017 20:56:12 +0100 Subject: [PATCH 122/153] Add test to showcase that printing a traceback works --- tests/test_cli.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index 313a34d2..82c69f93 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -191,3 +191,20 @@ def test_flaskgroup(): result = runner.invoke(cli, ['test']) assert result.exit_code == 0 assert result.output == 'flaskgroup\n' + + +def test_print_exceptions(): + """Print the stacktrace if the CLI.""" + def create_app(info): + raise Exception("oh no") + return Flask("flaskgroup") + + @click.group(cls=FlaskGroup, create_app=create_app) + def cli(**params): + pass + + runner = CliRunner() + result = runner.invoke(cli, ['--help']) + assert result.exit_code == 0 + assert 'Exception: oh no' in result.output + assert 'Traceback' in result.output From 92235697675956809156049b9d3d7da9e0a263d6 Mon Sep 17 00:00:00 2001 From: Ed Brannin Date: Tue, 21 Mar 2017 15:22:15 -0400 Subject: [PATCH 123/153] shorten output when ImportError due to app bug. Before: ``` C:\dev\tmp>py -2 -m flask run Traceback (most recent call last): File "C:\Python27\lib\runpy.py", line 174, in _run_module_as_main "__main__", fname, loader, pkg_name) File "C:\Python27\lib\runpy.py", line 72, in _run_code exec code in run_globals File "c:\dev\sourcetree\flask\flask\__main__.py", line 15, in main(as_module=True) File "c:\dev\sourcetree\flask\flask\cli.py", line 523, in main cli.main(args=args, prog_name=name) File "c:\dev\sourcetree\flask\flask\cli.py", line 383, in main return AppGroup.main(self, *args, **kwargs) File "C:\Python27\lib\site-packages\click\core.py", line 697, in main rv = self.invoke(ctx) File "C:\Python27\lib\site-packages\click\core.py", line 1066, in invoke return _process_result(sub_ctx.command.invoke(sub_ctx)) File "C:\Python27\lib\site-packages\click\core.py", line 895, in invoke return ctx.invoke(self.callback, **ctx.params) File "C:\Python27\lib\site-packages\click\core.py", line 535, in invoke return callback(*args, **kwargs) File "C:\Python27\lib\site-packages\click\decorators.py", line 64, in new_func return ctx.invoke(f, obj, *args[1:], **kwargs) File "C:\Python27\lib\site-packages\click\core.py", line 535, in invoke return callback(*args, **kwargs) File "c:\dev\sourcetree\flask\flask\cli.py", line 433, in run_command app = DispatchingApp(info.load_app, use_eager_loading=eager_loading) File "c:\dev\sourcetree\flask\flask\cli.py", line 153, in __init__ self._load_unlocked() File "c:\dev\sourcetree\flask\flask\cli.py", line 177, in _load_unlocked self._app = rv = self.loader() File "c:\dev\sourcetree\flask\flask\cli.py", line 238, in load_app rv = locate_app(self.app_import_path) File "c:\dev\sourcetree\flask\flask\cli.py", line 91, in locate_app __import__(module) File "C:\dev\tmp\error.py", line 1, in import whatisthisidonteven ImportError: No module named whatisthisidonteven ``` After: ``` C:\dev\tmp>py -2 -m flask run Usage: python -m flask run [OPTIONS] Error: There was an error trying to import the app (error): Traceback (most recent call last): File "c:\dev\sourcetree\flask\flask\cli.py", line 91, in locate_app __import__(module) File "C:\dev\tmp\error.py", line 1, in import whatisthisidonteven ImportError: No module named whatisthisidonteven``` --- flask/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flask/cli.py b/flask/cli.py index 8db7e07e..0cc240a2 100644 --- a/flask/cli.py +++ b/flask/cli.py @@ -93,7 +93,9 @@ def locate_app(app_id): # Reraise the ImportError if it occurred within the imported module. # Determine this by checking whether the trace has a depth > 1. if sys.exc_info()[-1].tb_next: - raise + stack_trace = traceback.format_exc() + raise NoAppException('There was an error trying to import' + ' the app (%s):\n%s' % (module, stack_trace)) else: raise NoAppException('The file/path provided (%s) does not appear' ' to exist. Please verify the path is ' From 9c40039620e5cb2970c00cf78c8fd02a47b2861b Mon Sep 17 00:00:00 2001 From: Ed Brannin Date: Tue, 21 Mar 2017 16:17:09 -0400 Subject: [PATCH 124/153] Fix CLI test for ImportError -> NoAppException --- tests/test_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 82c69f93..8b291a63 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -83,7 +83,7 @@ def test_locate_app(test_apps): pytest.raises(NoAppException, locate_app, "notanpp.py") pytest.raises(NoAppException, locate_app, "cliapp/app") pytest.raises(RuntimeError, locate_app, "cliapp.app:notanapp") - pytest.raises(ImportError, locate_app, "cliapp.importerrorapp") + pytest.raises(NoAppException, locate_app, "cliapp.importerrorapp") def test_find_default_import_path(test_apps, monkeypatch, tmpdir): From 0d07974a4986a664cb4237b0c2d34857a2bfc7d3 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 23 Mar 2017 17:30:48 +0100 Subject: [PATCH 125/153] Fix send_file to work with non-ascii filenames This commit implements https://tools.ietf.org/html/rfc2231#section-4 in order to support sending unicode characters. Tested on both Firefox and Chromium under Linux. This adds unidecode as a dependency, which might be relaxed by using .encode('latin-1', 'ignore') but wouldn't be as useful. Also, added a test for the correct headers to be added. Previously, using a filename parameter to send_file with unicode characters, it failed with the next error since HTTP headers don't allow non latin-1 characters. Error on request: Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 193, in run_wsgi execute(self.server.app) File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 186, in execute write(b'') File "/usr/lib/python3.6/site-packages/werkzeug/serving.py", line 152, in write self.send_header(key, value) File "/usr/lib64/python3.6/http/server.py", line 509, in send_header ("%s: %s\r\n" % (keyword, value)).encode('latin-1', 'strict')) UnicodeEncodeError: 'latin-1' codec can't encode character '\uff0f' in position 58: ordinal not in range(256) Fixes #1286 --- flask/helpers.py | 6 +++++- setup.py | 1 + tests/test_helpers.py | 11 +++++++++++ tox.ini | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 2f446327..b2ea2ce9 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -41,6 +41,7 @@ from .signals import message_flashed from .globals import session, _request_ctx_stack, _app_ctx_stack, \ current_app, request from ._compat import string_types, text_type +from unidecode import unidecode # sentinel @@ -534,8 +535,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if attachment_filename is None: raise TypeError('filename unavailable, required for ' 'sending as attachment') + filename_dict = { + 'filename': unidecode(attachment_filename), + 'filename*': "UTF-8''%s" % url_quote(attachment_filename)} headers.add('Content-Disposition', 'attachment', - filename=attachment_filename) + **filename_dict) if current_app.use_x_sendfile and filename: if file is not None: diff --git a/setup.py b/setup.py index 08995073..2ffe72ed 100644 --- a/setup.py +++ b/setup.py @@ -75,6 +75,7 @@ setup( 'Jinja2>=2.4', 'itsdangerous>=0.21', 'click>=2.0', + 'unidecode', ], classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tests/test_helpers.py b/tests/test_helpers.py index fd448fb8..362b6cfa 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -560,6 +560,17 @@ class TestSendfile(object): assert options['filename'] == 'index.txt' rv.close() + def test_attachment_with_utf8_filename(self): + app = flask.Flask(__name__) + with app.test_request_context(): + with open(os.path.join(app.root_path, 'static/index.html')) as f: + rv = flask.send_file(f, as_attachment=True, + attachment_filename='Ñandú/pingüino.txt') + value, options = \ + parse_options_header(rv.headers['Content-Disposition']) + assert options == {'filename': 'Nandu/pinguino.txt', 'filename*': "UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"} + rv.close() + def test_static_file(self): app = flask.Flask(__name__) # default cache timeout is 12 hours diff --git a/tox.ini b/tox.ini index 764b4030..3db7b91f 100644 --- a/tox.ini +++ b/tox.ini @@ -27,6 +27,7 @@ deps= devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/jek/blinker.git simplejson: simplejson + unidecode [testenv:docs] deps = sphinx From 5e8ac066e4c5e4f8d59c18003cea25fb4da618ed Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Fri, 24 Mar 2017 20:05:01 +0100 Subject: [PATCH 126/153] Fix previous commits to work with python 2 and python 3 Also, parse_options_header seems to interpret filename* so we better test the actual value used in the headers (and since it's valid in any order, use a set to compare) --- flask/helpers.py | 2 +- tests/test_helpers.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index b2ea2ce9..e4fb8c43 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -536,7 +536,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, raise TypeError('filename unavailable, required for ' 'sending as attachment') filename_dict = { - 'filename': unidecode(attachment_filename), + 'filename': unidecode(text_type(attachment_filename)), 'filename*': "UTF-8''%s" % url_quote(attachment_filename)} headers.add('Content-Disposition', 'attachment', **filename_dict) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 362b6cfa..f7affb2c 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -565,10 +565,11 @@ class TestSendfile(object): with app.test_request_context(): with open(os.path.join(app.root_path, 'static/index.html')) as f: rv = flask.send_file(f, as_attachment=True, - attachment_filename='Ñandú/pingüino.txt') - value, options = \ - parse_options_header(rv.headers['Content-Disposition']) - assert options == {'filename': 'Nandu/pinguino.txt', 'filename*': "UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"} + attachment_filename=u'Ñandú/pingüino.txt') + content_disposition = set(rv.headers['Content-Disposition'].split(';')) + assert content_disposition == set(['attachment', + ' filename="Nandu/pinguino.txt"', + " filename*=UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"]) rv.close() def test_static_file(self): From ebce4e2fc3def1cd2caa72df505c28e54bd05591 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Thu, 30 Mar 2017 17:32:21 +0200 Subject: [PATCH 127/153] Remove unidecode dependency and use unicodedata instead I found a way to remove the unidecode dependency without sacrificing much by using unicodedata.normalize . --- flask/helpers.py | 6 ++++-- setup.py | 1 - tox.ini | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index e4fb8c43..aa8be315 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -14,6 +14,7 @@ import sys import pkgutil import posixpath import mimetypes +import unicodedata from time import time from zlib import adler32 from threading import RLock @@ -41,7 +42,6 @@ from .signals import message_flashed from .globals import session, _request_ctx_stack, _app_ctx_stack, \ current_app, request from ._compat import string_types, text_type -from unidecode import unidecode # sentinel @@ -536,7 +536,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, raise TypeError('filename unavailable, required for ' 'sending as attachment') filename_dict = { - 'filename': unidecode(text_type(attachment_filename)), + 'filename': (unicodedata.normalize('NFKD', + text_type(attachment_filename)).encode('ascii', + 'ignore')), 'filename*': "UTF-8''%s" % url_quote(attachment_filename)} headers.add('Content-Disposition', 'attachment', **filename_dict) diff --git a/setup.py b/setup.py index 2ffe72ed..08995073 100644 --- a/setup.py +++ b/setup.py @@ -75,7 +75,6 @@ setup( 'Jinja2>=2.4', 'itsdangerous>=0.21', 'click>=2.0', - 'unidecode', ], classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tox.ini b/tox.ini index 3db7b91f..764b4030 100644 --- a/tox.ini +++ b/tox.ini @@ -27,7 +27,6 @@ deps= devel: git+https://github.com/pallets/itsdangerous.git devel: git+https://github.com/jek/blinker.git simplejson: simplejson - unidecode [testenv:docs] deps = sphinx From 2774221987699291fb4808f180aeaf71777d34fd Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Fri, 31 Mar 2017 17:07:43 +0100 Subject: [PATCH 128/153] Handle BaseExceptions (#2222) * Handle BaseExceptions * Add test and changes * Make test more idiomatic --- CHANGES | 3 +++ flask/app.py | 3 +++ tests/test_basic.py | 17 +++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/CHANGES b/CHANGES index 7c50d0c7..6933c0c9 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,9 @@ Major release, unreleased - Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() method returns compressed response by default, and pretty response in debug mode. +- Call `ctx.auto_pop` with the exception object instead of `None`, in the + event that a `BaseException` such as `KeyboardInterrupt` is raised in a + request handler. Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index c8540b5f..6617b02b 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1991,6 +1991,9 @@ class Flask(_PackageBoundObject): except Exception as e: error = e response = self.handle_exception(e) + except: + error = sys.exc_info()[1] + raise return response(environ, start_response) finally: if self.should_ignore_error(error): diff --git a/tests/test_basic.py b/tests/test_basic.py index 942eb0f6..ffc12dc1 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -791,6 +791,23 @@ def test_error_handling_processing(): assert resp.data == b'internal server error' +def test_baseexception_error_handling(): + app = flask.Flask(__name__) + app.config['LOGGER_HANDLER_POLICY'] = 'never' + + @app.route('/') + def broken_func(): + raise KeyboardInterrupt() + + with app.test_client() as c: + with pytest.raises(KeyboardInterrupt): + c.get('/') + + ctx = flask._request_ctx_stack.top + assert ctx.preserved + assert type(ctx._preserved_exc) is KeyboardInterrupt + + def test_before_request_and_routing_errors(): app = flask.Flask(__name__) From d41d0803ef6c149462d51d9c78e800c4d24f85c5 Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Thu, 23 Mar 2017 14:43:56 +0000 Subject: [PATCH 129/153] Handle BaseExceptions --- flask/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flask/app.py b/flask/app.py index 942992dc..1404e17e 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1983,6 +1983,9 @@ class Flask(_PackageBoundObject): except Exception as e: error = e response = self.handle_exception(e) + except: + error = sys.exc_info()[1] + raise return response(environ, start_response) finally: if self.should_ignore_error(error): From 965d444e0f56db9b253b18bafcf4c9063e3b09a2 Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Thu, 23 Mar 2017 16:15:00 +0000 Subject: [PATCH 130/153] Add test and changes --- CHANGES | 15 +++++++++++++++ tests/test_basic.py | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/CHANGES b/CHANGES index 03194421..dbb5947a 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,21 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.13 +------------ + +Major release, unreleased + +- Make `app.run()` into a noop if a Flask application is run from the + development server on the command line. This avoids some behavior that + was confusing to debug for newcomers. +- Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() + method returns compressed response by default, and pretty response in + debug mode. +- Call `ctx.auto_pop` with the exception object instead of `None`, in the + event that a `BaseException` such as `KeyboardInterrupt` is raised in a + request handler. + Version 0.12.1 -------------- diff --git a/tests/test_basic.py b/tests/test_basic.py index be3d5edd..8556268a 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -791,6 +791,26 @@ def test_error_handling_processing(): assert resp.data == b'internal server error' +def test_baseexception_error_handling(): + app = flask.Flask(__name__) + app.config['LOGGER_HANDLER_POLICY'] = 'never' + + @app.route('/') + def broken_func(): + raise KeyboardInterrupt() + + with app.test_client() as c: + try: + c.get('/') + raise AssertionError("KeyboardInterrupt should have been raised") + except KeyboardInterrupt: + pass + + ctx = flask._request_ctx_stack.top + assert ctx.preserved + assert type(ctx._preserved_exc) is KeyboardInterrupt + + def test_before_request_and_routing_errors(): app = flask.Flask(__name__) From 65cf64e01984cef1bd91cfd0acf7090e6d6f2084 Mon Sep 17 00:00:00 2001 From: Diggory Blake Date: Thu, 23 Mar 2017 17:51:45 +0000 Subject: [PATCH 131/153] Make test more idiomatic --- tests/test_basic.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_basic.py b/tests/test_basic.py index 8556268a..c5ec9f5c 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -800,11 +800,8 @@ def test_baseexception_error_handling(): raise KeyboardInterrupt() with app.test_client() as c: - try: + with pytest.raises(KeyboardInterrupt): c.get('/') - raise AssertionError("KeyboardInterrupt should have been raised") - except KeyboardInterrupt: - pass ctx = flask._request_ctx_stack.top assert ctx.preserved From 5b10ba89cec6fc3734c778b79dc4293c6b8151e0 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:41:10 +0200 Subject: [PATCH 132/153] Correct changelog --- CHANGES | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index dbb5947a..99ae5803 100644 --- a/CHANGES +++ b/CHANGES @@ -14,9 +14,6 @@ Major release, unreleased - Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() method returns compressed response by default, and pretty response in debug mode. -- Call `ctx.auto_pop` with the exception object instead of `None`, in the - event that a `BaseException` such as `KeyboardInterrupt` is raised in a - request handler. Version 0.12.1 -------------- @@ -27,6 +24,9 @@ Bugfix release, unreleased within the imported application module. - Fix encoding behavior of ``app.config.from_pyfile`` for Python 3. Fix ``#2118``. +- Call `ctx.auto_pop` with the exception object instead of `None`, in the + event that a `BaseException` such as `KeyboardInterrupt` is raised in a + request handler. Version 0.12 ------------ From 6b77184efb0ee283445a0aef11ff4ed5d73e2191 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:43:34 +0200 Subject: [PATCH 133/153] Prepare for 0.12.1 --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 99ae5803..613b8189 100644 --- a/CHANGES +++ b/CHANGES @@ -18,7 +18,7 @@ Major release, unreleased Version 0.12.1 -------------- -Bugfix release, unreleased +Bugfix release, released on March 31st 2017 - Prevent `flask run` from showing a NoAppException when an ImportError occurs within the imported application module. From c6a9895dc647d51ba66f9c0f8898fbabf72194c4 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:43:36 +0200 Subject: [PATCH 134/153] Bump version number to 0.12.1 --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 3cef3b43..2fcb3567 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12.1-dev' +__version__ = '0.12.1' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From 0cbee768d3beb938f91123f8f83827aa6d2a3774 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:43:52 +0200 Subject: [PATCH 135/153] Bump to dev version --- flask/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/__init__.py b/flask/__init__.py index 2fcb3567..59d711b8 100644 --- a/flask/__init__.py +++ b/flask/__init__.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ -__version__ = '0.12.1' +__version__ = '0.12.2-dev' # utilities we import from Werkzeug and Jinja2 that are unused # in the module but are exported as public interface. From 247a0c906d3622f1eff866a4dd9bbd6797474cb5 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 31 Mar 2017 18:44:14 +0200 Subject: [PATCH 136/153] Revert "Handle BaseExceptions (#2222)" This reverts commit 1d4448abe335741c61b3c8c5f99e1607a13f7e3d. --- CHANGES | 3 --- flask/app.py | 3 --- tests/test_basic.py | 17 ----------------- 3 files changed, 23 deletions(-) diff --git a/CHANGES b/CHANGES index 6933c0c9..7c50d0c7 100644 --- a/CHANGES +++ b/CHANGES @@ -14,9 +14,6 @@ Major release, unreleased - Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() method returns compressed response by default, and pretty response in debug mode. -- Call `ctx.auto_pop` with the exception object instead of `None`, in the - event that a `BaseException` such as `KeyboardInterrupt` is raised in a - request handler. Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index 6617b02b..c8540b5f 100644 --- a/flask/app.py +++ b/flask/app.py @@ -1991,9 +1991,6 @@ class Flask(_PackageBoundObject): except Exception as e: error = e response = self.handle_exception(e) - except: - error = sys.exc_info()[1] - raise return response(environ, start_response) finally: if self.should_ignore_error(error): diff --git a/tests/test_basic.py b/tests/test_basic.py index ffc12dc1..942eb0f6 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -791,23 +791,6 @@ def test_error_handling_processing(): assert resp.data == b'internal server error' -def test_baseexception_error_handling(): - app = flask.Flask(__name__) - app.config['LOGGER_HANDLER_POLICY'] = 'never' - - @app.route('/') - def broken_func(): - raise KeyboardInterrupt() - - with app.test_client() as c: - with pytest.raises(KeyboardInterrupt): - c.get('/') - - ctx = flask._request_ctx_stack.top - assert ctx.preserved - assert type(ctx._preserved_exc) is KeyboardInterrupt - - def test_before_request_and_routing_errors(): app = flask.Flask(__name__) From 742a143efd32c909e17c2f6692c5725f428154ca Mon Sep 17 00:00:00 2001 From: Adam Geitgey Date: Tue, 4 Apr 2017 13:26:40 -0700 Subject: [PATCH 137/153] Correct imports in file upload example (#2230) The example code uses `flash` but doesn't import it. So the code as written doesn't work. This simply adds `flash` to the list of imports in the sample code. --- docs/patterns/fileuploads.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst index 3a42d325..1c4b0d36 100644 --- a/docs/patterns/fileuploads.rst +++ b/docs/patterns/fileuploads.rst @@ -21,7 +21,7 @@ specific upload folder and displays a file to the user. Let's look at the bootstrapping code for our application:: import os - from flask import Flask, request, redirect, url_for + from flask import Flask, flash, request, redirect, url_for from werkzeug.utils import secure_filename UPLOAD_FOLDER = '/path/to/the/uploads' From d915c390a2544468b69ea650739bcffa98ef91b0 Mon Sep 17 00:00:00 2001 From: asilversempirical Date: Thu, 6 Apr 2017 11:26:01 -0400 Subject: [PATCH 138/153] Update out of date jsonify documentation https://github.com/pallets/flask/pull/2193 changed the conditions for when jsonify pretty prints, but this comment wasn't updated. --- flask/json.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/flask/json.py b/flask/json.py index 0ba9d717..825dcdf1 100644 --- a/flask/json.py +++ b/flask/json.py @@ -236,11 +236,10 @@ def jsonify(*args, **kwargs): Added support for serializing top-level arrays. This introduces a security risk in ancient browsers. See :ref:`json-security` for details. - This function's response will be pretty printed if it was not requested - with ``X-Requested-With: XMLHttpRequest`` to simplify debugging unless - the ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to false. - Compressed (not pretty) formatting currently means no indents and no - spaces after separators. + This function's response will be pretty printed if the + ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the + Flask app is running in debug mode. Compressed (not pretty) formatting + currently means no indents and no spaces after separators. .. versionadded:: 0.2 """ From 3d20e9184df52d297b57123cc1c913eed2c36d38 Mon Sep 17 00:00:00 2001 From: Grey Li Date: Fri, 7 Apr 2017 22:10:43 +0800 Subject: [PATCH 139/153] Add example for virtualenv integration in cli docs (#2234) --- docs/cli.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/cli.rst b/docs/cli.rst index 2ca0e83e..d0b033f6 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -56,6 +56,18 @@ If you are constantly working with a virtualenv you can also put the bottom of the file. That way every time you activate your virtualenv you automatically also activate the correct application name. +Edit the activate script for the shell you use. For example: + +Unix Bash: ``venv/bin/activate``:: + + FLASK_APP=hello + export FLASK_APP + +Windows CMD.exe: ``venv\Scripts\activate.bat``:: + + set "FLASK_APP=hello" + :END + Debug Flag ---------- From 8a2db3db18b38068038d65166e02195884a91f28 Mon Sep 17 00:00:00 2001 From: jab Date: Fri, 7 Apr 2017 10:31:54 -0400 Subject: [PATCH 140/153] Change Flask.__init__ to accept two new keyword arguments, host_matching and static_host. (#1560) This enables host_matching to be set properly by the time the constructor adds the static route, and enables the static route to be properly associated with the required host. Previously, you could only enable host_matching once your app was already instantiated (e.g. app.url_map.host_matching = True), but at that point the constructor would have already added the static route without host matching and an associated host, leaving the static route in a broken state. Fixes #1559. --- AUTHORS | 1 + CHANGES | 5 +++++ flask/app.py | 28 +++++++++++++++++++++------- tests/test_basic.py | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index cc157dc4..33210243 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,6 +21,7 @@ Patches and Suggestions - Florent Xicluna - Georg Brandl - Jeff Widman @jeffwidman +- Joshua Bronson @jab - Justin Quick - Kenneth Reitz - Keyan Pishdadian diff --git a/CHANGES b/CHANGES index 9500ee17..440cb260 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,11 @@ Major release, unreleased - Change default configuration `JSONIFY_PRETTYPRINT_REGULAR=False`. jsonify() method returns compressed response by default, and pretty response in debug mode. +- Change Flask.__init__ to accept two new keyword arguments, ``host_matching`` + and ``static_host``. This enables ``host_matching`` to be set properly by the + time the constructor adds the static route, and enables the static route to + be properly associated with the required host. (``#1559``) + Version 0.12.1 -------------- diff --git a/flask/app.py b/flask/app.py index 6617b02b..87621aee 100644 --- a/flask/app.py +++ b/flask/app.py @@ -123,6 +123,9 @@ class Flask(_PackageBoundObject): .. versionadded:: 0.11 The `root_path` parameter was added. + .. versionadded:: 0.13 + The `host_matching` and `static_host` parameters were added. + :param import_name: the name of the application package :param static_url_path: can be used to specify a different path for the static files on the web. Defaults to the name @@ -130,6 +133,13 @@ class Flask(_PackageBoundObject): :param static_folder: the folder with static files that should be served at `static_url_path`. Defaults to the ``'static'`` folder in the root path of the application. + folder in the root path of the application. Defaults + to None. + :param host_matching: sets the app's ``url_map.host_matching`` to the given + given value. Defaults to False. + :param static_host: the host to use when adding the static route. Defaults + to None. Required when using ``host_matching=True`` + with a ``static_folder`` configured. :param template_folder: the folder that contains the templates that should be used by the application. Defaults to ``'templates'`` folder in the root path of the @@ -337,7 +347,8 @@ class Flask(_PackageBoundObject): session_interface = SecureCookieSessionInterface() def __init__(self, import_name, static_path=None, static_url_path=None, - static_folder='static', template_folder='templates', + static_folder='static', static_host=None, + host_matching=False, template_folder='templates', instance_path=None, instance_relative_config=False, root_path=None): _PackageBoundObject.__init__(self, import_name, @@ -525,19 +536,22 @@ class Flask(_PackageBoundObject): #: app.url_map.converters['list'] = ListConverter self.url_map = Map() + self.url_map.host_matching = host_matching + # tracks internally if the application already handled at least one # request. self._got_first_request = False self._before_request_lock = Lock() - # register the static folder for the application. Do that even - # if the folder does not exist. First of all it might be created - # while the server is running (usually happens during development) - # but also because google appengine stores static files somewhere - # else when mapped with the .yml file. + # Add a static route using the provided static_url_path, static_host, + # and static_folder iff there is a configured static_folder. + # Note we do this without checking if static_folder exists. + # For one, it might be created while the server is running (e.g. during + # development). Also, Google App Engine stores static files somewhere if self.has_static_folder: + assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination' self.add_url_rule(self.static_url_path + '/', - endpoint='static', + endpoint='static', host=static_host, view_func=self.send_static_file) #: The click command line context for this application. Commands diff --git a/tests/test_basic.py b/tests/test_basic.py index ffc12dc1..76efd6e2 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1188,6 +1188,25 @@ def test_static_url_path(): assert flask.url_for('static', filename='index.html') == '/foo/index.html' +def test_static_route_with_host_matching(): + app = flask.Flask(__name__, host_matching=True, static_host='example.com') + c = app.test_client() + rv = c.get('http://example.com/static/index.html') + assert rv.status_code == 200 + rv.close() + with app.test_request_context(): + rv = flask.url_for('static', filename='index.html', _external=True) + assert rv == 'http://example.com/static/index.html' + # Providing static_host without host_matching=True should error. + with pytest.raises(Exception): + flask.Flask(__name__, static_host='example.com') + # Providing host_matching=True with static_folder but without static_host should error. + with pytest.raises(Exception): + flask.Flask(__name__, host_matching=True) + # Providing host_matching=True without static_host but with static_folder=None should not error. + flask.Flask(__name__, host_matching=True, static_folder=None) + + def test_none_response(): app = flask.Flask(__name__) app.testing = True From cca5c27a671c698484ae72f6736e91c392f07339 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Fri, 7 Apr 2017 20:34:52 +0200 Subject: [PATCH 141/153] Keep using only filename if it's valid ascii --- flask/helpers.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index aa8be315..19d33420 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -14,7 +14,7 @@ import sys import pkgutil import posixpath import mimetypes -import unicodedata +from unicodedata import normalize from time import time from zlib import adler32 from threading import RLock @@ -535,13 +535,22 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if attachment_filename is None: raise TypeError('filename unavailable, required for ' 'sending as attachment') - filename_dict = { - 'filename': (unicodedata.normalize('NFKD', - text_type(attachment_filename)).encode('ascii', - 'ignore')), - 'filename*': "UTF-8''%s" % url_quote(attachment_filename)} + normalized = normalize('NFKD', text_type(attachment_filename)) + + try: + normalized.encode('ascii') + except UnicodeEncodeError: + filenames = { + 'filename': normalized.encode('ascii', 'ignore'), + 'filename*': "UTF-8''%s" % url_quote(attachment_filename), + } + else: + filenames = { + 'filename': attachment_filename, + } + headers.add('Content-Disposition', 'attachment', - **filename_dict) + **filenames) if current_app.use_x_sendfile and filename: if file is not None: From 100863e4fdcd23dae66d4ac4ac45d8899face519 Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 7 Apr 2017 18:02:31 -0700 Subject: [PATCH 142/153] style cleanup break out header parts in test test for no filename* parameter for ascii header --- flask/helpers.py | 19 ++++++++++++------- tests/test_helpers.py | 25 ++++++++++++++++--------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 19d33420..420467ad 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -14,10 +14,10 @@ import sys import pkgutil import posixpath import mimetypes -from unicodedata import normalize from time import time from zlib import adler32 from threading import RLock +import unicodedata from werkzeug.routing import BuildError from functools import update_wrapper @@ -478,6 +478,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, .. versionchanged:: 0.12 The `attachment_filename` is preferred over `filename` for MIME-type detection. + + .. versionchanged:: 0.13 + UTF-8 filenames, as specified in `RFC 2231`_, are supported. + + .. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4 :param filename_or_fp: the filename of the file to send in `latin-1`. This is relative to the :attr:`~Flask.root_path` @@ -535,7 +540,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, if attachment_filename is None: raise TypeError('filename unavailable, required for ' 'sending as attachment') - normalized = normalize('NFKD', text_type(attachment_filename)) + + normalized = unicodedata.normalize( + 'NFKD', text_type(attachment_filename) + ) try: normalized.encode('ascii') @@ -545,12 +553,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, 'filename*': "UTF-8''%s" % url_quote(attachment_filename), } else: - filenames = { - 'filename': attachment_filename, - } + filenames = {'filename': attachment_filename} - headers.add('Content-Disposition', 'attachment', - **filenames) + headers.add('Content-Disposition', 'attachment', **filenames) if current_app.use_x_sendfile and filename: if file is not None: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index f7affb2c..1aeff065 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -540,10 +540,11 @@ class TestSendfile(object): value, options = \ parse_options_header(rv.headers['Content-Disposition']) assert value == 'attachment' + assert options['filename'] == 'index.html' + assert 'filename*' not in options rv.close() with app.test_request_context(): - assert options['filename'] == 'index.html' rv = flask.send_file('static/index.html', as_attachment=True) value, options = parse_options_header(rv.headers['Content-Disposition']) assert value == 'attachment' @@ -562,15 +563,21 @@ class TestSendfile(object): def test_attachment_with_utf8_filename(self): app = flask.Flask(__name__) + with app.test_request_context(): - with open(os.path.join(app.root_path, 'static/index.html')) as f: - rv = flask.send_file(f, as_attachment=True, - attachment_filename=u'Ñandú/pingüino.txt') - content_disposition = set(rv.headers['Content-Disposition'].split(';')) - assert content_disposition == set(['attachment', - ' filename="Nandu/pinguino.txt"', - " filename*=UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt"]) - rv.close() + rv = flask.send_file( + 'static/index.html', as_attachment=True, + attachment_filename=u'Ñandú/pingüino.txt' + ) + value, options = parse_options_header( + rv.headers['Content-Disposition'] + ) + rv.close() + + assert value == 'attachment' + assert sorted(options.keys()) == ('filename', 'filename*') + assert options['filename'] == 'Nandu/pinguino.txt' + assert options['filename*'] == 'UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt' def test_static_file(self): app = flask.Flask(__name__) From 01e6088c414b46546938f587f9d5583927629105 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 8 Apr 2017 10:33:06 -0700 Subject: [PATCH 143/153] need to test against raw header parsing prefers the last value parsed for the option --- tests/test_helpers.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 1aeff065..d93b443e 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -541,7 +541,7 @@ class TestSendfile(object): parse_options_header(rv.headers['Content-Disposition']) assert value == 'attachment' assert options['filename'] == 'index.html' - assert 'filename*' not in options + assert 'filename*' not in rv.headers['Content-Disposition'] rv.close() with app.test_request_context(): @@ -565,20 +565,15 @@ class TestSendfile(object): app = flask.Flask(__name__) with app.test_request_context(): - rv = flask.send_file( - 'static/index.html', as_attachment=True, - attachment_filename=u'Ñandú/pingüino.txt' - ) - value, options = parse_options_header( - rv.headers['Content-Disposition'] - ) + rv = flask.send_file('static/index.html', as_attachment=True, attachment_filename=u'Ñandú/pingüino.txt') + content_disposition = set(rv.headers['Content-Disposition'].split('; ')) + assert content_disposition == set(( + 'attachment', + 'filename="Nandu/pinguino.txt"', + "filename*=UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt" + )) rv.close() - assert value == 'attachment' - assert sorted(options.keys()) == ('filename', 'filename*') - assert options['filename'] == 'Nandu/pinguino.txt' - assert options['filename*'] == 'UTF-8''%C3%91and%C3%BA%EF%BC%8Fping%C3%BCino.txt' - def test_static_file(self): app = flask.Flask(__name__) # default cache timeout is 12 hours From d13febda57fa032eaeb8571cfd997fe328c0733a Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 8 Apr 2017 11:08:08 -0700 Subject: [PATCH 144/153] add changelog for #2223 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 440cb260..1f687409 100644 --- a/CHANGES +++ b/CHANGES @@ -18,7 +18,9 @@ Major release, unreleased and ``static_host``. This enables ``host_matching`` to be set properly by the time the constructor adds the static route, and enables the static route to be properly associated with the required host. (``#1559``) +- ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_) +.. _#2223: https://github.com/pallets/flask/pull/2223 Version 0.12.1 -------------- From 5a76bbe7c73069bf2cf9d9ced11a616828d7383d Mon Sep 17 00:00:00 2001 From: ka7 Date: Tue, 11 Apr 2017 21:44:32 +0200 Subject: [PATCH 145/153] Fix typo in docs (#2237) --- docs/patterns/packages.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/patterns/packages.rst b/docs/patterns/packages.rst index 1bb84f8c..cc149839 100644 --- a/docs/patterns/packages.rst +++ b/docs/patterns/packages.rst @@ -65,7 +65,7 @@ that tells Flask where to find the application instance:: export FLASK_APP=yourapplication If you are outside of the project directory make sure to provide the exact -path to your application directory. Similiarly you can turn on "debug +path to your application directory. Similarly you can turn on "debug mode" with this environment variable:: export FLASK_DEBUG=true From 50851d6a60099756a39e03a711f8ec42e21118bd Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 12 Apr 2017 09:18:07 -0700 Subject: [PATCH 146/153] filename can be latin-1, not just ascii only normalize basic name when utf-8 header is needed ref #2223 --- flask/helpers.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/flask/helpers.py b/flask/helpers.py index 420467ad..bfdcf224 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -484,7 +484,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, .. _RFC 2231: https://tools.ietf.org/html/rfc2231#section-4 - :param filename_or_fp: the filename of the file to send in `latin-1`. + :param filename_or_fp: the filename of the file to send. This is relative to the :attr:`~Flask.root_path` if a relative path is specified. Alternatively a file object might be provided in @@ -541,15 +541,12 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, raise TypeError('filename unavailable, required for ' 'sending as attachment') - normalized = unicodedata.normalize( - 'NFKD', text_type(attachment_filename) - ) - try: - normalized.encode('ascii') + attachment_filename = attachment_filename.encode('latin-1') except UnicodeEncodeError: filenames = { - 'filename': normalized.encode('ascii', 'ignore'), + 'filename': unicodedata.normalize( + 'NFKD', attachment_filename).encode('latin-1', 'ignore'), 'filename*': "UTF-8''%s" % url_quote(attachment_filename), } else: From c6bb311175c15da3c85493c38a3ae9315fa37575 Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 Apr 2017 14:55:56 -0700 Subject: [PATCH 147/153] get mtime in utc --- tests/test_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_helpers.py b/tests/test_helpers.py index d93b443e..d3906860 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -517,7 +517,7 @@ class TestSendfile(object): assert rv.status_code == 416 rv.close() - last_modified = datetime.datetime.fromtimestamp(os.path.getmtime( + last_modified = datetime.datetime.utcfromtimestamp(os.path.getmtime( os.path.join(app.root_path, 'static/index.html'))).replace( microsecond=0) From f3f4aa325b0200d465327e707a2223c3b002f384 Mon Sep 17 00:00:00 2001 From: accraze Date: Mon, 19 Dec 2016 08:03:37 -0800 Subject: [PATCH 148/153] Added missing testing config fixes #1302 --- docs/testing.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/testing.rst b/docs/testing.rst index 0737936e..edaa597c 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -98,8 +98,10 @@ test method to our class, like this:: def setUp(self): self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() + flaskr.app.config['TESTING'] = True self.app = flaskr.app.test_client() - flaskr.init_db() + with flaskr.app.app_context(): + flaskr.init_db() def tearDown(self): os.close(self.db_fd) From 0f514cea8f3bd1317ab2988214c7f3d8d18c03ac Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 13 Apr 2017 16:32:44 -0700 Subject: [PATCH 149/153] use app.testing property instead of config --- docs/testing.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index edaa597c..6fd7b504 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -41,7 +41,7 @@ In order to test the application, we add a second module def setUp(self): self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() - flaskr.app.config['TESTING'] = True + flaskr.app.testing = True self.app = flaskr.app.test_client() with flaskr.app.app_context(): flaskr.init_db() @@ -98,7 +98,7 @@ test method to our class, like this:: def setUp(self): self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() - flaskr.app.config['TESTING'] = True + flaskr.app.testing = True self.app = flaskr.app.test_client() with flaskr.app.app_context(): flaskr.init_db() @@ -210,7 +210,7 @@ temporarily. With this you can access the :class:`~flask.request`, functions. Here is a full example that demonstrates this approach:: import flask - + app = flask.Flask(__name__) with app.test_request_context('/?name=Peter'): From cce1d62fcf5584b42da3baaabe2bad5cb313035a Mon Sep 17 00:00:00 2001 From: Sobolev Nikita Date: Wed, 19 Apr 2017 08:46:33 +0300 Subject: [PATCH 150/153] Fix typo in app.py (#2248) --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 87621aee..c26387f0 100644 --- a/flask/app.py +++ b/flask/app.py @@ -544,7 +544,7 @@ class Flask(_PackageBoundObject): self._before_request_lock = Lock() # Add a static route using the provided static_url_path, static_host, - # and static_folder iff there is a configured static_folder. + # and static_folder if there is a configured static_folder. # Note we do this without checking if static_folder exists. # For one, it might be created while the server is running (e.g. during # development). Also, Google App Engine stores static files somewhere From e111d8dbf3739ee217344329d224fda7675114a4 Mon Sep 17 00:00:00 2001 From: rocambolesque Date: Fri, 9 Sep 2016 12:11:18 +0200 Subject: [PATCH 151/153] Add scheme to url_build error handler parameters --- CHANGES | 3 +++ flask/helpers.py | 1 + 2 files changed, 4 insertions(+) diff --git a/CHANGES b/CHANGES index 1f687409..d8f2577a 100644 --- a/CHANGES +++ b/CHANGES @@ -19,7 +19,10 @@ Major release, unreleased time the constructor adds the static route, and enables the static route to be properly associated with the required host. (``#1559``) - ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_) +- Pass ``_scheme`` argument from ``url_for`` to ``handle_build_error``. + (`#2017`_) +.. _#2017: https://github.com/pallets/flask/pull/2017 .. _#2223: https://github.com/pallets/flask/pull/2223 Version 0.12.1 diff --git a/flask/helpers.py b/flask/helpers.py index bfdcf224..828f5840 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -331,6 +331,7 @@ def url_for(endpoint, **values): values['_external'] = external values['_anchor'] = anchor values['_method'] = method + values['_scheme'] = scheme return appctx.app.handle_url_build_error(error, endpoint, values) if anchor is not None: From a2acabcc3f17e85bde688c3f772949da97ecee3d Mon Sep 17 00:00:00 2001 From: David Lord Date: Thu, 20 Apr 2017 08:52:37 -0700 Subject: [PATCH 152/153] add test for build error special values --- tests/test_basic.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_basic.py b/tests/test_basic.py index 76efd6e2..abce3ad4 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1131,6 +1131,23 @@ def test_build_error_handler_reraise(): pytest.raises(BuildError, flask.url_for, 'not.existing') +def test_url_for_passes_special_values_to_build_error_handler(): + app = flask.Flask(__name__) + + @app.url_build_error_handlers.append + def handler(error, endpoint, values): + assert values == { + '_external': False, + '_anchor': None, + '_method': None, + '_scheme': None, + } + return 'handled' + + with app.test_request_context(): + flask.url_for('/') + + def test_custom_converters(): from werkzeug.routing import BaseConverter From 3f10dd52c6224c96184196c02dcc55d094132e1a Mon Sep 17 00:00:00 2001 From: David Lord Date: Fri, 21 Apr 2017 07:16:09 -0700 Subject: [PATCH 153/153] update changelog move test next to existing test, rename reword / reflow param doc --- CHANGES | 7 ++-- flask/app.py | 11 +++--- tests/test_basic.py | 91 ++++++++++++++++++++++++--------------------- 3 files changed, 58 insertions(+), 51 deletions(-) diff --git a/CHANGES b/CHANGES index 34f7f13e..dba0112e 100644 --- a/CHANGES +++ b/CHANGES @@ -21,7 +21,11 @@ Major release, unreleased - ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_) - Pass ``_scheme`` argument from ``url_for`` to ``handle_build_error``. (`#2017`_) +- Add support for ``provide_automatic_options`` in ``add_url_rule`` to disable + adding OPTIONS method when the ``view_func`` argument is not a class. + (`#1489`_). +.. _#1489: https://github.com/pallets/flask/pull/1489 .. _#2017: https://github.com/pallets/flask/pull/2017 .. _#2223: https://github.com/pallets/flask/pull/2223 @@ -146,9 +150,6 @@ Released on May 29th 2016, codename Absinthe. - ``flask.g`` now has ``pop()`` and ``setdefault`` methods. - Turn on autoescape for ``flask.templating.render_template_string`` by default (pull request ``#1515``). -- Added support for `provide_automatic_options` in :meth:`add_url_rule` to - turn off automatic OPTIONS when the `view_func` argument is not a class - (pull request ``#1489``). - ``flask.ext`` is now deprecated (pull request ``#1484``). - ``send_from_directory`` now raises BadRequest if the filename is invalid on the server OS (pull request ``#1763``). diff --git a/flask/app.py b/flask/app.py index 32f5086c..a054d23c 100644 --- a/flask/app.py +++ b/flask/app.py @@ -980,8 +980,7 @@ class Flask(_PackageBoundObject): return iter(self._blueprint_order) @setupmethod - def add_url_rule(self, rule, endpoint=None, view_func=None, - provide_automatic_options=None, **options): + def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options): """Connects a URL rule. Works exactly like the :meth:`route` decorator. If a view_func is provided it will be registered with the endpoint. @@ -1021,10 +1020,10 @@ class Flask(_PackageBoundObject): endpoint :param view_func: the function to call when serving a request to the provided endpoint - :param provide_automatic_options: controls whether ``OPTIONS`` should - be provided automatically. If this - is not set, will check attributes on - the view or list of methods. + :param provide_automatic_options: controls whether the ``OPTIONS`` + method should be added automatically. This can also be controlled + by setting the ``view_func.provide_automatic_options = False`` + before adding the rule. :param options: the options to be forwarded to the underlying :class:`~werkzeug.routing.Rule` object. A change to Werkzeug is handling of method options. methods diff --git a/tests/test_basic.py b/tests/test_basic.py index 3fe69588..677b4be8 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -50,7 +50,7 @@ def test_options_on_multiple_rules(): assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] -def test_options_handling_disabled(): +def test_provide_automatic_options_attr(): app = flask.Flask(__name__) def index(): @@ -70,6 +70,54 @@ def test_options_handling_disabled(): assert sorted(rv.allow) == ['OPTIONS'] +def test_provide_automatic_options_kwarg(): + app = flask.Flask(__name__) + + def index(): + return flask.request.method + + def more(): + return flask.request.method + + app.add_url_rule('/', view_func=index, provide_automatic_options=False) + app.add_url_rule( + '/more', view_func=more, methods=['GET', 'POST'], + provide_automatic_options=False + ) + + c = app.test_client() + assert c.get('/').data == b'GET' + + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD'] + + # Older versions of Werkzeug.test.Client don't have an options method + if hasattr(c, 'options'): + rv = c.options('/') + else: + rv = c.open('/', method='OPTIONS') + + assert rv.status_code == 405 + + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == b'POST' + assert c.get('/more').data == b'GET' + + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'POST'] + + if hasattr(c, 'options'): + rv = c.options('/more') + else: + rv = c.open('/more', method='OPTIONS') + + assert rv.status_code == 405 + + def test_request_dispatching(): app = flask.Flask(__name__) @@ -1751,44 +1799,3 @@ def test_run_from_config(monkeypatch, host, port, expect_host, expect_port): app = flask.Flask(__name__) app.config['SERVER_NAME'] = 'pocoo.org:8080' app.run(host, port) - - -def test_disable_automatic_options(): - # Issue 1488: Add support for a kwarg to add_url_rule to disable the auto OPTIONS response - app = flask.Flask(__name__) - - def index(): - return flask.request.method - - def more(): - return flask.request.method - - app.add_url_rule('/', 'index', index, provide_automatic_options=False) - app.add_url_rule('/more', 'more', more, methods=['GET', 'POST'], provide_automatic_options=False) - - c = app.test_client() - assert c.get('/').data == b'GET' - rv = c.post('/') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD'] - # Older versions of Werkzeug.test.Client don't have an options method - if hasattr(c, 'options'): - rv = c.options('/') - else: - rv = c.open('/', method='OPTIONS') - assert rv.status_code == 405 - - rv = c.head('/') - assert rv.status_code == 200 - assert not rv.data # head truncates - assert c.post('/more').data == b'POST' - assert c.get('/more').data == b'GET' - rv = c.delete('/more') - assert rv.status_code == 405 - assert sorted(rv.allow) == ['GET', 'HEAD', 'POST'] - # Older versions of Werkzeug.test.Client don't have an options method - if hasattr(c, 'options'): - rv = c.options('/more') - else: - rv = c.open('/more', method='OPTIONS') - assert rv.status_code == 405