跳到内容

使用 SQLModel 创建表 - 使用引擎

现在让我们开始编写代码吧。👩‍💻

确保您在项目目录中,并已激活虚拟环境,如虚拟环境中所述。

我们将

  • 使用 SQLModel 定义表
  • 使用 SQLModel 创建相同的 SQLite 数据库和表
  • 使用 DB Browser for SQLite 确认操作

以下是我们想要的表结构的提醒

idnamesecret_nameage
1死侍戴夫·威尔逊
2蜘蛛男孩佩德罗·帕尔克多
3锈人汤米·夏普48

创建表模型类

我们需要做的第一件事是创建一个类来表示表中的数据。

像这样表示某些数据的类通常被称为模型

提示

这就是为什么这个包叫做 SQLModel。因为它主要用于创建 SQL 模型

为此,我们将导入 SQLModel(以及我们还将使用的其他东西),并创建一个继承自 SQLModelHero 类,它代表我们的英雄的表模型

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None

# Code below omitted 👇
👀 完整文件预览
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

这个 Hero代表我们的英雄的。我们稍后创建的每个实例将代表表中的一行

我们使用配置 table=True 来告诉 SQLModel 这是一个表模型,它代表一个表。

信息

也可以有不带 table=True 的模型,这些将只是数据模型,数据库中没有表,它们不是表模型

这些数据模型以后会非常有用,但目前,我们只会继续添加 table=True 配置。

定义字段、列

下一步是使用标准的 Python 类型注解来定义类的字段或列。

这些变量的名称将是表中列的名称。

它们的类型也将是表列的类型

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None

# Code below omitted 👇
👀 完整文件预览
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

现在让我们更详细地了解这些字段/列声明。

None 字段,可空列

让我们从 age 开始,注意它的类型是 int | None

这是在 Python 中声明某个东西“可以是 intNone”的标准方式。

我们还将 age 的默认值设置为 None

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None

# Code below omitted 👇
👀 完整文件预览
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

提示

我们还将 id 定义为 int | None。但我们将在下面讨论 id

因为类型是 int | None

  • 在验证数据时,None 将是 age 的允许值。
  • 在数据库中,age 的列将允许有 NULL(SQL 中等同于 Python 的 None)。

并且因为有一个默认值 = None

  • 在验证数据时,这个 age 字段将不是必需的,它将默认为 None
  • 保存到数据库时,age 列将默认为 NULL 值。

提示

默认值可以是其他东西,例如 = 42

主键 id

现在让我们回顾一下 id 字段。这是表的主键

所以,我们需要将 id 标记为主键

为此,我们使用 sqlmodel 中的特殊 Field 函数并设置参数 primary_key=True

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None

# Code below omitted 👇
👀 完整文件预览
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

这样,我们告诉 SQLModel 这个 id 字段/列是表的主键。

但在 SQL 数据库内部,它始终是必需的,并且不能是 NULL。我们为什么要将其声明为 int | None

id 在数据库中是必需的,但它将由数据库生成,而不是由我们的代码生成。

所以,无论何时我们创建这个类的实例(在下一章中),我们不会设置 idid 的值将是 None直到我们将其保存到数据库中,然后它最终才会有值。

my_hero = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")

do_something(my_hero.id)  # Oh no! my_hero.id is None! 😱🚨

# Imagine this saves it to the database
somehow_save_in_db(my_hero)

do_something(my_hero.id)  # Now my_hero.id has a value generated in DB 🎉

所以,因为在我们的代码中(而不是在数据库中)id 的值可能是 None,我们使用 int | None。这样编辑器就能帮助我们,例如,如果我们试图访问一个尚未保存到数据库中且仍然是 None 的对象的 id

现在,因为我们用 Field() 函数取代了默认值,所以我们在 Field() 中使用参数 default=Noneid实际默认值设置为 None

Field(default=None)

如果我们不设置 default 值,那么无论何时我们以后使用此模型进行数据验证(由 Pydantic 提供支持),它都将接受一个 None 值,而不是 int,但它仍然会要求传递该 None 值。这对于以后使用此模型的人(可能就是我们)来说会很困惑,因此最好在这里设置默认值

创建引擎

现在我们需要创建 SQLAlchemy 引擎

它是一个处理与数据库通信的对象。

