layout: post # 使用的布局(不需要改)
title: SQLModel # 标题
subtitle: SQLModel
date: 2019-09-15 # 时间
author: NSX # 作者
header-img: img/post-bg-2015.jpg #这篇文章标题背景图片
catalog: true # 是否归档
tags: #标签

  • SQLModel
  • ORM

SQLModel – SQL Databases in FastAPI

SQLModel 是一个用于与 SQL DB 交互的库,基于 Python 类型提示。

由 Pydantic(数据校验库)和 SQLAlchemy(SQL 对象映射器)提供技术支持,并且都针对 FastAPI 进行了优化。

GitHub 在这里: https://github.com/tiangolo/sqlmodel

此 Twitter 线程中的更多信息: https://twitter.com/tiangolo/status/1430252646968004612

文档在这里: https://sqlmodel.tiangolo.com/

基于 SQLAlchemy

SQLModel也基于 SQLAlchemy 并将其用于一切。

在下面,✨一个SQLModel模型也是一个SQLAlchemy模型。✨

很多研究和努力致力于使其成为这种方式。特别是,要使单个模型同时成为 SQLAlchemy 模型和 Pydantic模型,需要付出很多努力和实验。

这意味着您可以获得 SQLAlchemy(Python 中使用最广泛的数据库库)的所有功能、稳健性和确定性。

SQLModel提供了自己的实用程序来改善开发人员体验,但在底层,它使用了所有 SQLAlchemy。

您甚至可以SQLModel 模型与 SQLAlchemy 模型结合起来

SQLModel 旨在满足最常见的用例,并为这些用例尽可能简单方便,提供最佳的开发人员体验。

但是,当您有更多需要更复杂功能的奇特用例时,您仍然可以将 SQLAlchemy 直接插入 SQLModel 并在您的代码中使用其所有功能。

ORM介绍

面向对象编程把所有实体看成对象(object),关系型数据库则是采用实体之间的关系(relation)连接数据。很早就有人提出,关系也可以用对象表达,这样的话,就能使用面向对象编程,来操作关系型数据库。

2021-09-15-SQLModel调研总结 - 图1

简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是”对象-关系映射”(Object/Relational Mapping) 的缩写。

ORM 把数据库映射成对象。

  • 数据库的表(table) —> 类(class)
  • 记录(record,行数据)—> 对象(object)
  • 字段(field)—> 对象的属性(attribute)

2021-09-15-SQLModel调研总结 - 图2

总结起来,ORM 有下面这些优点。

  • 数据模型都在一个地方定义,更容易更新和维护,也利于重用代码。
  • ORM 有现成的工具,很多功能都可以自动完成,比如数据消毒、预处理、事务等等。
  • 它迫使你使用 MVC 架构,ORM 就是天然的 Model,最终使代码更清晰。
  • 基于 ORM 的业务代码比较简单,代码量少,语义性好,容易理解。
  • 你不必编写性能不佳的 SQL。

但是,ORM 也有很突出的缺点。

  • ORM 库不是轻量级工具,需要花很多精力学习和设置。
  • 对于复杂的查询,ORM 要么是无法表达,要么是性能不如原生的 SQL。
  • ORM 抽象掉了数据库层,开发者无法了解底层的数据库操作,也无法定制一些特殊的 SQL。

Installation

  1. pip install sqlmodel

CRUD operations with SQLModel using a single table

Create an SQLModel: models.py

  1. from sqlmodel import SQLModel,Field
  2. from typing import Optional
  3. class Book(SQLModel,table=True):
  4. id:Optional[int]=Field(default=None,primary_key=True)
  5. title:str
  6. description:str

id字段不能NULL在数据库中,因为它是主键,我们使用Field(primary_key=True). 但是在 Python 代码中id实际上可以有None相同的字段,所以我们用 声明类型Optional[int],并将默认值设置为Field(default=None)

Database setup: database.py

  1. from sqlmodel import SQLModel,create_engine
  2. import os
  3. BASE_DIR=os.path.dirname(os.path.realpath(__file__))
  4. conn_str='sqlite:///'+os.path.join(BASE_DIR,'books.db')
  5. print(conn_str)
  6. engine=create_engine(conn_str,echo=True)

在这个例子中,我们还使用了参数 echo=True。它将使引擎打印它执行的所有 SQL 语句,这可以帮助您了解正在发生的事情。

Creating the database create_db.py

  1. from sqlmodel import SQLModel
  2. from models import Book
  3. from database import engine
  4. print("CREATING DATABASE.....")
  5. SQLModel.metadata.create_all(engine)

