ToB企服应用市场:ToB评测及商务社交产业平台

标题: FastAPI-10 数据层 [打印本页]

作者: 道家人    时间: 2024-6-14 15:32
标题: FastAPI-10 数据层
10 数据层

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

10.1 DB-API

20多年来,Python一直包罗关系数据库接口的基本定义,称为 DB-API:PEP 249。任何为关系数据库编写Python驱动程序的人都应该至少包罗对 DB-API 的支持,固然也可能包罗其他功能。
这些是 DB-API 的重要功能:
execute...()函数运行带有可选参数的 SQL 语句 stmt 字符串,参数如下所示:
有五种指定参数的方式,但并非全部数据库驱动程序都支持全部方式。如果我们有一条以 "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元组):

参考资料

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之间有两个实用功能:
到目前为止,每一层(网络 → 服务 → 数据)都有虚伪的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企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4