如果您有服务器数据库(例如 PostgreSQL 或 MySQL),引擎将持有与该数据库的网络连接

创建引擎非常简单,只需调用 create_engine() 并提供数据库的 URL 即可

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
👀 完整文件预览
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

您通常应该为整个应用程序只创建一个引擎对象并在任何地方重复使用它。

提示

还有另一个相关的东西叫做 Session,它通常应该是每个应用程序的单个对象。

但我们稍后再讨论。

引擎数据库 URL

每个受支持的数据库都有自己的 URL 类型。例如,对于 SQLite,它是 sqlite:///,后面跟着文件路径。例如

  • sqlite:///database.db
  • sqlite:///databases/local/application.db
  • sqlite:///db.sqlite

SQLite 支持一种特殊的数据库,它完全在内存中。因此,它速度非常快,但请注意,程序终止后数据库会被删除。您可以通过只使用两个斜杠字符 (//) 且不带文件名来指定此内存数据库

  • sqlite://
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
👀 完整文件预览
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

您可以在 SQLAlchemy 文档中阅读更多关于 SQLAlchemy(以及因此 SQLModel)支持的所有数据库的信息。

引擎回显

在此示例中,我们还使用了参数 echo=True

它将使引擎打印所有执行的 SQL 语句,这有助于您了解正在发生的事情。

它特别适用于学习调试

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
👀 完整文件预览
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

但在生产环境中,您可能希望移除 echo=True

engine = create_engine(sqlite_url)

引擎技术细节

提示

如果您以前不了解 SQLAlchemy,并且只是在学习 SQLModel,那么您可以跳过此部分,向下滚动。

您可以在 SQLAlchemy 文档中阅读更多关于引擎的信息。

SQLModel 定义了自己的 create_engine() 函数。它与 SQLAlchemy 的 create_engine() 相同,但区别在于它默认为使用 future=True(这意味着它使用最新的 SQLAlchemy 1.4 和未来的 2.0 风格)。

并且 SQLModel 版本的 create_engine() 在内部进行了类型注解,因此您的编辑器将能够通过自动补全和内联错误来帮助您。

创建数据库和表

现在一切就绪,终于可以创建数据库和表了

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

提示

创建引擎不会创建 database.db 文件。

但一旦我们运行 SQLModel.metadata.create_all(engine),它就会创建 database.db 文件在该数据库中创建 hero 表。

这两件事都是在这个单一的步骤中完成的。

让我们展开一下

SQLModel.metadata.create_all(engine)

SQLModel 元数据

SQLModel 类有一个 metadata 属性。它是 MetaData 类的一个实例。

每当您创建一个继承自 SQLModel 并配置为 table = True 的类时,它都会注册到此 metadata 属性中。

因此,到最后一行,SQLModel.metadata 已经注册了 Hero

调用 create_all()

这个 SQLModel.metadata 处的 MetaData 对象有一个 create_all() 方法。

它接受一个引擎并使用它来创建数据库和在此 MetaData 对象中注册的所有表。

SQLModel 元数据顺序很重要

这也意味着您必须在创建继承自 SQLModel 的新模型类的代码之后调用 SQLModel.metadata.create_all()

例如,我们假设您执行以下操作

  • 在一个 Python 文件 models.py 中创建模型。
  • 在文件 db.py 中创建引擎对象。
  • 创建主应用程序并在 app.py 中调用 SQLModel.metadata.create_all()

如果您只导入 SQLModel 并尝试在 app.py 中调用 SQLModel.metadata.create_all(),它将不会创建您的表

# This wouldn't work! 🚨
from sqlmodel import SQLModel

from .db import engine

SQLModel.metadata.create_all(engine)

它不起作用,因为当您单独导入 SQLModel 时,Python 不会执行所有创建继承自它的类的代码(在我们的示例中是 Hero 类),因此 SQLModel.metadata 仍然是空的。

但是如果您在调用 SQLModel.metadata.create_all() 之前导入模型,它将起作用

from sqlmodel import SQLModel

from . import models
from .db import engine

SQLModel.metadata.create_all(engine)

这将起作用,因为通过导入模型,Python 会执行所有创建继承自 SQLModel 的类的代码,并将其注册到 SQLModel.metadata 中。

作为替代方案,您可以在 db.py 中导入 SQLModel 和您的模型

# db.py
from sqlmodel import SQLModel, create_engine
from . import models


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url)

