跳到内容

使用 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()

...那么你的文件中由 Python 自动创建的内部变量 __name__ 的值将是字符串 "__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 创建引擎。

    此时,这并不会创建数据库,也不会创建文件或表,只会创建处理与此特定数据库连接的引擎对象,并根据 URL 提供对 SQLite 的特定支持。

  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 创建引擎。

    此时,这并不会创建数据库,也不会创建文件或表,只会创建处理与此特定数据库连接的引擎对象,并根据 URL 提供对 SQLite 的特定支持。

  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 数据库进行交互。 🤓