mirror of https://github.com/mitsuhiko/flask.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
213 lines
6.7 KiB
213 lines
6.7 KiB
.. currentmodule:: flask |
|
|
|
Define and Access the Database |
|
============================== |
|
|
|
The application will use a `SQLite`_ database to store users and posts. |
|
Python comes with built-in support for SQLite in the :mod:`sqlite3` |
|
module. |
|
|
|
SQLite is convenient because it doesn't require setting up a separate |
|
database server and is built-in to Python. However, if concurrent |
|
requests try to write to the database at the same time, they will slow |
|
down as each write happens sequentially. Small applications won't notice |
|
this. Once you become big, you may want to switch to a different |
|
database. |
|
|
|
The tutorial doesn't go into detail about SQL. If you are not familiar |
|
with it, the SQLite docs describe the `language`_. |
|
|
|
.. _SQLite: https://sqlite.org/about.html |
|
.. _language: https://sqlite.org/lang.html |
|
|
|
|
|
Connect to the Database |
|
----------------------- |
|
|
|
The first thing to do when working with a SQLite database (and most |
|
other Python database libraries) is to create a connection to it. Any |
|
queries and operations are performed using the connection, which is |
|
closed after the work is finished. |
|
|
|
In web applications this connection is typically tied to the request. It |
|
is created at some point when handling a request, and closed before the |
|
response is sent. |
|
|
|
.. code-block:: python |
|
:caption: ``flaskr/db.py`` |
|
|
|
import sqlite3 |
|
|
|
import click |
|
from flask import current_app, g |
|
from flask.cli import with_appcontext |
|
|
|
|
|
def get_db(): |
|
if 'db' not in g: |
|
g.db = sqlite3.connect( |
|
current_app.config['DATABASE'], |
|
detect_types=sqlite3.PARSE_DECLTYPES |
|
) |
|
g.db.row_factory = sqlite3.Row |
|
|
|
return g.db |
|
|
|
|
|
def close_db(e=None): |
|
db = g.pop('db', None) |
|
|
|
if db is not None: |
|
db.close() |
|
|
|
:data:`g` is a special object that is unique for each request. It is |
|
used to store data that might be accessed by multiple functions during |
|
the request. The connection is stored and reused instead of creating a |
|
new connection if ``get_db`` is called a second time in the same |
|
request. |
|
|
|
:data:`current_app` is another special object that points to the Flask |
|
application handling the request. Since you used an application factory, |
|
there is no application object when writing the rest of your code. |
|
``get_db`` will be called when the application has been created and is |
|
handling a request, so :data:`current_app` can be used. |
|
|
|
:func:`sqlite3.connect` establishes a connection to the file pointed at |
|
by the ``DATABASE`` configuration key. This file doesn't have to exist |
|
yet, and won't until you initialize the database later. |
|
|
|
:class:`sqlite3.Row` tells the connection to return rows that behave |
|
like dicts. This allows accessing the columns by name. |
|
|
|
``close_db`` checks if a connection was created by checking if ``g.db`` |
|
was set. If the connection exists, it is closed. Further down you will |
|
tell your application about the ``close_db`` function in the application |
|
factory so that it is called after each request. |
|
|
|
|
|
Create the Tables |
|
----------------- |
|
|
|
In SQLite, data is stored in *tables* and *columns*. These need to be |
|
created before you can store and retrieve data. Flaskr will store users |
|
in the ``user`` table, and posts in the ``post`` table. Create a file |
|
with the SQL commands needed to create empty tables: |
|
|
|
.. code-block:: sql |
|
:caption: ``flaskr/schema.sql`` |
|
|
|
DROP TABLE IF EXISTS user; |
|
DROP TABLE IF EXISTS post; |
|
|
|
CREATE TABLE user ( |
|
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|
username TEXT UNIQUE NOT NULL, |
|
password TEXT NOT NULL |
|
); |
|
|
|
CREATE TABLE post ( |
|
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|
author_id INTEGER NOT NULL, |
|
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, |
|
title TEXT NOT NULL, |
|
body TEXT NOT NULL, |
|
FOREIGN KEY (author_id) REFERENCES user (id) |
|
); |
|
|
|
Add the Python functions that will run these SQL commands to the |
|
``db.py`` file: |
|
|
|
.. code-block:: python |
|
:caption: ``flaskr/db.py`` |
|
|
|
def init_db(): |
|
db = get_db() |
|
|
|
with current_app.open_resource('schema.sql') as f: |
|
db.executescript(f.read().decode('utf8')) |
|
|
|
|
|
@click.command('init-db') |
|
@with_appcontext |
|
def init_db_command(): |
|
"""Clear the existing data and create new tables.""" |
|
init_db() |
|
click.echo('Initialized the database.') |
|
|
|
:meth:`open_resource() <Flask.open_resource>` opens a file relative to |
|
the ``flaskr`` package, which is useful since you won't necessarily know |
|
where that location is when deploying the application later. ``get_db`` |
|
returns a database connection, which is used to execute the commands |
|
read from the file. |
|
|
|
:func:`click.command` defines a command line command called ``init-db`` |
|
that calls the ``init_db`` function and shows a success message to the |
|
user. You can read :ref:`cli` to learn more about writing commands. |
|
|
|
|
|
Register with the Application |
|
----------------------------- |
|
|
|
The ``close_db`` and ``init_db_command`` functions need to be registered |
|
with the application instance, otherwise they won't be used by the |
|
application. However, since you're using a factory function, that |
|
instance isn't available when writing the functions. Instead, write a |
|
function that takes an application and does the registration. |
|
|
|
.. code-block:: python |
|
:caption: ``flaskr/db.py`` |
|
|
|
def init_app(app): |
|
app.teardown_appcontext(close_db) |
|
app.cli.add_command(init_db_command) |
|
|
|
:meth:`app.teardown_appcontext() <Flask.teardown_appcontext>` tells |
|
Flask to call that function when cleaning up after returning the |
|
response. |
|
|
|
:meth:`app.cli.add_command() <click.Group.add_command>` adds a new |
|
command that can be called with the ``flask`` command. |
|
|
|
Import and call this function from the factory. Place the new code at |
|
the end of the factory function before returning the app. |
|
|
|
.. code-block:: python |
|
:caption: ``flaskr/__init__.py`` |
|
|
|
def create_app(): |
|
app = ... |
|
# existing code omitted |
|
|
|
from . import db |
|
db.init_app(app) |
|
|
|
return app |
|
|
|
|
|
Initialize the Database File |
|
---------------------------- |
|
|
|
Now that ``init-db`` has been registered with the app, it can be called |
|
using the ``flask`` command, similar to the ``run`` command from the |
|
previous page. |
|
|
|
.. note:: |
|
|
|
If you're still running the server from the previous page, you can |
|
either stop the server, or run this command in a new terminal. If |
|
you use a new terminal, remember to change to your project directory |
|
and activate the env as described in :ref:`install-activate-env`. |
|
You'll also need to set ``FLASK_APP`` and ``FLASK_ENV`` as shown on |
|
the previous page. |
|
|
|
Run the ``init-db`` command: |
|
|
|
.. code-block:: none |
|
|
|
flask init-db |
|
Initialized the database. |
|
|
|
There will now be a ``flaskr.sqlite`` file in the ``instance`` folder in |
|
your project. |
|
|
|
Continue to :doc:`views`.
|
|
|