然后从 app.py 中的 db.py 导入 SQLModel,并在那里调用 SQLModel.metadata.create_all()

# app.py
from .db import engine, SQLModel

SQLModel.metadata.create_all(engine)

db.py 导入 SQLModel 会起作用,因为 SQLModel 也已在 db.py 中导入。

并且这个技巧将正确地工作并在数据库中创建表,因为通过从 db.py 导入 SQLModel,Python 会执行在 db.py 文件中创建继承自 SQLModel 的所有类的代码,例如 Hero 类。

迁移

对于这个简单的示例,以及教程 - 用户指南的大部分内容,使用 SQLModel.metadata.create_all() 就足够了。

但对于生产系统,您可能需要使用数据库迁移系统。

例如,每当您添加或删除列、添加新表、更改类型等时,这都将非常有用和重要。

但是您将在高级用户指南中学习迁移。

运行程序

让我们运行程序,看看它是否正常工作。

如果尚未将代码放入文件 app.py 中。

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

SQLModel.metadata.create_all(engine)

提示

记得在运行之前激活虚拟环境

现在用 Python 运行程序

// We set echo=True, so this will show the SQL code
$ python app.py

// First, some boilerplate SQL that we are not that interested in

INFO Engine BEGIN (implicit)
INFO Engine PRAGMA main.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine PRAGMA temp.table_info("hero")
INFO Engine [raw sql] ()
INFO Engine

// Finally, the glorious SQL to create the table ✨

CREATE TABLE hero (
        id INTEGER,
        name VARCHAR NOT NULL,
        secret_name VARCHAR NOT NULL,
        age INTEGER,
        PRIMARY KEY (id)
)

// More SQL boilerplate

INFO Engine [no key 0.00020s] ()
INFO Engine COMMIT

信息

我稍微简化了上面的输出,使其更易阅读。

但实际上,它不是显示

INFO Engine BEGIN (implicit)

它会显示类似

2021-07-25 21:37:39,175 INFO sqlalchemy.engine.Engine BEGIN (implicit)

TEXTVARCHAR

在上一章的示例中,我们使用 TEXT 为某些列创建了表。

但在此输出中,SQLAlchemy 使用的是 VARCHAR。让我们看看发生了什么。

还记得每个 SQL 数据库在支持的功能上都有一些不同的变体吗?

这就是其中一个差异。每个数据库都支持一些特定的数据类型,例如 INTEGERTEXT

有些数据库有一些特殊的类型用于某些特定事物。例如,PostgreSQL 和 MySQL 支持 BOOLEAN 用于 TrueFalse 值。SQLite 接受带布尔值的 SQL,即使在定义表列时也是如此,但它内部实际使用的是 INTEGER,其中 1 代表 True0 代表 False

同样,有几种可能的类型用于存储字符串。SQLite 使用 TEXT 类型。但其他数据库(如 PostgreSQL 和 MySQL)默认使用 VARCHAR 类型,而 VARCHAR 是最常见的数据类型之一。

VARCHAR 来自可变长度字符

SQLAlchemy 生成用于创建表的 SQL 语句时使用 VARCHAR,然后 SQLite 接收它们,并在内部将它们转换为 TEXT

除了这两种数据类型之间的差异之外,有些数据库(如 MySQL)要求为 VARCHAR 类型设置最大长度,例如 VARCHAR(255) 将最大字符数设置为 255。

为了让您更容易立即开始使用 SQLModel,而无需考虑您使用的数据库(即使是 MySQL),也无需任何额外配置,默认情况下,str 字段在大多数数据库中被解释为 VARCHAR,在 MySQL 中被解释为 VARCHAR(255),这样您就知道相同的类无需额外努力即可与最流行的数据库兼容。

提示

您将在高级教程 - 用户指南中学习如何更改字符串列的最大长度。

验证数据库

现在,用 DB Browser for SQLite 打开数据库,您会看到程序创建了与以前完全相同的 hero 表。🎉

重构数据创建

现在让我们稍微重构代码,使其更容易复用共享测试

让我们将具有主要副作用的代码(更改数据:创建带有数据库和表的文件的代码)移到一个函数中。

