3 快速入门
第二章是python基础,故不做介绍。
fastapi是一个现代、快速(高性能)的网络框架,用于使用基于标准python 类型提示的python 3.6+构建api。
fastapi的创建者是sebastián ramírez。
fastapi由sebastián ramírez于2018年发布。与大多数python web框架相比,它在很多方面都更加现代化--充分利用了过去几年中添加到python 3 中的功能。本章将快速介绍fastapi的主要功能,重点是您首先需要了解的内容:如何处理web请求和响应。
3.1 fastapi简介
与其他网络框架一样,fastapi 可以帮助您构建网络应用程序。每个框架的设计都是为了通过功能、遗漏和默认设置来简化某些操作。顾名思义,fastapi的目标是开发网络api,不过您也可以将其用于传统的网络内容应用程序。
fastapi具有以下优势:
-
性能:在某些情况下与node.js和go一样快,与python框架不同。
-
快速开发:容易上手,简单清晰。
-
代码质量更高:类型提示和模型有助于减少错误。
-
自动生成文档和测试页面:比手工编辑openapi说明容易得多。
fastapi 使用以下功能:
- python 类型提示
- 用于网络机制的 starlette,包括异步支持
- 用于数据定义和验证的 pydantic
- 用于利用和扩展其他工具的特殊集成
这种组合为网络应用程序(尤其是 restful 网络服务)提供了令人满意的开发环境。
3.2 fastapi应用
让我们编写一个小巧的 fastapi 应用程序--只有一个端点的网络服务。现在,我们处于我所说的网络层,只处理网络请求和响应。首先,安装我们要用到的基本 python 包:
- fastapi 框架: pip install fastapi
- uvicorn 网络服务器: pip install uvicorn
- httpie 文本网络客户端: pip install httpie
- 请求同步网络客户端软件包: pip install requests
- httpx 同步/异步网络客户端软件包: pip install httpx
虽然curl是最著名的文本网络客户端,但我认为httpie更容易使用。此外,它默认使用json编码和解码,更适合 fastapi。在本章后面,你会看到一张截图,其中包含访问特定端点所需的curl命令行语法。
hello.py:
from fastapi import fastapi app = fastapi() @app.get("/hi") def greet(): return "hello? world?"
- app是顶级fastapi对象,代表整个网络应用程序。
- @app.get("/hi") 是一个路径装饰器。它告诉 fastapi 以下内容:
- 在此服务器上对url"/hi"的请求应被指向以下函数。
- 此装饰器仅适用于 http get verb。您也可以使用其他 http 动词(put、post 等)来响应"/hi"url,每个动词都有一个单独的函数。
- def greet()是一个路径函数--http 请求和响应的主要连接点。
下一步是在网络服务器中运行该网络应用程序。fastapi本身不包含网络服务器,但推荐使用uvicorn。您可以通过两种方式启动uvicorn和fastapi 网络应用程序:外部启动或内部启动。
从外部通过命令行启动uvicorn 的方法:
$ uvicorn hello:app --reload
其中hello指的是hello.py文件,app是其中的fastapi变量名。内部启动如下:
from fastapi import fastapi app = fastapi() @app.get("/hi") def greet(): return "hello? world?" if __name__ == "__main__": import uvicorn uvicorn.run("hello:app", reload=true, host=r'0.0.0.0', port=8008)
无论哪种情况,如果hello.py发生变化,reload都会告诉 uvicorn 重新启动网络服务器。在本章中,我们将经常使用自动重载功能。默认情况下都会使用你机器上的 8000 端口(名为 localhost)。如果你想使用其他方法,外部方法和内部方法都有主机和端口参数。
现在服务器有了一个端点(/hi),可以随时接受请求了。让我们用多个网络客户端进行测试:
- 对于浏览器,在顶部位置栏键入url。
- httpie
$ http localhost:8008/hi http/1.1 200 ok content-length: 15 content-type: application/json date: thu, 06 jun 2024 01:41:28 gmt server: uvicorn "hello? world?" # 使用 -b 参数跳过响应头,只打印正文。 $ http -b localhost:8008/hi "hello? world?" # 使用 -v 获取完整的请求头和响应。 # http -v localhost:8008/hi get /hi http/1.1 accept: */* accept-encoding: gzip, deflate connection: keep-alive host: localhost:8008 user-agent: httpie/3.2.2 http/1.1 200 ok content-length: 15 content-type: application/json date: thu, 06 jun 2024 01:51:23 gmt server: uvicorn "hello? world?"
- requests 或 httpx
>>> import requests >>> r = requests.get("http://localhost:8008/hi") >>> r.json() 'hello? world?' >>> import httpx >>> r = httpx.get("http://localhost:8008/hi") >>> r.json() 'hello? world?'
3.3 http请求
上面第一个httpie请求包含以下内容:
- 动词 (get) 和路径 (/hi)
- 查询参数(此处为"无",有的话跟在问号后面)
- 其他 http 头信息
- 正文内容(无请求)
fastapi 将这些信息归纳为方便的定义:header、path、query、body。
fastapi从http请求的各个部分提供数据的方式是其最大的特点之一,也是对大多数python web框架的改进。您需要的所有参数都可以直接在path函数中声明和提供,使用前面列表中的定义 (path、query 等),以及您编写的函数。这使用了一种名为"依赖注入"的技术,我们将继续讨论这种技术,并在第 6 章中进一步阐述。
3.3.1 url path
让我们在前面的应用程序中添加一个名为 who 的参数,向某人发送 hello? 我们将尝试不同的方法来传递这个新参数:
from fastapi import fastapi app = fastapi() @app.get("/hi/{who}") def greet(who): return f"hello? {who}?" if __name__ == "__main__": import uvicorn uvicorn.run("hello:app", reload=true, host=r'0.0.0.0', port=8008)
在 url 中(@app.get 之后)添加 {who},会告诉 fastapi 在 url 中的该位置期待一个名为 who 的变量。然后,fastapi 将其赋值给下面 greet() 函数中的 who 参数。这显示了路径装饰器和路径函数之间的协调。
注意不要在此处使用 python f-string 来表示修改后的 url 字符串 ("/hi/{who}")。
from fastapi import fastapi app = fastapi() @app.get("/hi/{who}") def greet(who): return f"hello? {who}?" if __name__ == "__main__": import uvicorn uvicorn.run("hello:app", reload=true, host=r'0.0.0.0', port=8008)
执行:
# http -b localhost:8008/hi/test "hello? test?" # http -b localhost:8008/hi { "detail": "not found" }
参考资料
- 软件测试精品书籍文档下载持续更新 https://github.com/china-testing/python-testing-examples 请点赞,谢谢!
- 本文涉及的python测试开发库 谢谢点赞! https://github.com/china-testing/python_cn_resouce
- python精品书籍下载 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
- linux精品书籍下载
3.3.2查询参数
查询参数是 url中?后面的name=value字符串,用&字符分隔。
from fastapi import fastapi app = fastapi() @app.get("/hi") def greet(who): return f"hello? {who}?" if __name__ == "__main__": import uvicorn uvicorn.run("hello:app", reload=true, host=r'0.0.0.0', port=8008)
执行:
# http localhost:8008/hi?who=mom http/1.1 200 ok content-length: 13 content-type: application/json date: thu, 06 jun 2024 02:58:24 gmt server: uvicorn "hello? mom?" # http localhost:8008/hi http/1.1 422 unprocessable entity content-length: 89 content-type: application/json date: thu, 06 jun 2024 02:58:31 gmt server: uvicorn { "detail": [ { "input": null, "loc": [ "query", "who" ], "msg": "field required", "type": "missing" } ] } # 注意两个等号为get, 冒号表示http头,一个等号为post, # http -b localhost:8008/hi who==mom "hello? mom?"
3.3.3 post
from fastapi import fastapi, body app = fastapi() @app.post("/hi") def greet(who:str = body(embed=true)): return f"hello? {who}?" if __name__ == "__main__": import uvicorn uvicorn.run("hello:app", reload=true, host=r'0.0.0.0', port=8000)
执行:
# /root/code/fastapi/example^c root@www:~/code/fastapi/example# http -v localhost:8000/hi who=mom post /hi http/1.1 accept: application/json, */*;q=0.5 accept-encoding: gzip, deflate connection: keep-alive content-length: 14 content-type: application/json host: localhost:8000 user-agent: httpie/3.2.2 { "who": "mom" } http/1.1 200 ok content-length: 13 content-type: application/json date: thu, 06 jun 2024 03:25:34 gmt server: uvicorn "hello? mom?"
需要使用 body(embed=true) 来告 fastapi,这次我们从json格式的请求正文中获取who的值。embed 部分意味着它看起来应该像 {"who": "mom"},而不仅仅是 "mom"。
3.3.4 http 头信息
from fastapi import fastapi, header app = fastapi() @app.get("/hi") def greet(who:str = header()): return f"hello? {who}?" if __name__ == "__main__": import uvicorn uvicorn.run("hello:app", reload=true, host=r'0.0.0.0', port=8000)
执行:
# http -v localhost:8000/hi who:mom get /hi http/1.1 accept: */* accept-encoding: gzip, deflate connection: keep-alive host: localhost:8000 user-agent: httpie/3.2.2 who: mom http/1.1 200 ok content-length: 13 content-type: application/json date: thu, 06 jun 2024 06:09:55 gmt server: uvicorn "hello? mom?"
3.3.5 多重请求数据
你可以在同一个路径函数中使用多个方法。也就是说,你可以从 url、查询参数、http 主体、http 头文件、cookie等获取数据。你还可以编写自己的依赖函数,以特殊方式处理和组合这些数据,例如用于分页或身份验证。
3.3.6哪种方法最好?
以下是一些建议:
- 在url中传递参数时,遵循 restful 准则是标准做法。
- 查询字符串通常用于提供可选参数,如分页。
- body通常用于较大的输入,如整个或部分模型。
在每种情况下,如果您在数据定义中提供了类型提示,pydantic就会自动对您的参数进行类型检查。这将确保参数的存在和正确性。
3.4 http响应
默认情况下fastapi会将返回的内容转换为json;http响应的头行内容类型为:application/json。
3.4.1 状态代码
fastapi默认返回200状态代码;异常会产生4xx代码。在路径装饰器中,指定在一切顺利的情况下应返回的 http 状态代码可以改写返回(异常会生成自己的代码并覆盖它)。
from fastapi import fastapi, body app = fastapi() @app.post("/hi") def greet(who:str = body(embed=true)): return f"hello? {who}?" if __name__ == "__main__": import uvicorn uvicorn.run("hello:app", reload=true, host=r'0.0.0.0', port=8000)
执行:
~# http localhost:8000/happy http/1.1 200 ok content-length: 4 content-type: application/json date: thu, 06 jun 2024 06:39:00 gmt server: uvicorn ":)" # http localhost:8000/happy3 http/1.1 404 not found content-length: 22 content-type: application/json date: thu, 06 jun 2024 06:40:32 gmt server: uvicorn { "detail": "not found" }
3.4.2 headers
from fastapi import fastapi, body, response app = fastapi() @app.get("/header/{name}/{value}") def header(name: str, value: str, response:response): response.headers[name] = value return "normal body" if __name__ == "__main__": import uvicorn uvicorn.run("hello:app", reload=true, host=r'0.0.0.0', port=8000)
执行:
# http localhost:8000/header/marco/polo http/1.1 200 ok content-length: 13 content-type: application/json date: thu, 06 jun 2024 06:48:10 gmt marco: polo server: uvicorn "normal body"
3.4.3 响应类型
响应类型(从 fastapi.responses 中导入这些类)包括以下内容:
- jsonresponse(默认值)
- htmlresponse
- plaintextresponse
- redirectresponse
- fileresponse
- streamingresponse
关于后两种,我将在第15章中详细介绍。对于其他输出格式(也称为 mime 类型),可以使用通用的 response 类,它需要以下内容:
-
content:字符串或字节
-
media_type:字符串 mime 类型
-
status_code:http 整数状态代码
-
headers:字符串
3.4.4 类型转换
路径函数可以返回任何内容,默认情况下(使用 jsonresponse),fastapi 会将其转换为 json 字符串并返回,同时返回与之匹配的 http 响应头 content-length 和 content-type。这包括任何 pydantic 模型类。
但它是如何做到这一点的呢?如果您使用过 python json 库,您可能会发现,当给定某些数据类型(如日期时间)时,它会引发异常。fastapi 使用名为 jsonable_encoder() 的内部函数将任何数据结构转换为 "jsonable" python 数据结构,然后调用 json.dumps() 将其转换为 json 字符串。
import datetime import json import pytest from fastapi.encoders import jsonable_encoder @pytest.fixture def data(): return datetime.datetime.now() def test_json_dump(data): with pytest.raises(exception): _ = json.dumps(data) def test_encoder(data): out = jsonable_encoder(data) assert out json_out = json.dumps(out) assert json_out
3.4.4 模型类型和响应模型
不同的类可能有许多相同的字段,只是一个专门用于用户输入,一个用于输出,还有一个用于内部使用。产生这些变体的原因可能包括以下几点:
- 从输出中移除一些敏感信息,比如去识别个人医疗数据,如果你遇到《健康保险可携性和责任法案》(hipaa)的要求。
- 为用户输入添加字段(如创建日期和时间)。
下列展示了一个假定案例中的三个相关类:
- tagin 是定义用户需要提供的内容(在本例中,只是一个名为 tag 的字符串)的类。
- tag 是由 tagin 生成的,并增加了两个字段:created(创建时间)和 secret(内部字符串,可能存储在数据库中,但永远不会向外界公开)。
- tagout 是定义可以返回给用户(通过查询或搜索终端)的内容的类。它包含来自原始 tagin 对象及其派生 tag 对象的标签字段,以及为 tag 生成的创建字段,但不是秘密字段。
from datetime import datetime from pydantic import baseclass class tagin(baseclass): tag: str class tag(baseclass): tag: str created: datetime secret: str class tagout(baseclass): tag: str created: datetime
您可以通过不同方式从fastapi路径函数返回默认json以外的数据类型。其中一种方法是在路径装饰器中使用 response_model 参数,让 fastapi 返回其他类型的数据。fastapi 将放弃任何在返回对象中存在但不在 response_model 指定的对象中的字段。
假设你编写了一个名为 service/tag.py 的新服务模块,其中包含 create() 和 get() 函数,为该网络模块提供了调用的内容。这些低层堆栈细节在这里并不重要。重要的是底部的 get_one() 路径函数,以及路径装饰器中的 response_model=tagout。这会自动将内部 tag 对象转换为经过消毒的 tagout 对象。
import datetime from fastapi import fastapi from model.tag import tagin, tag, tagout import service.tag as service app = fastapi() @app.post('/') def create(tag_in: tagin) -> tagin: tag: tag = tag(tag=tag_in.tag, created=datetime.utcnow(), secret="shhhh") service.create(tag) return tag_in @app.get('/{tag_str}', response_model=tagout) def get_one(tag_str: str) -> tagout: tag: tag = service.get(tag_str) return tag
尽管我们返回的是 tag,但response_model会将其转换为 tagout。
3.5 自动化文档
上面3.3.3 post的自动文档http://localhost:8000/docs。
fastapi会根据您的代码生成一个openapi规范,并包含这个页面来显示和测试您的所有端点。这只是其秘诀之一。
单击绿色框右侧的向下箭头,打开它进行测试。
点击右侧的 "试用 "按钮。现在你会看到一个区域,可以在正文部分输入数值(。
点击 "字符串"。将其更改为 "cousin eddie"(保留双引号)。然后单击底部的蓝色 "执行 "按钮。
3.6复杂数据
这些示例只展示了如何向端点传递单个字符串。许多端点,尤其是 get 或 delete 端点,可能根本不需要参数,或者只需要几个简单的参数,如字符串和数字。但在创建(post)或修改(put 或 patch)资源时,我们通常需要更复杂的数据结构。第 5 章展示了 fastapi 如何使用 pydantic 和数据模型来简洁地实现这些功能。
3.7小结
在本章中,我们使用 fastapi 创建了一个只有一个端点的网站。多个网络客户端对其进行了测试:网络浏览器、httpie 文本程序、requests python 包和 httpx python 包。从简单的 get 调用开始,请求参数通过 url 路径、查询参数和 http 标头进入服务器。然后,http 主体被用来向 post 端点发送数据。随后,本章展示了如何返回各种 http 响应类型。最后,自动生成的表单页面为第四个测试客户端提供了文档和实时表单。
发表评论