FastAPI-10 数据层

pythontesting 2024-06-14 16:39:00 阅读 66

10 数据

本章终于为我们的网站数据创建了一个持久的家园,最终将三个层连接起来。本章使用关系数据库 SQLite,并介绍了 Python 的数据库 API(DB-API)。第14章将详细介绍数据库,包括 SQLAlchemy 软件包和非关系型数据库。

10.1 DB-API

20多年来,Python一直包含关系数据库接口的基本定义,称为 DB-API:PEP 249。任何为关系数据库编写Python驱动程序的人都应该至少包含对 DB-API 的支持,当然也可能包含其他功能。

这些是 DB-API 的主要功能:

  • 使用 connect() 创建数据库连接 conn。
  • 使用 conn.cursor() 创建游标 cursor。
  • 使用 curs.execute(stmt) 执行 SQL 字符串 stmt。

execute...()函数运行带有可选参数的 SQL 语句 stmt 字符串,参数如下所示:

  • execute(stmt) 如果没有参数
  • execute(stmt,params),参数为单个序列(列表或元组)或 dict
  • executemany(stmt,params_seq),在参数序列 params_seq 中包含多个参数组。

有五种指定参数的方式,但并非所有数据库驱动程序都支持所有方式。如果我们有一条以 "select * from creature where "开头的语句 stmt,并且我们想为生物的名称或国家指定字符串参数,那么 stmt 字符串的其余部分及其参数将如表所示。

前三项使用元组参数,参数顺序与语句中的 ?、:N 或 %s 匹配。后两个使用一个字典,其中的键与语句中的名称相匹配。

  • 使用命名式参数

stmt = """select * from creature where

name=:name or country=:country"""

params = {"name": "yeti", "country": "CN"}

curs.execute(stmt, params)

对于 SQL INSERT、DELETE 和 UPDATE 语句,execute() 返回的值会告诉你它是如何工作的。对于SELECT语句,您可以使用fetch方法遍历返回的数据行(Python元组):

  • fetchone() 返回一个元组,或者返回 None。
  • fetchall() 返回一个元组序列。
  • fetchmany(num) 最多返回num个元组。

参考资料

  • 软件测试精品书籍文档下载持续更新 https://github.com/china-testing/python-testing-examples 请点赞,谢谢!
  • 本文涉及的python测试开发库 谢谢点赞! https://github.com/china-testing/python_cn_resouce
  • python精品书籍下载 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
  • Linux精品书籍下载 https://www.cnblogs.com/testing-/p/17438558.html

10.2 SQLite

Python 标准软件包中的sqlite3模块支持数据库(SQLite)。

SQLite 不同寻常:它没有单独的数据库服务器。所有代码都在一个库中,存储在一个文件中。其他数据库运行单独的服务器,客户端通过 TCP/IP 使用特定协议与之通信。

首先,我们需要定义如何在数据库中表示我们在网站中使用的数据结构(模型)。到目前为止,我们唯一的模型是简单、相似但不完全相同的: 生物和资源管理器。随着我们想出更多的方法来使用这些模型,它们也会随之改变,并在不修改大量代码的情况下让数据不断发展。

data/init.py

"""Initialize SQLite database"""

import os

from pathlib import Path

from sqlite3 import connect, Connection, Cursor, IntegrityError

conn: Connection | None = None

curs: Cursor | None = None

def get_db(name: str|None = None, reset: bool = False):

"""Connect to SQLite database file"""

global conn, curs

if conn:

if not reset:

return

conn = None

if not name:

name = os.getenv("CRYPTID_SQLITE_DB")

top_dir = Path(__file__).resolve().parents[1] # repo top

db_dir = top_dir / "db"

db_name = "cryptid.db"

db_path = str(db_dir / db_name)

name = os.getenv("CRYPTID_SQLITE_DB", db_path)

conn = connect(name, check_same_thread=False)

curs = conn.cursor()

get_db()

data/creature.py

from .init import curs

from model.creature import Creature