在此示例中,它只是 SQLModel.metadata.create_all(engine)

我们将其放入函数 create_db_and_tables()

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

# Code below omitted 👇
👀 完整文件预览
from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


if __name__ == "__main__":
    create_db_and_tables()
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


if __name__ == "__main__":
    create_db_and_tables()

如果 SQLModel.metadata.create_all(engine) 不在一个函数中,并且我们尝试从这个模块(从这个文件)导入其他东西,那么每次我们执行导入此模块的其他文件时,它都会尝试创建数据库和表。

我们不希望那样发生,只有当我们有意让它发生时才行,这就是为什么我们把它放在一个函数中,因为我们可以确保只有当我们调用该函数时才创建表,而不是当这个模块被导入到其他地方时。

现在,我们就可以在其他文件中导入 Hero 类,而不会产生这些副作用

提示

😅 剧透警告:该函数名为 create_db_and_tables(),因为将来除了 Hero 之外,我们还会拥有更多的。🚀

将数据创建为脚本

我们阻止了从 app.py 文件导入任何内容时产生的副作用。

但是我们仍然希望它在从终端直接使用 Python 调用它作为独立脚本时创建数据库和表,就像上面那样。

提示

将“脚本”和“程序”这两个词视为可互换。

脚本”一词通常意味着代码可以独立且轻松地运行。或者在某些情况下,它指代一个相对简单的程序。

为此,我们可以在 if 块中使用特殊变量 __name__

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


if __name__ == "__main__":
    create_db_and_tables()
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, SQLModel, create_engine


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


if __name__ == "__main__":
    create_db_and_tables()

关于 __name__ == "__main__"

__name__ == "__main__" 的主要目的是让一些代码在您的文件被以下方式调用时执行

$ python app.py

// Something happens here ✨

...但当另一个文件导入它时不会被调用,例如

from app import Hero

提示

使用 if __name__ == "__main__":if 块有时被称为“主块”。

官方名称(在Python 文档中)是“顶级脚本环境”。

更多细节

假设您的文件名为 myapp.py

如果你用

$ python myapp.py

// This will call create_db_and_tables()

...那么你的文件中的内部变量 __name__(由 Python 自动创建)将具有字符串 "__main__" 作为其值。

所以,函数在

if __name__ == "__main__":
    create_db_and_tables()

...将运行。


如果您导入该模块(文件),则不会发生这种情况。

因此,如果您有另一个文件 importer.py,其中包含

from myapp import Hero

# Some more code

...在这种情况下,myapp.py 内部的自动变量 __name__ 的值不会是 "__main__"

所以,这行代码

if __name__ == "__main__":
    create_db_and_tables()

...将不会被执行。

信息

有关更多信息,请查阅官方 Python 文档

最后复习

这些更改后,您可以再次运行它,它将生成与以前相同的输出。

但现在我们可以从其他文件导入此模块中的内容。

现在,让我们最后看一下代码

from sqlmodel import Field, SQLModel, create_engine  # (2)!


class Hero(SQLModel, table=True):  # (3)!
    id: int | None = Field(default=None, primary_key=True)  # (4)!
    name: str  # (5)!
    secret_name: str  # (6)!
    age: int | None = None  # (7)!


sqlite_file_name = "database.db"  # (8)!
sqlite_url = f"sqlite:///{sqlite_file_name}"  # (9)!

engine = create_engine(sqlite_url, echo=True)  # (10)!


def create_db_and_tables():  # (11)!
    SQLModel.metadata.create_all(engine)  # (12)!