导入models的Book类,Python 执行所有代码,创建从 SQLModel 继承的类并将它们注册到 SQLModel.metadata 中。create_all通过engine来创建数据库和在此 MetaData 对象中注册的所有表。

Get all items main.py

  1. from fastapi import FastAPI
  2. from fastapi import status
  3. from fastapi.exceptions import HTTPException
  4. from models import Book
  5. from database import engine
  6. from sqlmodel import Session,select
  7. from typing import Optional,List
  8. app=FastAPI()
  9. session=Session(bind=engine)
  10. @app.get('/books',response_model=List[Book],
  11. status_code=status.HTTP_200_OK)
  12. async def get_all_books():
  13. statement=select(Book)
  14. results=session.exec(statement).all()
  15. return results

Get one item

  1. @app.get("/book/{book_id}", response_model=Book)
  2. async def get_a_book(book_id: int):
  3. statement = select(Book).where(Book.id == book_id)
  4. # statement = select(Hero).where(Hero.age >= 35, Hero.age < 40)
  5. # statement = select(Hero).where(or_(Hero.age <= 35, Hero.age > 90))
  6. result = session.exec(statement).first()
  7. if result == None:
  8. raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
  9. return result

Create an item

  1. @app.post("/books", response_model=Book, status_code=status.HTTP_201_CREATED)
  2. async def create_a_book(book: Book):
  3. new_book = Book(title=book.title, description=book.description)
  4. session.add(new_book)
  5. print("After adding to the session")
  6. print("new_book:", new_book)
  7. session.commit()
  8. print("After committing the session")
  9. print("new_book:", new_book.id)
  10. print("new_book:", new_book.title)
  11. session.refresh(new_book) # 显式刷新对象,保证获取的是最新数据
  12. print("After refreshing the heroes")
  13. print("new_book:", new_book)
  14. return new_book

id默认设置为None,在与数据库交互之前,值实际上可能一直是None.

Update a book

  1. @app.put("/book/{book_id}", response_model=Book)
  2. async def update_a_book(book_id: int, book: Book):
  3. statement = select(Book).where(Book.id == book_id)
  4. result = session.exec(statement).first()
  5. result.title = book.title
  6. result.description = book.description
  7. session.commit()
  8. session.refresh(result)
  9. return result

Delete a book

  1. @app.delete("/book/{book_id}", status_code=status.HTTP_204_NO_CONTENT)
  2. async def delete_a_book(book_id: int):
  3. statement = select(Book).where(Book.id == book_id)
  4. result = session.exec(statement).one_or_none()
  5. if result == None:
  6. raise HTTPException(
  7. status_code=status.HTTP_404_NOT_FOUND, detail="Resource Not Found"
  8. )
  9. session.delete(result)
  10. return result

Connect Tables - JOIN

联合查询的另一种方式,使用关键字JOIN而不是WHERE.

使用WHERE

  1. SELECT hero.id, hero.name, team.name
  2. FROM hero, team
  3. WHERE hero.team_id = team.id

这是使用的替代版本JOIN

  1. SELECT hero.id, hero.name, team.name
  2. FROM hero
  3. JOIN team
  4. ON hero.team_id = team.id

两者是等价的。

  1. @app.get("/books_join", response_model=List[Book], status_code=status.HTTP_200_OK)
  2. async def select_heroes():
  3. statement = select(Book, Team).where(Book.id==Team.id)
  4. results = session.exec(statement)
  5. print(results.all())
  6. statement = select(Book, Team).join(Team)
  7. results2 = session.exec(statement)
  8. for hero, team in results2:
  9. print("Hero:", hero, "Team:", team)

当使用 时.join(),因为我们foreign_key在创建模型时已经声明了什么,所以我们不必传递ON部分,它会自动推断

.join()有一个参数,我们可以isouter=True用来使JOINa LEFT OUTER JOIN

  1. # Code above omitted 👆
  2. def select_heroes():
  3. with Session(engine) as session:
  4. statement = select(Hero, Team).join(Team, isouter=True)
  5. results = session.exec(statement)
  6. for hero, team in results:
  7. print("Hero:", hero, "Team:", team)
  8. # Code below omitted 👇

参考

SQLModel 官方文档 https://sqlmodel.tiangolo.com/

SQLAlchemy ORM 官方文档 https://www.tutorialspoint.com/sqlalchemy/sqlalchemy_orm_filter_operators.htm

https://docs.sqlalchemy.org/en/14/orm/query.html