编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

FastAPI教程:5 Pydantic,类型提示和模型之旅

wxchong 2024-07-23 21:41:48 开源技术 44 ℃ 0 评论

预览

FastAPI主要基于一个名为Pydantic的Python包。 这使用(Python 对象类)来 定义数据结构。 这些在 FastAPI 应用程序中大量使用, 在编写大型应用程序时是一个真正的优势。

类型提示

是时候 了解有关 Python 的更多信息。

第2章提到,在许多计算机语言中, 变量直接指向内存中的值。 这要求程序员声明其类型,因此 可以确定值的大小和位数。 在 Python 中,变量只是与对象关联的名称, 它是具有类型的对象。

在正常编程中, 变量通常与 相同的对象。 如果我们将类型提示与该变量相关联, 我们可以避免一些编程错误。 所以Python在语言中添加了类型提示, 在标准模块键入中。 Python 解释器忽略 该类型提示语法并运行程序,就好像它不存在一样。 那有什么意义呢?

您可以将变量视为一行中的字符串, 稍后忘记并为其分配不同类型的对象。 尽管其他语言的编译器会抱怨, Python不会。 标准的 Python 解释器将捕获 正常语法错误和运行时异常。 但不混合变量的类型。 像 Mypy 这样的辅助工具会注意类型提示, 并警告您 任何不匹配。

此外,提示可供 Python 开发人员使用, 谁可以编写比类型错误检查更多的工具。 以下各节介绍了 Pydantic 软件包如何 是为了满足并不明显的需求而开发的。 稍后,您将看到它如何与FastAPI集成 使许多Web开发问题更容易处理。

顺便问一下,类型提示是什么样的? 变量有一种语法, 另一个用于函数返回值。

变量类型提示可能仅包含以下类型:

name: type

或者还用一个值初始化变量:

name: type = value

可以是标准的 Python 简单类型之一,如 int 和 str 或集合类型,如元组、列表和字典。

thing: str = "yeti"

注意

在 Python 3.9 之前,您需要导入大写版本 来自键入模块的这些标准类型名称:

 from   typing   import   Str 
 thing  :   Str   =   "yeti" 

初始化示例:

 physics_magic_number  :   float   =   1.0  /  137.03599913 
 hp_lovecraft_noun  :   str   =   "ichor" 
 exploding_sheep  :   tuple   =   "sis"  ,   "boom"  ,   bah  !  " 
 responses  :   dict   =   {  "Marco"  :   "Polo"  ,   "answer"  :   42  } 

您还可以包括集合的子类型:

name: dict[keytype, valtype] = {key1: val1, key2: val2}

键入模块具有子类型的有用附加功能, 最常见的是:

  • 任何 : 任何类型
  • 联合 :指定的任何类型,例如 Union[str, int] 。

注意

在 Python 3.10 及更高版本中,你可以说 type1 |类型2 改为 联合[类型1,类型2]。

例子:

 from   typing   import   Any 
 responses  :   dict  [  str  ,   Any  ]   =   {  "Marco"  :   "Polo"  ,   "answer"  :   42  } 

或者,更具体一点:

 from   typing   import   Union 
 responses  :   dict  [  str  ,   Union  [  str  ,   int  ]]   =   {  "Marco"  :   "Polo"  ,   "answer"  :   42  } 

或(Python 3.10 及更高版本):

 responses  :   dict  [  str  ,   str   |   int  ]   =   {  "Marco"  :   "Polo"  ,   "answer"  :   42  } 

请注意,类型提示的变量行是合法的 Python, 但裸变量线不是:

$ python
...
>>> thing0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name thing0 is not defined
>>> thing0: str

此外,常规不会捕获不正确的类型用法 蟒蛇解释器:

$ python
...
>>> thing1: str = "yeti"
>>> thing1 = 47

但他们会被Mypy抓住。 如果您还没有它,请 pip 安装 mypy . 将上面的这两行保存到名为 的文件中, 然后试试这个:

