定義與存取資料庫

此應用程式將使用 SQLite 資料庫來儲存使用者和貼文。Python 在 sqlite3 模組中內建支援 SQLite。

SQLite 很方便,因為它不需要設定獨立的資料庫伺服器,而且是 Python 內建的。然而,如果同時有多個請求嘗試寫入資料庫,它們會因為每次寫入循序發生而變慢。小型應用程式不會注意到這一點。一旦您的應用程式變大,您可能會想要切換到不同的資料庫。

本教學不會深入探討 SQL 的細節。如果您不熟悉 SQL,SQLite 文件描述了其語言

連線到資料庫

使用 SQLite 資料庫(以及大多數其他 Python 資料庫函式庫)時,首先要做的是建立與資料庫的連線。任何查詢和操作都使用此連線執行,並在工作完成後關閉。

在 Web 應用程式中,此連線通常與請求綁定。它在處理請求的某個時間點建立,並在傳送回應之前關閉。

flaskr/db.py
import sqlite3
from datetime import datetime

import click
from flask import current_app, g


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()

g 是一個特殊的物件,對於每個請求都是獨一無二的。它用於儲存可能在請求期間被多個函式存取的資料。連線會被儲存和重複使用,而不是在同一個請求中第二次呼叫 get_db 時建立新的連線。

current_app 是另一個特殊的物件,指向處理請求的 Flask 應用程式。由於您使用了應用程式工廠,因此在編寫其餘程式碼時沒有應用程式物件。get_db 將在應用程式建立並正在處理請求時被呼叫,因此可以使用 current_app

sqlite3.connect() 建立與 DATABASE 組態金鑰指向的檔案的連線。此檔案不必事先存在,並且在您稍後初始化資料庫之前不會存在。

sqlite3.Row 告訴連線傳回行為類似字典的列。這允許通過名稱存取欄位。

close_db 檢查是否通過檢查是否已設定 g.db 來建立連線。如果連線存在,則將其關閉。在稍後的部分,您將在應用程式工廠中告知您的應用程式關於 close_db 函式,以便在每個請求之後呼叫它。

建立表格

在 SQLite 中,資料儲存在表格欄位中。這些需要在您可以儲存和檢索資料之前建立。Flaskr 將使用者儲存在 user 表格中,並將貼文儲存在 post 表格中。建立一個包含建立空表格所需的 SQL 命令的檔案

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)
);

將執行這些 SQL 命令的 Python 函式添加到 db.py 檔案中

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')
def init_db_command():
    """Clear the existing data and create new tables."""
    init_db()
    click.echo('Initialized the database.')


sqlite3.register_converter(
    "timestamp", lambda v: datetime.fromisoformat(v.decode())
)

open_resource() 開啟相對於 flaskr 套件的檔案,這很有用,因為您不一定知道稍後部署應用程式時該位置在哪裡。get_db 傳回資料庫連線,用於執行從檔案讀取的命令。

click.command() 定義了一個名為 init-db 的命令列指令,它呼叫 init_db 函式並向使用者顯示成功訊息。您可以閱讀 命令列介面 以了解有關編寫命令的更多資訊。

呼叫 sqlite3.register_converter() 告訴 Python 如何解釋資料庫中的時間戳記值。我們將值轉換為 datetime.datetime

向應用程式註冊

close_dbinit_db_command 函式需要向應用程式實例註冊;否則,它們將不會被應用程式使用。但是,由於您使用的是工廠函式,因此在編寫函式時該實例不可用。相反,編寫一個接受應用程式並執行註冊的函式。

flaskr/db.py
def init_app(app):
    app.teardown_appcontext(close_db)
    app.cli.add_command(init_db_command)

app.teardown_appcontext() 告訴 Flask 在返回回應後清理時呼叫該函式。

app.cli.add_command() 新增一個可以使用 flask 命令呼叫的新命令。

從工廠匯入並呼叫此函式。將新程式碼放在工廠函式的末尾,然後再返回應用程式。

flaskr/__init__.py
def create_app():
    app = ...
    # existing code omitted

    from . import db
    db.init_app(app)

    return app

初始化資料庫檔案

現在 init-db 已向應用程式註冊,可以使用 flask 命令呼叫它,類似於上一頁中的 run 命令。

注意

如果您仍在執行上一頁的伺服器,您可以停止伺服器,或在新終端機中執行此命令。如果您使用新的終端機,請記住切換到您的專案目錄並按照安裝中的說明啟動 env。

執行 init-db 命令

$ flask --app flaskr init-db
Initialized the database.

現在專案的 instance 資料夾中將會有一個 flaskr.sqlite 檔案。

繼續前往 藍圖與視圖