为什么要有API:
如果我们想要获取一篇博客,输入http://localhost:9000/blog/123,就可以看到id为123的博客页面,但这个结果是HTML页面,
它同时混合包含了两个部分的数据:博文的内容数据、该页面的展示数据(html、js、css)。
如下图所示:
对于用户来说,看上去是没有问题的。但是对于开发人员来说,是有麻烦的。很难从HTML中解析出博文的内容数据
WEB API示例
http://localhost:8000/api/blogs/123
如下图所示:
该URL返回的不是HTML,而是博文数据,这个URL就可以看成是一个Web API。
简单的说就是: api 是可以直接提取干货的!
REST就是一种设计API的模式。最常用的返回数据格式是JSON。由于JSON能直接被JavaScript读取,所以,以JSON格式编写的REST风格的API具有简单、易读、易用的特点。
编写API有什么好处呢?由于API就是把Web App的功能全部封装了,所以,通过API操作数据,可以极大地把前端和后端的代码隔离,使得后端代码易于测试,前端代码编写更简单。
一个API也是一个URL的处理函数,我们希望能直接通过一个@api来把函数变成JSON格式的REST API。
python怎么写api? --FastAPI
FastApi是PythonWeb框架的新晋干将!
它内部使用了 Python 的 Async 异步,因此速度很快, 也要求必须是 Py3.6+
Async 不同于之前自己实现的 gevent ,Async是官方写的, 听说Py4将会大量使用来提高效率
FastApi官网展示了FastApi的特点
官方文档地址:https://fastapi.tiangolo.com/
- 快速:非常高的性能,看齐的NodeJS和Go(感谢Starlette和Pydantic)。现有最快的Python框架之一。
- 快速编码:将功能开发速度提高约200%至300%。
- 更少的错误:减少约40%的人为错误(开发人员)。
- 直观:强大的编辑器支持。完成无处不在。调试时间更少。
- 简易:旨在易于使用和学习。减少阅读文档的时间。
- 短:最小化代码重复。每个参数声明中的多个功能。更少的错误。
- 健壮:获取可用于生产的代码。具有自动交互式文档。
- 基于标准:基于(并完全兼容)API的开放标准:OpenAPI(以前称为Swagger)和JSON Schema。
安装
先安装最基础的组件
安装fastapi
pip install fastapi
安装应用服务器
pip install uvicorn
官方推荐了可以与 FastApi 搭配的几个模块, 基本上涵盖了几个大的使用场景
由Pydantic使用:
ujson-用于更快的JSON “解析”。
email_validator -用于电子邮件验证。
由Starlette使用:
requests-如果要使用,则为必填TestClient。
aiofiles-如果要使用FileResponse或,则为必填StaticFiles。
jinja2 -如果要使用默认模板配置,则为必需。
python-multipart-如果要使用来支持表单“解析”,则为必填request.form()。
itsdangerous-需要SessionMiddleware支持。
pyyaml-Starlette SchemaGenerator支持所必需的(FastAPI 可能不需要它)。
graphene-需要GraphQLApp支持。
ujson-如果要使用,则为必需UJSONResponse。
由FastAPI / Starlette使用:
uvicorn -用于加载和服务您的应用程序的服务器。
orjson-如果要使用,则为必需ORJSONResponse。
您可以一次安装所有 这些
pip install fastapi[all]
写第一个demo
from fastapi import FastAPI
app = FastAPI()
@app.get("/") # 监听GET请求
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
或者
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
运行
uvicorn main:app --reload
看到如下提示,证明运行成功
main: 表示app所在文件名
app:文件内创建的对象 app = FastAPI()
reload:debug模式,可以自动重启
试着请求http://127.0.0.1:8000/items/5?q=somequery,会看到如下返回
里面的内容就是一个JSON格式的返回数据。
远程服务器部署 需要调整ip及端口号
ip要改为0.0.0.0,端口自定义
uvicorn的指令需要添加host参数和port参数:
uvicorn main:app --host '0.0.0.0' --port 8080 --reload
端口一定要是在云服务器安全组策略中开启的。
安装screen
要用screen来挂服务,以免终端关闭后服务终止
yum install screen
新建screen
screen -S fastapi
执行指令
uvicorn main:app --host '0.0.0.0'--port 8082 --reload
后台挂起screen
按CTRL + A + D组合键挂起。
然后关掉终端程序后依旧执行。
恢复screen
screen -ls # 查看screen列表
screen -r [screen会话号]
可以使用 postman 等调试工具测试接口,也可以直接用浏览器打开。
自动生成接口文档
FastApi 会自己给你生成接口文档, 真正的解放你的双手
FastApi 默认提供了两种接口文档, 其实内容一样, 只是使用了两个开源的文档框架
swagger
这得益于 swagger 的帮助, swagger的更多使用这里不再赘述
http://127.0.0.1:8000/docs 使用浏览器打开即可
在你更新代码时接口文档也会同步更新
redoc
得益于 redoc 的帮助, redoc的更多使用这里不再赘述
默认的文档位置在 http://127.0.0.1:8000/redoc 使用浏览器打开即可
在你更新代码时接口文档也会同步更新
更新代码
如果我们对代码进行了修改,保存之后服务器会自动重新启动服务
get传参
在 get 请求时,传递参数通常使用 url 拼接参数和 query 的方式传参
url传参
在 get 请求时,传递参数通常使用 url 拼接参数和 query 的方式传参
url传参
比如 http://127.0.0.1:8000/phone/123456
在 main.py 中增加函数
@app.get("/phone/{phone}")
async def get_phone(phone: int):
return {"phone": phone}
这里强调一下, phone: int 是声明参数 phone 是 int 类型,这是 python 3.5+ 有的语法, 目的是增加对开发人员的友好度与IDE的提示
因我们在启动时设置了 --reload 所以我们在保存时项目会自己更新, 同时 docs 也会更新
我们访问url查看结果
{
"phone": 123456
}
query传参
如: http://127.0.0.1:8000/user?user_id=12
在函数中是
@app.get("/user")
async def get_user(user_id: int):
return {"user_id": user_id}
复合传参
如果一个请求中有两种方式
http://127.0.0.1:8000/user/12?mod=login
对应的是
@app.get("/user/{user_id}")
async def user_mod(user_id: int, mod: str = None): # str = None 代表mod参数可以为空
return {
"user_id": user_id,
"mod": mod
}
返回
{
"user_id": 12,
"mod": "login"
}
body传参
针对Body传参的情况, 其实也是以函数传参的形式, 但是考虑到传统的 form-data 传参方式字段很多, 我们可以采用 application/json 的方式, 并且定义一个参数类来管控参数
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel): # 定义一个类用作参数
name: str
price: float
is_offer: bool = None # 该字段可为空
@app.put("/{item_id}")
async def update_item(item_id: int, item: Item): # item需要与Item对象定义保持一致
return {
"item_name": item.name,
"item_id": item_id
}
这里接受 URL 参数与 Body 的 json 格式的参数
举个例子, 我们使用 postman 调试, Body里设置为 json 传递
{
"name": "t1",
"price": 12.8
}
这里注意我们没有传递 is_offer 因为可以为空
url为 http://127.0.0.1:8000/12 , 请求方式为 put, 结果为
{
"item_name": "t1",
"item_id": 12
}
与类型声明结合
如果你试过在以上几种接口中传递错误的参数或者少传递参数, 你会发现 FastApi 会自己返回错误给客户端,
比如在参数中定义了 id 为 int 的时候传递了 str
没错, FastApi 将其与 类型声明 结合在了一起, 这便是其另一个强大之处。
使用枚举来限定参数
可以使用枚举的方式来限定参数为某几个值之内才通过
from fastapi import FastAPI
from enum import Enum
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/model/{model_name}")
async def get_model(model_name: ModelName): # 限定参数必须是ModelName枚举中的
if model_name == ModelName.alexnet: # 枚举判断方法1
return {
"model_name": model_name
}
if model_name.value == "lenet": # 枚举判断方法2
return {
"model_name": model_name
}
return {
"model_name": model_name
}
Fast错误返回
以上面为例, 如果你传递了不在枚举中的参数, Fast 会返回错误给客户端, 例如
{
"detail": [
{
"loc": [
"path", # 出错的地方
"model_name" # 具体的参数
],
"msg": "value is not a valid enumeration member; permitted: 'alexnet', 'resnet', 'lenet'", # 出错信息
"type": "type_error.enum", # 出错type
"ctx": {
"enum_values": [
"alexnet",
"resnet",
"lenet"
]
}
}
]
}
URL传递路径参数
有时候需要传递路径参数给后端, 如果使用灵活的方式
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {
"file_path": file_path
}
设置参数为 path 即可,注意 : 后不能加空格否则会404
因为我们匹配的 URL files本身后面加了 /, 如果我们想在传递时读取到的参数本身前面就带有 /, 写 // 即可, 例如: http://127.0.0.1:8000/files//home/johndoe/myfile.txt 读取到的就是 /home/johndoe/myfile.txt
参数默认值
对于需要分页的接口来说,通常需要传递 page 和 page_size , 而通常, 为了提高代码健壮性, 我们需要给 page 和 page_size 指定一个默认值, 在(一)中我们介绍了如何设置某个字段为可传, 现在我们在FastApi 中指定参数默认值的方法如下
@app.get("/log")
async def get_logs(page: int = 1, page_size: int = 20):
return {
"page": page,
"page_size": page_size
}
测试不带参数 http://127.0.0.1:8000/log
返回默认值
{
"page": 1,
"page_size": 20
}
测试带参数 http://127.0.0.1:8000/log?page=2
返回
{
"page": 2,
"page_size": 20
}
post传参
FastAPI 定义请求体,需要 Pydantic 模型。你需要从pydantic中导入BaseModel。
from pydantic import BaseModel
创建数据类型然后,声明你的数据模型为一个类,且该类继承 BaseModel.
# 创建数据模型
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
服务端完整demo:
# -*- coding: utf-8 -*-
# author:laidefa
# 载入包
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
# 创建数据模型
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
app = FastAPI()
@app.get("/")
async def root():
return 'Hello World!'
@app.post("/sys/sinoma/v1/optimization/predict/fcao")
async def fcao_predict(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
if __name__ == '__main__':
uvicorn.run(app)
客户端demo:
# -*- coding: utf-8 -*-
# author:laidefa
# 载入包
import requests
import json
import time
params={
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5
}
url='http://127.0.0.1:8000/sys/sinoma/v1/optimization/predict/fcao'
time1=time.time()
html = requests.post(url, json.dumps(params))
print('发送post数据请求成功!')
print('返回post结果如下:')
print(html.text)
time2=time.time()
print('总共耗时:' + str(time2 - time1) + 's')
Form表单传参
我们之前讲了从 body 的 JSON 里获取参数, 这里说一下从传统的 Form 表单里获取数据(application/x-www-form-urlencoded/multipart/form-data)
想要从 Form 中获取数据,需要导入 Form 模块, 并且安装 python-multipart 包
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login")
async def login(*, name: str = Form(...), pwd: str = Form(...)): # 函数的接受参数第一个是 * 代表此函数只接受关键字传参
# Form(...) 必写
return {
"name": name,
"pwd": pwd
}
运行结果如下:
发送post数据请求成功!
返回post结果如下:
{"name":"Foo","description":"An optional description","price":45.2,"tax":3.5,"price_with_tax":48.7}
总共耗时:0.004986763000488281s
本文参考文献:
https://www.cnblogs.com/sanduzxcvbnm/p/12195385.html
https://www.cnblogs.com/chnmig/p/12603895.html
https://blog.csdn.net/u013421629/article/details/104892975
感谢上述博主提供的内容!