$ mypy stuff.py
stuff.py:2: error: Incompatible types in assignment
(expression has type "int", variable has type "str")
Found 1 error in 1 file (checked 1 source file)

函数返回类型提示使用箭头而不是冒号:

function(args) -> type:

如:

 def   get_thing  ()   ->   str  : 
    return   "yeti" 

可以使用任何类型,包括已定义的类, 以及它们的组合。 您将在几页中看到这一点。

数据分组

通常,我们需要将一组相关的变量放在一起, 而不是传递单个变量的日志。 我们如何将多个变量整合为一个组, 并保留类型提示?

让我们抛开前几章中不温不火的问候示例, 并从现在开始使用更丰富的数据。 与本书的其余部分一样, 我们将使用生物(虚构生物)的例子, 以及寻找它们的(也是虚构的)。 我们最初的生物定义将只包括、和的字符串变量。

Python 的历史数据分组结构 (超越基本的整数、字符串等) 是:

  • 元组:不可变的对象序列
  • 列表:可变的对象序列
  • 集合:可变的不同对象
  • 字典:可变键:值对象对(键必须是不可变类型)

元组(示例 5-1)和列表(示例 5-2) 仅允许您通过偏移量访问成员变量, 所以你必须记住什么去了哪里:

使用元组

 >>>   tuple_thing   =   (  "yeti"  ,   "Abominable Snowman"  ,   "Himalayas"  ) 
 >>>   print  (  "Name is"  ,   tuple_thing  [  0  ]) 
 Name   is   yeti 

使用列表

 >>>   list_thing   =   [  "yeti"  ,   "Abominable Snowman"  ,   "Himalayas"  ] 
 >>>   print  (  "Name is"  ,   list_thing  [  0  ]) 
 Name   is   yeti 

示例 5-3 显示您可以通过以下方式获得更多解释 定义整数偏移量的名称:

使用元组和命名偏移量

 >>>   NAME   =   0 
 >>>   LOCATION   =   1 
 >>>   DESCRIPTION   =   2 
 >>>   tuple_thing   =   (  "yeti"  ,   "Abominable Snowman"  ,   "Himalayas"  ) 
 >>>   print  (  "Name is"  ,   tuple_thing  [  NAME  ]) 
 Name   is   yeti 

字典在示例 5-4 中稍微好一点, 通过描述性键为您提供访问权限:

使用字典

 >>>   dict_thing   =   {  "name"  :   "yeti"  , 
 ...       "description"  :   "Abominable Snowman"  , 
 ...       "location"  :   "Himalayas"  } 
 >>>   print  (  "Name is"  ,   dict_thing  [  "name"  ]) 
 Name   is   yeti 

集合仅包含唯一值,因此它们对 聚类不同的变量。

在示例 5-5 中,为 允许您通过整数偏移名称进行访问的元组:

例 5-5。

