Python 现代开发学习笔记

(十)用应用上下文管理 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 数据库连接。

一次请求第一次 get_db 创建连接 C 并放入 g,认证和书籍函数复用 C,事务先完成,再在 teardown 取出连接并关闭

笔记正文与全部题解 · Ctrl / ⌘ K