Browse Source

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
pull/1953/head
Kyle Lawlor 9 years ago committed by David Lord
parent
commit
17d4cb3828
  1. 4
      docs/tutorial/css.rst
  2. 28
      docs/tutorial/dbcon.rst
  3. 29
      docs/tutorial/dbinit.rst
  4. 22
      docs/tutorial/folders.rst
  5. 1
      docs/tutorial/index.rst
  6. 9
      docs/tutorial/introduction.rst
  7. 8
      docs/tutorial/schema.rst
  8. 69
      docs/tutorial/setup.rst
  9. 89
      docs/tutorial/setuptools.rst
  10. 15
      docs/tutorial/templates.rst
  11. 78
      docs/tutorial/testing.rst
  12. 41
      docs/tutorial/views.rst
  13. 1
      examples/flaskr/.gitignore
  14. 3
      examples/flaskr/MANIFEST.in
  15. 14
      examples/flaskr/README
  16. 0
      examples/flaskr/flaskr/__init__.py
  17. 0
      examples/flaskr/flaskr/flaskr.py
  18. 0
      examples/flaskr/flaskr/schema.sql
  19. 0
      examples/flaskr/flaskr/static/style.css
  20. 0
      examples/flaskr/flaskr/templates/layout.html
  21. 0
      examples/flaskr/flaskr/templates/login.html
  22. 0
      examples/flaskr/flaskr/templates/show_entries.html
  23. 2
      examples/flaskr/setup.cfg
  24. 16
      examples/flaskr/setup.py
  25. 6
      examples/flaskr/tests/context.py
  26. 3
      examples/flaskr/tests/test_flaskr.py

4
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

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

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

22
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
/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.
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.
Continue with :ref:`tutorial-schema`.
For now you should continue with :ref:`tutorial-schema`.
.. _Jinja2: http://jinja.pocoo.org/

1
docs/tutorial/index.rst

@ -24,6 +24,7 @@ the `example source`_.
folders
schema
setup
setuptools
dbcon
dbinit
views

9
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

8
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

69
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
@ -58,7 +60,7 @@ configuration file. Flask allows you to import multiple configurations and it
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
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::
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 <public-server>` section for more
information.
In the next section you will see how to run the application.
Continue with :ref:`tutorial-dbcon`.
Continue with :ref:`tutorial-setuptools`.

89
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 <public-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

15
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

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

41
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.
::
@ -95,10 +95,23 @@ if the user was logged in.
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 <http://flask.pocoo.org/extensions/>`_
Continue with :ref:`tutorial-templates`.
.. _hashed and salted: https://blog.codinghorror.com/youre-probably-storing-passwords-incorrectly/

1
examples/flaskr/.gitignore vendored

@ -1 +1,2 @@
flaskr.db
.eggs/

3
examples/flaskr/MANIFEST.in

@ -0,0 +1,3 @@
graft flaskr/templates
graft flaskr/static
include flaskr/schema.sql

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

0
examples/flaskr/flaskr/__init__.py

0
examples/flaskr/flaskr.py → examples/flaskr/flaskr/flaskr.py

0
examples/flaskr/schema.sql → examples/flaskr/flaskr/schema.sql

0
examples/flaskr/static/style.css → examples/flaskr/flaskr/static/style.css

0
examples/flaskr/templates/layout.html → examples/flaskr/flaskr/templates/layout.html

0
examples/flaskr/templates/login.html → examples/flaskr/flaskr/templates/login.html

0
examples/flaskr/templates/show_entries.html → examples/flaskr/flaskr/templates/show_entries.html

2
examples/flaskr/setup.cfg

@ -0,0 +1,2 @@
[aliases]
test=pytest

16
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',
],
)

6
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

3
examples/flaskr/test_flaskr.py → 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):
Loading…
Cancel
Save