curs.execute("""create table if not exists creature(

name text primary key,

description text,

country text,

area text,

aka text)""")

def row_to_model(row: tuple) -> Creature:

(name, description, country, area, aka) = row

return Creature(name, description, country, area, aka)

def model_to_dict(creature: Creature) -> dict:

return creature.dict()

def get_one(name: str) -> Creature:

qry = "select * from creature where name=:name"

params = {"name": name}

curs.execute(qry, params)

return row_to_model(curs.fetchone())

def get_all() -> list[Creature]:

qry = "select * from creature"

curs.execute(qry)

return [row_to_model(row) for row in curs.fetchall()]

def create(creature: Creature) -> Creature:

qry = """insert into creature values

(:name, :description, :country, :area, :aka)"""

params = model_to_dict(creature)

curs.execute(qry, params)

return get_one(creature.name)

def modify(creature: Creature) -> Creature:

qry = """update creature

set country=:country,

name=:name,

description=:description,

area=:area,

aka=:aka

where name=:name_orig"""

params = model_to_dict(creature)

params["name_orig"] = creature.name

_ = curs.execute(qry, params)

return get_one(creature.name)

def delete(creature: Creature) -> bool:

qry = "delete from creature where name = :name"

params = {"name": creature.name}

res = curs.execute(qry, params)

return bool(res)

在Pydantic模型和DB-API之间有两个实用功能:

  • row_too_model() 将获取函数返回的元组转换为模型对象。
  • model_to_dict()将 Pydantic 模型转换为字典,适合用作命名查询参数。

到目前为止,每一层(网络 → 服务 → 数据)都有虚假的CRUD函数,现在这些函数将被取代。它们只使用普通SQL和 sqlite3中的DB-API法。

data/explorer.py

from .init import curs

from model.explorer import Explorer

curs.execute("""create table if not exists explorer(

name text primary key,

country text,

description text)""")

def row_to_model(row: tuple) -> Explorer:

return Explorer(name=row[0], country=row[1], description=row[2])

def model_to_dict(explorer: Explorer) -> dict:

return explorer.dict() if explorer else None

def get_one(name: str) -> Explorer:

qry = "select * from explorer where name=:name"

params = {"name": name}

curs.execute(qry, params)

return row_to_model(curs.fetchone())

def get_all() -> list[Explorer]:

qry = "select * from explorer"

curs.execute(qry)

return [row_to_model(row) for row in curs.fetchall()]

def create(explorer: Explorer) -> Explorer:

qry = """insert into explorer (name, country, description)

values (:name, :country, :description)"""

params = model_to_dict(explorer)

_ = curs.execute(qry, params)

return get_one(explorer.name)

def modify(name: str, explorer: Explorer) -> Explorer:

qry = """update explorer

set country=:country,

name=:name,

description=:description

where name=:name_orig"""

params = model_to_dict(explorer)

params["name_orig"] = explorer.name

_ = curs.execute(qry, params)

explorer2 = get_one(explorer.name)

return explorer2

def delete(explorer: Explorer) -> bool:

qry = "delete from explorer where name = :name"

params = {"name": explorer.name}

res = curs.execute(qry, params)

return bool(res)

10.3 测试

  • 启动

$ export CRYPTID_SQLITE_DB=cryptid.db

$ python main.py

INFO: Will watch for changes in these directories: ['/home/andrew/code/fastapi/example/ch8']

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

INFO: Started reloader process [6659] using WatchFiles

INFO: Started server process [6661]

INFO: Waiting for application startup.

INFO: Application startup complete.

  • 测试

$ http post localhost:8000/explorer name="Beau Buffette", contry="US"

HTTP/1.1 422 Unprocessable Entity

content-length: 174

content-type: application/json

date: Thu, 13 Jun 2024 06:11:44 GMT

server: uvicorn

{

"detail": [

{

"loc": [

"body",

"country"

],

"msg": "field required",

"type": "value_error.missing"

},

{

"loc": [

"body",

"description"

],

"msg": "field required",

"type": "value_error.missing"

}

]

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。