(十)用应用上下文管理 SQLite 连接
1. 表之间保存明确关系
书籍仍然关联用户。登录记录单独保存随机编号的摘要、用户编号与到期时间,稍后用来核对会话。
-- 项目文件:shelf_project/src/shelfweb/schema.sql
-- 初始化只创建缺少的表,保留已有账号、登录与书籍。
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS logins (
digest TEXT PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
expires_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY,
owner_id INTEGER NOT NULL REFERENCES users(id),
title TEXT NOT NULL CHECK (length(title) BETWEEN 1 AND 100),
finished INTEGER NOT NULL CHECK (finished IN (0, 1)),
tags TEXT NOT NULL,
UNIQUE (owner_id, title)
);
CREATE INDEX IF NOT EXISTS books_owner ON books(owner_id);UNIQUE(owner_id,title) 允许两个用户各自保存同名书,同一用户再次写入同名书产生冲突。CREATE TABLE IF NOT EXISTS 只创建缺少的表,重复初始化保留已有数据;调整已有字段仍要使用第五章的迁移方法。
2. 第一次使用时连接,请求结束时关闭
# 项目文件:shelf_project/src/shelfweb/db.py
import csv
import json
import sqlite3
import click
from flask import current_app, g
from flask.cli import with_appcontext
from werkzeug.security import generate_password_hash
def get_db():
if "db" not in g:
connection = sqlite3.connect(current_app.config["DATABASE"], timeout=10)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
g.db = connection # 当前应用上下文中,后续调用复用这一条连接。
return g.db
def close_db(_error=None):
connection = g.pop("db", None)
if connection is not None:
connection.close() # close 不代替成功写入时的 commit。
def init_db():
with current_app.open_resource("schema.sql", mode="r", encoding="utf-8") as source:
get_db().executescript(source.read())
@click.command("init-db")
@with_appcontext
def init_db_command():
"""创建缺少的表,不清空已经保存的记录。"""
init_db()
click.echo("数据库表已准备。")
def init_app(app):
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)get_db 第一次调用时连接配置中的文件,设定按列名读取行并打开外键检查,再放入 g.db;本次上下文中的后续调用直接取回同一个连接。close_db 在离开应用上下文时取出并关闭它,避免每个业务函数各写一份关闭逻辑。
current_app.open_resource 从安装包目录读取 schema.sql,不依赖终端碰巧位于哪个目录。executescript 执行固定的建表 SQL;请求字段进入查询时仍用参数绑定。
click.command 创建命令,app.cli.add_command 把它登记为 init-db。@with_appcontext 在命令执行期间准备应用上下文,命令中的 current_app 和 g 才有当前对象。app.teardown_appcontext 则登记关闭连接的回调。这个组织方式可对照 Flask 数据库连接。