Chapter03 Getting Started with FastAPI
13 FastAPI in a nutshell
FastAPI+React全栈开发13 FastAPI概述
In Chapter 1, We Development and the FARM Stack, I already mentioned why FastAPI is our REST framework of choice in the FARM stack. What sets FastAPI apart from other solutions is its speed of coding and clean code, which enables developers to spot bugs fast and early. The author of the framework himself, Sebastian Ramirez, often modestly emphasizes that FastAPI is just a mix of Starlette and Pydantic, while heavily relying on modern Python features, especially type hinting. Before diving into an example and building a FastAPI app, I believe that it is useful to quickly just go over the concepts that FastAPI is based on so that you know what to expect.
在第一章“我们开发和FARM栈”中,我已经提到了为什么FastAPI是我们在FARM栈中选择的REST框架。FastAPI与其他解决方案的不同之处在于它的编码速度和干净的代码,这使得开发人员能够更快、更早地发现bug。该框架的作者Sebastian Ramirez经常谦虚地强调FastAPI只是Starlette和Pydantic的混合,同时严重依赖于现代Python功能,特别是类型提示。在深入研究一个示例并构建FastAPI应用程序之前,我认为快速浏览一下FastAPI所基于的概念是有用的,这样你就知道会发生什么。
Starlette
Starlette(www.starleette.io) is an ASGI framework that routinely places at the top in various web framework speed contests and provides numerous features that are available in FastAPI as well WebSocket support, envents on startup and shutdown, session and cookie support, background tasks, middleware implementations, templates, and many more. We will not be coding directly in Starlette, but it is very useful to konw how FastAPI works under the hood and what its origins are.
Starlette(www.starleette.io)是一个ASGI框架,经常在各种web框架速度竞赛中名列前茅,并提供了许多在FastAPI中可用的功能,以及WebSocket支持,启动和关闭事件,会话和cookie支持,后台任务,中间件实现,模板等等。我们不会直接在Starlette中编写代码,但是了解FastAPI是如何工作的以及它的起源是非常有用的。
Python type hinting
Type hinting is a feature introduced in Python version 3.5 in an attempt to provide developers with the opportunity to check the types of the variables before runtime. By using type annotations, developers can annotate variables, functions, and classes and give indications of the type that are excpected. It is important to note that these annotations are completely optional and do not make Python a statically typed language! The annotations are ignored by the Python interpreter, but they are picked up by static type checkers that will validate the code and check if it is consistent with the annotations. Code editors and IDEs, such as Visual Studio Code, will be able to provide autocomplete features, thus speeding up coding, while tools such as Mypy will provide helpful error warnings. The syntax for type hinting is as follows.
类型提示是Python 3.5版引入的一项特性,旨在为开发人员提供在运行前检查变量类型的机会。通过使用类型注释,开发人员可以对变量、函数和类进行注释,并给出预期类型的指示。重要的是要注意,这些注释是完全可选的,不会使Python成为静态类型语言!Python解释器会忽略注释,但静态类型检查器会检查代码并检查它是否与注释一致。代码编辑器和ide(如Visual Studio Code)将能够提供自动完成功能,从而加快编码速度,而Mypy等工具将提供有用的错误警告。类型提示的语法如下所示。
```def annotated_function(name: str, age: int) -> str:
return f"Your name is {name.upper()} and you are {age} years old"
print(annotated_function("Marko", 33))
```
Adding the type for the variables is done with a colon, :, while the return type is annotated with an arrow, ->. This simply means that the function takes two parameters, a string name and an integer age, that is supposed to return a string, denoted by the arrow. Note that if you try this function with a string argument for the age variable, you will still get a valid result.
添加变量的类型用冒号:完成,而返回类型用箭头->注释。这仅仅意味着该函数接受两个参数,一个字符串名称和一个整数年龄,它应该返回一个字符串,用箭头表示。注意,如果使用age变量的字符串参数尝试此函数,仍然会得到有效的结果。
Types can be the most basic Python types, such as strings, or integers, but the Typing module hosts numberous data strucctures that can be used when we want to specify that we need a directionary or a list or something more complex, such as a list of dictionaries.
类型可以是最基本的Python类型,如字符串或整数,但Typing模块包含许多数据结构,当我们想要指定我们需要一个指令或列表或更复杂的东西时,可以使用这些数据结构,如字典列表。
Pydantic
Pydantic is a Python library for data balidation, it enforces type hints at runtime and provides user friendly errors, allowing us to catch invalid data as soon as possible, that is, before they make it deep into the system and cause havoc. Although it is a parsing library and not a validation tool, it achieves validation by catching invalid data.
Pydantic是一个用于数据校验的Python库,它在运行时强制类型提示并提供用户友好的错误,允许我们尽快捕获无效数据,也就是说,在它们深入系统并造成破坏之前。虽然它是一个解析库而不是验证工具,但它通过捕获无效数据来实现验证。
If you are working within a virtual environment that already has FastAPI installed, Pydantic will already be there since FastAPI depends on it. If you just want to play with Pydantic in a newly created virtual environment, you can install Pydantic with pip, just make sure that you are in your activated virtual environment and type.
如果你在一个已经安装了FastAPI的虚拟环境中工作,Pydantic应该已经在那里了,因为FastAPI依赖于它。如果您只是想在新创建的虚拟环境中使用Pydantic,那么可以使用pip安装Pydantic,只需确保您处于激活的虚拟环境中并键入。
pip install pydantic
Pydantic enables us to create data models or schemas (not to be confused with MongoDB schemas!), which are essentially a specification of how your data must be structured: wha fields should be present, what their type are, which are strings, which are integers, Booleans, whether any of them are required, whether they should have default values in case no value is provided, and so on.
Pydantic使我们能够创建数据模型或模式(不要与MongoDB模式混淆!),这本质上是数据必须如何结构化的规范:应该存在哪些字段,它们的类型是什么,哪些是字符串,哪些是整数,布尔值,是否需要它们中的任何一个,是否应该在没有提供值的情况下具有默认值,等等。
If you have done a bit of web development, you may have run into the painful issues that arise from the fact that the client of your web application, the user, can send essentially any data that is wants, not only what you wanted the system to ingest and process. Take, for instance, the request body, we will see that FastAPI makes it easy to extract all the data that’s sent through the body, but we want to be able to differentiate various bits of data and only consider what we want and what we allow.
如果你做过一点web开发,你可能会遇到这样一个痛苦的问题:你的web应用程序的客户端,即用户,可以发送任何他想要的数据,而不仅仅是你想让系统摄取和处理的数据。以请求体为例,我们将看到FastAPI可以很容易地提取通过请求体发送的所有数据,但是我们希望能够区分不同的数据位,并且只考虑我们想要的和我们允许的。
Furthermore, we ultimately want to have that data validated. If we require an integer value, we cannot let 5 a string or 3.4 a float pass. Pydantic allows us to explicitly define the expected type and not only on the receiving end, we can use Pydantic to validate and parse output data as well, making sure the response body is exactly how we want it to be, including some pretty complex validations.
此外,我们最终希望对这些数据进行验证。如果需要整数值,则不能让5传递字符串,也不能让3.4传递浮点数。Pydantic允许我们显式地定义期望的类型,不仅在接收端,我们还可以使用Pydantic来验证和解析输出数据,确保响应体正是我们想要的,包括一些相当复杂的验证。
Let’s say that we want to create a simple model for inserting used cars into our database. The model should contain the following fields: brand a sing , model string, year of production integer, fuel, that is, if it is petrol, diesel, or LPG powered enumeration, and a list of countries in which it has been registered (list of strings).
假设我们想创建一个简单的模型,用于将二手车插入到数据库中。模型应包含以下字段:品牌名称,模型字符串,生产年份整数,燃料,即,如果是汽油,柴油或液化石油气动力的枚举,以及它已注册的国家列表(字符串列表)。
Pydantic is based on Python hints, and we can derive our model from Pydantic’s BaseModel class, a class that we will be using to kickstart all of our schemas. Pydantic contains numerous classes for handlingand accommodating different kinds of data, but in the beginning, when definning your models, you will probably start with a BaseModel class, all the models are inherited from this class, so this is the class that you will want to import.
Pydantic基于Python提示,我们可以从Pydantic的BaseModel类派生我们的模型,我们将使用这个类启动所有的模式。Pydantic包含许多用于处理和容纳不同类型数据的类,但在开始时,在定义模型时,您可能会从BaseModel类开始,所有模型都继承自该类,因此这是您想要导入的类。
from enum import Enum
from typing import List
from pydantic import BaseModel, ValidationError
class Fuel(str, Enum):
PETROL = "PETROL"
DIESEL = "DIESEL"
LPG = "LPG"
class Car(BaseModel):
brand: str
model: str
year: int
fuel: Fuel
countries: List[str]
note: str = "No note"
The code may look complicated at first, but it is quite straightforward. First, we imported the Enum class, which enables us to create an enumeration type for the admissible types of fuel. From the typing module, we import List as we will need it to validate our list or countries. Brand and model are declared as string variables, while year is an integer.
这段代码乍一看可能很复杂,但其实非常简单。首先,我们导入Enum类,它使我们能够为可接受的燃料类型创建枚举类型。从输入模块导入List,因为我们需要它来验证我们的列表或国家。Brand和model被声明为字符串变量,而year是一个整数。
Now that we have a model in place, we can explore its capabilities. First, let’s test it out by passing some valid data and using the json() method, one of many methods that Pydantic provides.
现在我们有了一个合适的模型,我们可以探索它的功能。首先,让我们通过传递一些有效数据并使用json()方法(Pydantic提供的众多方法之一)对其进行测试。
from enum import Enum
from typing import List
from pydantic import BaseModel, ValidationError
class Fuel(str, Enum):
PETROL = "PETROL"
DIESEL = "DIESEL"
LPG = "LPG"
class Car(BaseModel):
brand: str
model: str
year: int
fuel: Fuel
countries: List[str]
note: str = "No note"
car = Car(
brand="Lancia",
model="Musa",
fuel="PETROL",
year="2006",
countries=["Italy", "France"],
)
print(car.model_dump())
print(car.model_dump_json())
As you can see, the data is perfectly valid JSON, the countries list is populated (since we haven’t provided any content for the note, it is populated by default) and the year is correctly cast to an integer! This is very good and very useful. Let’s try and pass wome invalid data. Let’s omit model and make year a string that cannot be cast to an integer.
如您所见,数据是完全有效的JSON,国家列表被填充(因为我们没有为注释提供任何内容,它是默认填充的),年份被正确地转换为整数!这非常好,非常有用。让我们尝试传递无效数据。让我们省略model,并使year成为一个不能强制转换为整数的字符串。
To get a nice error message, all we have to do is make use of Pydantic’s ValidationError class and wrap it all in a try-catch block.
要获得一个漂亮的错误消息,我们所要做的就是利用Pydantic的ValidationError类,并将其全部包装在一个try-catch块中。
from enum import Enum
from typing import List
from pydantic import BaseModel, ValidationError
class Fuel(str, Enum):
PETROL = "PETROL"
DIESEL = "DIESEL"
LPG = "LPG"
class Car(BaseModel):
brand: str
model: str
year: int
fuel: Fuel
countries: List[str]
note: str = "No note"
try:
# 如果传递了无效的参数,则会触发错误
car = Car(
brand="Lancia",
fuel="PETROL",
year="abc",
countries=["Italy", "France"],
)
print(car.model_dump_json())
except ValidationError as e:
print(e)
After making this code modification, the command prompt will be gentle to us and pinpoint where it found errors.
修改代码后,命令提示符将变得温和,并指出它发现错误的地方。
2 validation errors for Car
model
Field required [type=missing, input_value={'brand': 'Lancia', 'fuel...s': ['Italy', 'France']}, input_type=dict]
For further information visit https://errors.pydantic.dev/2.5/v/missing
year
Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='abc', input_type=str]
For further information visit https://errors.pydantic.dev/2.5/v/int_parsing
You could play around with other potential errors and try various Pydantic error messages. It is important to point out that in this example, I only used the json() method, but many more are available: dict() for returning a Python dictionary, copy() for creating a deep copy of the model, and so on.
您可以处理其他潜在的错误,并尝试各种Pydantic错误消息。需要指出的是,在本例中,我只使用了json()方法,但还有更多可用的方法:dict()用于返回Python字典,copy()用于创建模型的深度拷贝,等等。
Finally, Pydantic offers individual field validations and with some addtional packages installed, we can perform email validations, URL validations, and anything else that comes to mind. Validation is available at the field level, but also at the object level, when you need to combine different field values into a single condition, for example, to check that two passwords have been entered in two fields on a registration page match.
最后,Pydantic提供了单独的字段验证,通过安装一些附加的包,我们可以执行电子邮件验证、URL验证和任何想到的东西。当您需要将不同的字段值组合成一个条件时(例如,检查是否在注册页面匹配的两个字段中输入了两个密码),可以在字段级别进行验证,也可以在对象级别进行验证。
A pattern that is pretty common when working with Pydantic is the model’s inheritance. You may, for instance, define a basic car model with just the bar minimum fields and hen derive, via inheritance, different car models for editing, for showcasing in an endpoint that will feed an image gallery, and so on, similar to what we did with projections in MongoDB. We will implement this later when we start building our basic app. Another strength of Pydantic is the ability to build complex, nested models by defining schemas (or models) that rely on other or previously defined models, not unlike nesting in MongoDB.
使用Pydantic时非常常见的一个模式是模型的继承。例如,您可以定义一个只有栏最小字段的基本汽车模型,然后通过继承派生不同的汽车模型,用于编辑,用于在将提供图片库的端点中显示,等等,类似于我们在MongoDB中使用投影所做的事情。Pydantic的另一个优势是能够通过定义依赖于其他或先前定义的模型的模式(或模型)来构建复杂的嵌套模型,这与MongoDB中的嵌套模型没有什么不同。
With that, we’ve seen what Pydantic is and how it helps us parse and validate data, as well as complex data structures. However, we’ve only just scratched the surface of what is possible. We haven’t examined the validator decorator or the additional external packages for special validations, but by understanding the basic mechanism of Pydantic, we can see how it makes FastAPI’s data flow safe.
至此,我们已经了解了Pydantic是什么,以及它如何帮助我们解析和验证数据以及复杂的数据结构。然而,我们只是触及了可能性的表面。我们还没有研究验证器装饰器或用于特殊验证的额外外部包,但是通过理解Pydantic的基本机制,我们可以看到它是如何使FastAPI的数据流安全的。
Asynchronous I/O
If you have ever made a web app using Node.js, you may have encountered the asynchronous programming paradigm. The idea is to make operations that are slow compared to others, such as hefty network calls, reading files from a disk, and similar, run, but at the same time allow the system to respond to other calls and then return the appropriate response of the long-running process, while not blocking the other, less time-consuming responses. This is achieved by using an event loop, a manager of asynchronous tasks that receives requests and can move to the next one, even though the previous one hasn’t finished and yielded a response.
如果你曾经使用Node.js制作过一个web应用程序,你可能会遇到异步编程范例。这样做的目的是让运行速度相对较慢的操作(如繁重的网络调用、从磁盘读取文件等),但同时允许系统响应其他调用,然后返回长时间运行的进程的适当响应,同时不阻塞其他耗时较短的响应。这是通过使用事件循环实现的,事件循环是异步任务的管理器,它接收请求并可以移动到下一个任务,即使前一个任务尚未完成并产生响应。
The simplest real-life example would be baking a cake, you could do all the operations sequentially: put the dough in the oven and then grab a chair and sit for 40 miniutes staring at the oven until it is finished. After these 40 minutes, you wait for 10 minutes for the dough to cool off; after that, you make the cream and let it rest for another 20 minutes, and then spend another 10 minutes putting it all together. That would take you 70 minutes. In the async version of our cake, we would put the dough in the oven and start working on the cream right away so that it’s ready by the time the dough is ready and cool, saving 20 minutes of total preparation time. Include some other meals to prepare simultaneously, and the time gains will be much more impressive, but you get the idea.
现实生活中最简单的例子是烤蛋糕,你可以按顺序做所有的操作:把面团放进烤箱,然后找把椅子,盯着烤箱坐40分钟,直到烤好。40分钟后,再等10分钟让面团冷却;之后,你做奶油,让它静置20分钟,然后再花10分钟把它们放在一起。这需要70分钟。在我们的异步版本的蛋糕中,我们会把面团放在烤箱里,然后马上开始做奶油,这样在面团准备好和冷却的时候它就准备好了,节省了20分钟的准备时间。同时准备一些其他的食物,时间的增加会更令人印象深刻,但是你知道的。
Python has added support for asynchronous I/O programming in version 3.4 and added the async/await keywords in version 3.6. ASGI was introduced soon after async made its way into the Python world and the specification outlines how applications should be structured and called. It also defines the events that can be sent and received. FastAPI relies on ASGI and returns an ASGI-compatible app, which is why it is so performant.
Python在3.4版本中增加了对异步I/O编程的支持,并在3.6版本中添加了async/await关键字。ASGI是在async进入Python世界后不久引入的,该规范概述了应用程序应该如何构建和调用。它还定义了可以发送和接收的事件。FastAPI依赖于ASGI并返回一个与ASGI兼容的应用程序,这就是它如此高性能的原因。
Standard REST API studff
I listed the features that make FastAPI our REST API framework of choice in Chapter 1, Web Development and the FARM Stack. So, in this section, I just want to go over some of the terminologies that are pretty common in the realm of developing APIs.
我在第1章“Web开发和FARM Stack”中列出了使FastAPI成为REST API框架的特性。因此,在本节中,我只想回顾一下在开发api领域中非常常见的一些术语。
Our communicaton will occur via the HTTP protocol, through HTTP requests and responses. In this chapter, I will provide an overview of how FastAPI handles both and how it leverages some additional libraries, such as Pydantic, to help us write faster and with fewer bugs. The server that I will be using in all the examples will be Uvicorn, although, in a more general way, the whole FastAPI and Uvicorn part of the code could be considered the server.
我们的通信将通过HTTP协议,通过HTTP请求和响应进行。在本章中,我将概述FastAPI如何处理这两种情况,以及它如何利用一些额外的库(如Pydantic)来帮助我们更快地编写代码,减少错误。我将在所有示例中使用的服务器将是Uvicorn,尽管在更一般的方式下,整个FastAPI和Uvicorn部分代码可以被认为是服务器。
The basis of any REST API communicaton is the relevant URLs and paths. The URL for our local web development server will be http://localhost:8000 since 8000 is the default port that Uvicorn uses. The path part (optional) of an endpoint could be /cars, while http is the scheme. We will see how FastAPI handles paths, why the order when defining endpoint functions in our code matters, and how we can extract variables from dynamic portions of the path in a simple way.
任何REST API通信的基础都是相关的url和路径。我们本地web开发服务器的URL将是http://localhost:8000,因为8000是Uvicorn使用的默认端口。端点的路径部分(可选)可以是/cars,而http是方案。我们将看到FastAPI如何处理路径,为什么在代码中定义端点函数的顺序很重要,以及如何以简单的方式从路径的动态部分提取变量。
Every path or address, the URL and the path, provides a list of approved actions that can be performed on it, HTTP verbs. For example, there might be a page or a URL that lists all the cars on sale, but you cannot issue a POST request to it since this is not allowed.
每个路径或地址(URL和路径)都提供了可在其上执行的已批准操作的列表,即HTTP动词。例如,可能有一个页面或URL列出了所有正在销售的汽车,但是您不能向它发出POST请求,因为这是不允许的。
In FastAPI, these verbs are implemented as Python decorators. To put it better, they are exposed as decorators, and they are implemented only if you, the developer, implement them.
在FastAPI中,这些动词被实现为Python装饰器。换句话说,它们是作为装饰器公开的,并且只有当您(开发人员)实现它们时才会实现它们。
FastAPI encourages the proper use of HTTP verbs concerning the data-resource operations that they perform, so you should always use POST (or the @post decorator) when creating new resources.
FastAPI鼓励正确使用HTTP动词来处理它们所执行的数据资源操作,因此在创建新资源时应该始终使用POST(或@post装饰器)。
Finally, HTTP messages consist of a request/status line, headers, and, optionally, body data. Again, FastAPI offers us tools to easily create and modify headers, set response codes, and do pretty much anything that we please with the request and response body. It does so in a very clean and intuitive way, as we will see shortly.
最后,HTTP消息由请求/状态行、标头和(可选的)正文数据组成。同样,FastAPI为我们提供了工具,可以轻松地创建和修改标头、设置响应代码,以及对请求和响应体做任何我们喜欢的事情。我们很快就会看到,它以一种非常干净和直观的方式做到了这一点。
In this section, we have tried to pinpoint the programming concepts and specific Python features that FastAPI is built on and enable it to be so performant and produce maintainable code. In the next section, we will go over some standard REST API operations and see how they are achieved with FastAPI.
在本节中,我们试图找出构建FastAPI的编程概念和特定的Python特性,并使其具有如此高的性能并生成可维护的代码。在下一节中,我们将讨论一些标准的REST API操作,并了解如何使用FastAPI实现这些操作