if __name__ == "__main__":  # (13)!
    create_db_and_tables()  # (14)!
  1. typing 导入 Optional 来声明可能为 None 的字段。
  2. sqlmodel 导入我们需要的对象:FieldSQLModelcreate_engine
  3. 创建 Hero 模型类,表示数据库中的 hero 表。

    并使用 table=True 将此类标记为表模型

  4. 创建 id 字段

    它可能为 None,直到数据库为其赋值,因此我们使用 Optional 进行注解。

    它是一个主键,所以我们使用 Field() 和参数 primary_key=True

  5. 创建 name 字段。

    它是必需的,所以没有默认值,也不是 Optional

  6. 创建 secret_name 字段。

    也是必需的。

  7. 创建 age 字段。

    它不是必需的,默认值为 None

    在数据库中,默认值将为 NULL,即 SQL 中 None 的等价物。

    由于此字段可能为 None(在数据库中为 NULL),我们使用 Optional 对其进行注解。

  8. 写入数据库文件的名称。

  9. 使用数据库文件的名称创建数据库 URL。
  10. 使用 URL 创建引擎。

    此时尚未创建数据库,也没有创建文件或表,只创建了将处理与此特定数据库的连接并专门支持 SQLite(基于 URL)的引擎对象。

  11. 将产生副作用的代码放入一个函数中。

    在这种情况下,只有一行代码创建了带表的数据库文件。

  12. 创建所有自动注册到 SQLModel.metadata 中的表。

  13. 添加一个主块,或“顶级脚本环境”。

    并放置一些逻辑,以便在直接用 Python 调用时执行,例如

    $ python app.py
    
    // Execute all the stuff and show the output
    

    ...但导入此模块中的内容时不会执行,例如

    from app import Hero
    
  14. 在此主块中,调用创建数据库文件和表的函数。

    这样当我们用

    $ python app.py
    
    // Doing stuff ✨
    

    ...它将创建数据库文件和表。

from typing import Optional  # (1)!

from sqlmodel import Field, SQLModel, create_engine  # (2)!


class Hero(SQLModel, table=True):  # (3)!
    id: Optional[int] = Field(default=None, primary_key=True)  # (4)!
    name: str  # (5)!
    secret_name: str  # (6)!
    age: Optional[int] = None  # (7)!


sqlite_file_name = "database.db"  # (8)!
sqlite_url = f"sqlite:///{sqlite_file_name}"  # (9)!

engine = create_engine(sqlite_url, echo=True)  # (10)!


def create_db_and_tables():  # (11)!
    SQLModel.metadata.create_all(engine)  # (12)!


if __name__ == "__main__":  # (13)!
    create_db_and_tables()  # (14)!
  1. typing 导入 Optional 来声明可能为 None 的字段。
  2. sqlmodel 导入我们需要的对象:FieldSQLModelcreate_engine
  3. 创建 Hero 模型类,表示数据库中的 hero 表。

    并使用 table=True 将此类标记为表模型

  4. 创建 id 字段

    它可能为 None,直到数据库为其赋值,因此我们使用 Optional 进行注解。

    它是一个主键,所以我们使用 Field() 和参数 primary_key=True

  5. 创建 name 字段。

    它是必需的,所以没有默认值,也不是 Optional

  6. 创建 secret_name 字段。

    也是必需的。

  7. 创建 age 字段。

    它不是必需的,默认值为 None

    在数据库中,默认值将为 NULL,即 SQL 中 None 的等价物。

    由于此字段可能为 None(在数据库中为 NULL),我们使用 Optional 对其进行注解。

  8. 写入数据库文件的名称。

  9. 使用数据库文件的名称创建数据库 URL。
  10. 使用 URL 创建引擎。

    此时尚未创建数据库,也没有创建文件或表,只创建了将处理与此特定数据库的连接并专门支持 SQLite(基于 URL)的引擎对象。

  11. 将产生副作用的代码放入一个函数中。

    在这种情况下,只有一行代码创建了带表的数据库文件。

  12. 创建所有自动注册到 SQLModel.metadata 中的表。

  13. 添加一个主块,或“顶级脚本环境”。

    并放置一些逻辑,以便在直接用 Python 调用时执行,例如

    $ python app.py
    
    // Execute all the stuff and show the output
    

    ...但导入此模块中的内容时不会执行,例如

    from app import Hero
    
  14. 在此主块中,调用创建数据库文件和表的函数。

    这样当我们用

    $ python app.py
    
    // Doing stuff ✨
    

    ...它将创建数据库文件和表。

提示

通过单击代码中的每个数字气泡来回顾每行代码的作用。👆

回顾

我们学习了如何使用 SQLModel 定义数据库中表的结构,并使用 SQLModel 创建了数据库和表。

我们还重构了代码,使其更容易复用、共享和测试。

在接下来的章节中,我们将看到 SQLModel 如何帮助我们通过代码与 SQL 数据库进行交互。🤓