FastAPI-10 数据层

道家人  金牌会员 | 2024-6-14 15:32:38 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 924|帖子 924|积分 2772

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 匹配。后两个使用一个字典,其中的键与语句中的名称相匹配。

  • 使用定名式参数
  1. stmt = """select * from creature where
  2.     name=:name or country=:country"""
  3. params = {"name": "yeti", "country": "CN"}
  4. curs.execute(stmt, params)
复制代码
对于 SQL INSERT、DELETE 和 UPDATE 语句,execute() 返回的值会告诉你它是如何工作的。对于SELECT语句,您可以使用fetch方法遍历返回的数据行(Python元组):

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

参考资料

10.2 SQLite

Python 尺度软件包中的sqlite3模块支持数据库(SQLite)。
SQLite 差别平常:它没有单独的数据库服务器。全部代码都在一个库中,存储在一个文件中。其他数据库运行单独的服务器,客户端通过 TCP/IP 使用特定协议与之通信。
首先,我们需要定义如安在数据库中表现我们在网站中使用的数据结构(模子)。到目前为止,我们唯一的模子是简单、相似但不完全相同的: 生物和资源管理器。随着我们想出更多的方法来使用这些模子,它们也会随之改变,并在不修改大量代码的情况下让数据不断发展。
data/init.py
  1. """Initialize SQLite database"""
  2. import os
  3. from pathlib import Path
  4. from sqlite3 import connect, Connection, Cursor, IntegrityError
  5. conn: Connection | None = None
  6. curs: Cursor | None = None
  7. def get_db(name: str|None = None, reset: bool = False):
  8.     """Connect to SQLite database file"""
  9.     global conn, curs
  10.     if conn:
  11.         if not reset:
  12.             return
  13.         conn = None
  14.     if not name:
  15.         name = os.getenv("CRYPTID_SQLITE_DB")
  16.         top_dir = Path(__file__).resolve().parents[1] # repo top
  17.         db_dir = top_dir / "db"
  18.         db_name = "cryptid.db"
  19.         db_path = str(db_dir / db_name)
  20.         name = os.getenv("CRYPTID_SQLITE_DB", db_path)
  21.     conn = connect(name, check_same_thread=False)
  22.     curs = conn.cursor()
  23. get_db()
复制代码
data/creature.py
  1. from .init import curs
  2. from model.creature import Creature
  3. curs.execute("""create table if not exists creature(
  4.                 name text primary key,
  5.                 description text,
  6.                 country text,
  7.                 area text,
  8.                 aka text)""")
  9. def row_to_model(row: tuple) -> Creature:
  10.     (name, description, country, area, aka) = row
  11.     return Creature(name, description, country, area, aka)
  12. def model_to_dict(creature: Creature) -> dict:
  13.     return creature.dict()
  14. def get_one(name: str) -> Creature:
  15.     qry = "select * from creature where name=:name"
  16.     params = {"name": name}
  17.     curs.execute(qry, params)
  18.     return row_to_model(curs.fetchone())
  19. def get_all() -> list[Creature]:
  20.     qry = "select * from creature"
  21.     curs.execute(qry)
  22.     return [row_to_model(row) for row in curs.fetchall()]
  23.    
  24. def create(creature: Creature) -> Creature:
  25.     qry = """insert into creature values
  26.           (:name, :description, :country, :area, :aka)"""
  27.     params = model_to_dict(creature)
  28.     curs.execute(qry, params)
  29.     return get_one(creature.name)
  30. def modify(creature: Creature) -> Creature:
  31.     qry = """update creature
  32.              set country=:country,
  33.                  name=:name,
  34.                  description=:description,
  35.                  area=:area,
  36.                  aka=:aka
  37.              where name=:name_orig"""
  38.     params = model_to_dict(creature)
  39.     params["name_orig"] = creature.name
  40.     _ = curs.execute(qry, params)
  41.     return get_one(creature.name)
  42. def delete(creature: Creature) -> bool:
  43.     qry = "delete from creature where name = :name"
  44.     params = {"name": creature.name}
  45.     res = curs.execute(qry, params)
  46.     return bool(res)
复制代码
在Pydantic模子和DB-API之间有两个实用功能:

  • row_too_model() 将获取函数返回的元组转换为模子对象。
  • model_to_dict()将 Pydantic 模子转换为字典,适合用作定名查询参数。
到目前为止,每一层(网络 → 服务 → 数据)都有虚伪的CRUD函数,现在这些函数将被取代。它们只使用普通SQL和 sqlite3中的DB-API法。
data/explorer.py
  1. from .init import curs
  2. from model.explorer import Explorer
  3. curs.execute("""create table if not exists explorer(
  4.                 name text primary key,
  5.                 country text,
  6.                 description text)""")
  7. def row_to_model(row: tuple) -> Explorer:
  8.     return Explorer(name=row[0], country=row[1], description=row[2])
  9. def model_to_dict(explorer: Explorer) -> dict:
  10.     return explorer.dict() if explorer else None
  11. def get_one(name: str) -> Explorer:
  12.     qry = "select * from explorer where name=:name"
  13.     params = {"name": name}
  14.     curs.execute(qry, params)
  15.     return row_to_model(curs.fetchone())
  16. def get_all() -> list[Explorer]:
  17.     qry = "select * from explorer"
  18.     curs.execute(qry)
  19.     return [row_to_model(row) for row in curs.fetchall()]
  20.    
  21. def create(explorer: Explorer) -> Explorer:
  22.     qry = """insert into explorer (name, country, description)
  23.              values (:name, :country, :description)"""
  24.     params = model_to_dict(explorer)
  25.     _ = curs.execute(qry, params)
  26.     return get_one(explorer.name)
  27. def modify(name: str, explorer: Explorer) -> Explorer:
  28.     qry = """update explorer
  29.              set country=:country,
  30.              name=:name,
  31.              description=:description
  32.              where name=:name_orig"""
  33.     params = model_to_dict(explorer)
  34.     params["name_orig"] = explorer.name
  35.     _ = curs.execute(qry, params)
  36.     explorer2 = get_one(explorer.name)
  37.     return explorer2
  38. def delete(explorer: Explorer) -> bool:
  39.     qry = "delete from explorer where name = :name"
  40.     params = {"name": explorer.name}
  41.     res = curs.execute(qry, params)
  42.     return bool(res)
复制代码
10.3 测试


  • 启动
  1. $ export CRYPTID_SQLITE_DB=cryptid.db
  2. $ python main.py
  3. INFO:     Will watch for changes in these directories: ['/home/andrew/code/fastapi/example/ch8']
  4. INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
  5. INFO:     Started reloader process [6659] using WatchFiles
  6. INFO:     Started server process [6661]
  7. INFO:     Waiting for application startup.
  8. INFO:     Application startup complete.
复制代码

  • 测试
  1. $ http post localhost:8000/explorer name="Beau Buffette", contry="US"
  2. HTTP/1.1 422 Unprocessable Entity
  3. content-length: 174
  4. content-type: application/json
  5. date: Thu, 13 Jun 2024 06:11:44 GMT
  6. server: uvicorn
  7. {
  8.     "detail": [
  9.         {
  10.             "loc": [
  11.                 "body",
  12.                 "country"
  13.             ],
  14.             "msg": "field required",
  15.             "type": "value_error.missing"
  16.         },
  17.         {
  18.             "loc": [
  19.                 "body",
  20.                 "description"
  21.             ],
  22.             "msg": "field required",
  23.             "type": "value_error.missing"
  24.         }
  25.     ]
  26. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

道家人

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表