>>> from collections import namedtuple
>>> CreatureNamedTuple = namedtuple("CreatureNamedTuple",
...     "name, description, location)
>>> namedtuple_thing = CreatureNamedTuple("yeti",
...     "Abominable Snowman",
...     "Himalayas")
>>> print("Name is", namedtuple_thing[0])
Name is yeti
>>> print("Name is", namedtuple_thing.name)
Name is yeti

注意

你不能说namedtuple_thing[“名字”]。 这是一个元组,而不是字典, 所以索引需要是一个整数。

例5-6定义了一个新的Python类, 并添加所有 属性与自我 . 但是你需要做很多输入来定义它们:

使用普通类

 >>>   class   CreatureClass  (): 
 ...       def   __init__  (  self  ,   name  :   str  ,   description  :   str  ,   location  :   str  ): 
 ...           self  .  name   =   name 
 ...           self  .  description   =   description 
 ...           self  .  location   =   location 
 ... 
 >>>   class_thing   =   CreatureClass  (  "yeti"  , 
 ...       "Abominable Snowman"  , 
 ...       "Himalayas"  ) 
 >>>   print  (  "Name is"  ,   class_thing  .  name  ) 
 Name   is   yeti 

注意

你可能会想,这有什么不好的? 使用常规类,您可以添加更多数据(属性), 但尤其是行为(方法)。 你可能会决定,有一天疯狂的一天, 添加查找资源管理器的方法 最喜欢的歌曲。 (这不适用于 生物.) 但这里的用例只是移动一堆数据 坦然 在层中,并在进出途中进行验证。 此外,方法是方形钉子 这将很难适应数据库的圆孔。

Python有类似的东西吗 到什么其他计算机语言 调用或(一组名称和值)? Python最近增加的一个是。 示例 5-7 显示了所有这些自我填充的内容 随数据类一起消失:

使用数据类

 >>>   from   dataclasses   import   dataclass 
 >>> 
 >>>   @dataclass 
 ...   class   CreatureDataClass  (): 
 ...       name  :   str 
 ...       description  :   str 
 ...       location  :   str 
 ... 
 >>>   dataclass_thing   =   CreatureDataClass  (  "yeti"  , 
 ...       "Abominable Snowman"  ,   "Himalayas"  ) 
 >>>   print  (  "Name is"  ,   dataclass_thing  .  name  ) 
 Name   is   yeti 

这对于将变量保持在一起部分非常有用。 但我们想要更多,所以让我们向圣诞老人要求:

  • 可能的替代类型的
  • 缺失/可选值
  • 默认值
  • 数据验证
  • 与 JSON 等格式的序列化

选择

使用Python内置的数据结构很诱人, 尤其是字典。 但你不可避免地会发现字典有点太“松散”。 自由是有代价的。 您需要检查:

  • 密钥是可选的吗?
  • 如果缺少密钥,是否有默认值?
  • 密钥是否存在?
  • 如果是这样,键的值类型是否正确?
  • 如果是这样,该值是否在正确的范围内,或者与模式匹配?

至少有三种解决方案可以解决至少 其中一些要求:

  • : 标准 Python 的一部分。
  • : 第三方,但数据类的超集。
  • : 也是第三方,但集成到FastAPI中, 所以如果你已经在使用FastAPI,这是一个简单的选择。 如果你正在读这本书,那很有可能。

在Youtube上对这三者进行了。 一个结论是,Pydantic在验证中脱颖而出, 它与FastAPI的集成抓住了许多潜力 数据错误。 另一个是Pydantic依赖于继承(来自 基本模型类), 另外两个使用Python装饰器来定义他们的对象。 这更多的是风格问题。

在另一个中, Pydantic优于旧的验证包,如和有趣的。 Pydantic的另一大优势是它使用标准 蟒蛇类型提示语法; 较旧的库早于类型提示并自行推出。

所以我在这本书中选择了Pydantic, 但您可能会发现这两种选择的用途 如果您没有使用快速API。

Pydantic 提供了指定这些检查的任意组合的方法:

  • 必需与可选
  • 默认值(如果未指定但必需)
  • 预期的数据类型
  • 值范围限制
  • 其他基于功能的检查(如果需要)
  • 序列化和反序列化

一个简单的例子

你已了解如何将简单字符串馈送到 Web 终结点 通过 URL、查询参数或 HTTP 正文。 问题是通常您通常会请求和接收 多种类型的数据组。

这就是Pydantic模型首次出现在FastAPI中的地方。

此初始示例将使用三个文件:

  • 定义了一个 Pydantic 模型
  • 是假数据源,定义模型的实例
  • 定义了一个返回假数据的 FastAPI Web 端点

为简单起见,本章, 让我们将所有文件保留在同一目录中 目前。 在后面讨论大型网站的章节中, 我将将它们分成各自的层。

首先,定义示例 5-8 中生物的:

定义生物模型:model.py

 from   pydantic   import   BaseModel 

 class   Creature  (  BaseModel  ): 
     name  :   str 
     description  :   str 
     location  :   str 

 thing   =   Creature  (  name  =  "yeti"  , 
     description  =  "Abominable Snowman"  , 
     location  =  "Himalayas"  ) 
 print  (  "Name is"  ,   thing  .  name  ) 

Creature 类继承自 Pydantic 的 BaseModel。 那 : str 部分 在名称、位置和描述之后 是一个类型提示,每个都是一个 Python 字符串。

注意

在此示例中,所有三个字段都是必填字段。 在 Pydantic 中,如果类型描述中没有“可选”, 该字段必须具有值。

在示例 5-9 中, 如果包含参数的名称,则按任意顺序传递参数:

创建一个生物

 >>>   thing   =   Creature  (  name  =  "yeti"  , 
 ...       description  =  "Abominable Snowman"  , 
 ...       location  =  "Himalayas"  ) 
 >>>   print  (  "Name is"  ,   thing  .  name  ) 
 Name   is   yeti 

目前,示例 5-10 定义了一个很小的数据源; 在后面的章节中,数据库将执行此操作。 类型提示列表[生物] 告诉 Python 这只是一个 Creature 对象列表。

在 data.py 中定义假数据

 from   model   import   Creature 

 _creatures  :   list  [  Creature  ]   =   [ 
     Creature  (  name  =  "yeti"  , 
              description  =  "Abominable Snowman"  , 
              location  =  "Himalayas"  ) 
     Creature  (  name  =  "sasquatch"  , 
              description  =  "Bigfoot"  , 
              location  =  "North America"  ) 
 ] 

 def   get_creatures  ()   ->   list  [  Creature  ]: 
     return   _creatures 

这段代码导入了我们刚刚编写。 它通过调用其列表来隐藏一些数据 生物对象_creatures,并提供功能 get_creatures() 返回它们。

示例 5-11 列出了 , 定义 FastAPI Web 终结点的文件:

定义快速 API Web 终结点:web.py

 from   model   import   Creature 
 from   fastapi   import   FastAPI 

 app   =   FastAPI  () 

 @app  .  get  (  "/creature"  ) 
 def   get_all  ()   ->   list  [  Creature  ]: 
     from   data   import   get_creatures 
     return   get_creatures  () 

现在在示例 5-12 中启动此单端点服务器:

启动 Uvicorn

$ uvicorn creature:app
INFO:     Started server process [24782]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

在另一个窗口中,示例 5-13 使用 Httpie Web 客户端访问它 (如果您愿意,也可以尝试使用浏览器或请求模块):

使用 Httpie 进行测试

$ http http://localhost:8000/creature
HTTP/1.1 200 OK
content-length: 183
content-type: application/json
date: Mon, 12 Sep 2022 02:21:15 GMT
server: uvicorn

[
    {
        "description": "Abominable Snowman",
        "location": "Himalayas",
        "name": "yeti"
    },
    {
        "description": "Bigfoot",
        "location": "North America",
        "name": "sasquatch"
    }

FastAPI 和 Starlette 自动转换了原始内容 生物模型对象列表转换为 JSON 字符串。 这是 FastAPI 中的默认输出格式, 所以我们不需要指定它。

此外,您最初在其中启动的窗口 Uvicorn 网络服务器应该 已打印日志行:

INFO:     127.0.0.1:52375 - "GET /creature HTTP/1.1" 200 OK

验证类型

上一节演示了如何:

  • 将类型提示应用于变量和函数
  • 定义和使用 Pydantic 模型
  • 从数据源返回模型列表
  • 将模型列表返回到 Web 客户端, 自动将模型列表转换为 JSON

现在,让我们真正使用它来验证数据。

尝试将错误类型的值分配给一个或多个 的生物领域。 让我们为此使用一个独立的测试 (Pydantic 不回复任何网络代码; 这是一个数据问题)。

示例 5-14 列出了 :

测试生物模型

 from   model   import   Creature 

 dragon   =   Creature  ( 
     name  =  "dragon"  , 
     description  =  [  "incorrect"  ,   "string"  ,   "list"  ], 
     location  =  "Worldwide" 
     ) 

现在在示例 5-15 中尝试一下:

运行测试

$ python test1.py
Traceback (most recent call last):
  File ".../test1.py", line 3, in <module>
    dragon = Creature(
  File "pydantic/main.py", line 342, in
    pydantic.main.BaseModel.init
    pydantic.error_wrappers.ValidationError:
    1 validation error for Creature description
  str type expected (type=type_error.str)

这发现我们将字符串列表分配给 描述字段, 它想要一根普通的旧字符串。

验证值

即使值的类型与其规范匹配 在生物类中, 可能需要通过更多检查。 可以对值本身施加一些限制。

  • 整数 ( conint ) 或浮点数:
    • gt — 大于
    • lt — 小于
    • ge — 大于或等于
    • le — 小于或等于
    • multiple_of — 值的整数倍
  • 字符串 ( constr ):
    • min_length — 最小字符(非字节)长度
    • max_length — 最大字符长度
    • to_upper — 转换为大写
    • to_lower — 转换为小写
    • 正则表达式 — 匹配 Python 正则表达式
  • 元组、列表或集合:
    • min_items — 最小元素数
    • max_items — 元素的最大间隔

这些在模型的类型部分中指定。

示例 5-1 确保名称字段始终为 长度至少为两个字符。 否则,“”(空字符串)是有效的字符串。

查看验证失败

 >>>   from   pydantic   import   BaseModel  ,   constr 
 >>> 
 >>>   class   Creature  (  BaseModel  ): 
 ...       name  :   constr  (  min_length  =  2  ) 
 ...       description  :   str 
 ...       location  :   str 
 ... 
 >>>   bad_creature   =   Creature  (  name  =  "!"  , 
 ...       description  =  "it's a raccoon"  , 
 ...       location  =  "your attic"  ) 
 Traceback   (  most   recent   call   last  ): 
   File   "<stdin>"  ,   line   1  ,   in   <  module  > 
   File   "pydantic/main.py"  ,   line   342  , 
   in   pydantic  .  main  .  BaseModel  .  __init__ 
 pydantic  .  error_wrappers  .  ValidationError  : 
 1   validation   error   for   Creature   name 
   ensure   this   value   has   at   least   2   characters 
   (  type  =  value_error  .  any_str  .  min_length  ;   limit_value  =  2  ) 

该 constr 表示一个。 示例 5-17 使用替代方法, 皮丹蒂克场规范:

另一个验证失败,使用 Field

 >>>   from   pydantic   import   BaseModel  ,   Field 
 >>> 
 >>>   class   Creature  (  BaseModel  ): 
 ...       name  :   str   =   Field  (  ...  ,   min_length  =  2  ) 
 ...       description  :   str 
 ...       location  :   str 
 ... 
 >>>   bad_creature   =   Creature  (  name  =  "!"  , 
 ...       location  =  "your attic"  , 
 ...       description  =  "it's a raccoon"  ) 
 Traceback   (  most   recent   call   last  ): 
   File   "<stdin>"  ,   line   1  ,   in   <  module  > 
   File   "pydantic/main.py"  ,   line   342  , 
   in   pydantic  .  main  .  BaseModel  .  __init__ 
 pydantic  .  error_wrappers  .  ValidationError  : 
 1   validation   error   for   Creature   name 
   ensure   this   value   has   at   least   2   characters 
   (  type  =  value_error  .  any_str  .  min_length  ;   limit_value  =  2  ) 

那。。。参数到 Field() 表示需要一个值, 并且没有默认值。

这是对Pydantic的最小介绍。 主要要点是它可以让您自动验证 您的数据。 您将看到这在从以下位置获取数据时是多么有用 网络或数据结束。

回顾

模型是定义数据的最佳方式,这些数据将 在 Web 应用程序中传递。 Pydantic 利用 Python 来定义要在应用程序中传递的数据模型。

接下来:定义要分离的 常规代码中的特定详细信息。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表