FastAPI for Flask Users

https://amitness.com/2020/06/fastapi-vs-flask/

虽然 Flask 已成为机器学习项目中 API 开发的实际选择,但有一个名为 FastAPI 的新框架已经获得了很多社区的关注.

Flask 用户的 FastAPI - 图1

我最近决定通过移植一个生产 Flask 项目来尝试一下 FastAPI。 从 Flask 获取 FastAPI 非常容易,我能够在几个小时内启动并运行.

自动数据验证、文档生成和 pydantic 模式和 python 类型等内置最佳实践的额外好处使其成为未来项目的有力选择.

在这篇文章中,我将通过对比 Flask 和 FastAPI 中各种常见用例的实现来介绍 FastAPI.

版本信息:

在撰写本文时,Flask 版本为 1.1.2,FastAPI 版本为 0.58.1

安装永久链接

PyPI 上提供了 Flask 和 FastAPI。 对于 conda,您需要使用 conda-forge 安装 FastAPI 的通道,而它在 Flask 的默认通道中可用.

烧瓶:

  1. pip install flask
  2. conda install flask

快速API:

  1. pip install fastapi uvicorn
  2. conda install fastapi uvicorn -c conda-forge

运行“Hello World”永久链接

烧瓶:

  1. # app.py
  2. from flask import Flask
  3. app = Flask(__name__)
  4. @app.route('/')
  5. def home():
  6. return {'hello': 'world'}
  7. if __name__ == '__main__':
  8. app.run()

现在您可以使用以下命令运行开发服务器。 它默认在 5000 端口上运行.

  1. python app.py

快速API

  1. # app.py
  2. import uvicorn
  3. from fastapi import FastAPI
  4. app = FastAPI()
  5. @app.get('/')
  6. def home():
  7. return {'hello': 'world'}
  8. if __name__ == '__main__':
  9. uvicorn.run(app)

FastAPI 将服务推迟到一个生产就绪的服务器,称为 uvicorn. 我们可以在开发模式下使用默认端口运行它 8000.

  1. python app.py

生产服务器永久链接

烧瓶:

  1. # app.py
  2. from flask import Flask
  3. app = Flask(__name__)
  4. @app.route('/')
  5. def home():
  6. return {'hello': 'world'}
  7. if __name__ == '__main__':
  8. app.run()

对于生产服务器, gunicorn 是Flask中常见的选择.

  1. gunicorn app:app

快速API

  1. # app.py
  2. import uvicorn
  3. from fastapi import FastAPI
  4. app = FastAPI()
  5. @app.get('/')
  6. def home():
  7. return {'hello': 'world'}
  8. if __name__ == '__main__':
  9. uvicorn.run(app)

FastAPI 将服务推迟到一个生产就绪的服务器,称为 优维康. 我们可以启动服务器:

  1. uvicorn app:app

您也可以通过运行以热重载模式启动它

  1. uvicorn app:app --reload

此外,您还可以更改端口.

  1. uvicorn app:app --port 5000

工人数量也可以控制.

  1. uvicorn app:app --workers 2

您可以使用 gunicorn 也可以使用以下命令管理 uvicorn。 所有常规 gunicorn 标志,例如工人数量(-w) 工作.

  1. gunicorn -k uvicorn.workers.UvicornWorker app:app

HTTP 方法永久链接

烧瓶:

  1. @app.route('/', methods=['POST'])
  2. def example():
  3. ...

快速API:

  1. @app.post('/')
  2. def example():
  3. ...

每个 HTTP 方法都有单独的装饰器方法.

  1. @app.get('/')
  2. @app.put('/')
  3. @app.patch('/')
  4. @app.delete('/')

网址变量永久链接

我们想从 URL 中获取用户 ID,例如. /users/1 然后将用户id返回给用户.

烧瓶:

  1. @app.route('/users/<int:user_id>')
  2. def get_user_details(user_id):
  3. return {'user_id': user_id}

快速API:

在 FastAPI 中,我们利用 Python 中的类型提示来指定所有数据类型。 例如,这里我们指定 user_id 应该是一个整数。 URL 路径中的变量也被指定为类似于 f-strings.

  1. @app.get('/users/{user_id}')
  2. def get_user_details(user_id: int):
  3. return {'user_id': user_id}

查询字符串永久链接

我们希望允许用户通过使用查询字符串来指定搜索词 ?q=abc 在网址中.

烧瓶:

  1. from flask import request
  2. @app.route('/search')
  3. def search():
  4. query = request.args.get('q')
  5. return {'query': query}

快速API:

  1. @app.get('/search')
  2. def search(q: str):
  3. return {'query': q}

JSON POST 请求永久链接

让我们举一个玩具示例,我们想发送一个 JSON POST 请求,其中包含 text 键并取回小写版本.

  1. # Request
  2. {"text": "HELLO"}
  3. # Response
  4. {"text": "hello"}

烧瓶:

  1. from flask import request
  2. @app.route('/lowercase', methods=['POST'])
  3. def lower_case():
  4. text = request.json.get('text')
  5. return {'text': text.lower()}

快速API:
如果您只是从 Flask 复制功能,您可以在 FastAPI 中按照以下方式进行操作.

  1. from typing import Dict
  2. @app.post('/lowercase')
  3. def lower_case(json_data: Dict):
  4. text = json_data.get('text')
  5. return {'text': text.lower()}

但是,这正是 FastAPI 引入了一个新概念的地方,即创建映射到正在接收的 JSON 数据的 Pydantic 模式。 我们可以使用 pydantic 重构上面的例子:

  1. from pydantic import BaseModel
  2. class Sentence(BaseModel):
  3. text: str
  4. @app.post('/lowercase')
  5. def lower_case(sentence: Sentence):
  6. return {'text': sentence.text.lower()}

正如所见,JSON 数据不是获取字典,而是转换为模式的对象 Sentence. 因此,我们可以使用数据属性访问数据,例如 sentence.text. 这也提供了数据类型的自动验证。 如果用户尝试发送字符串以外的任何数据,他们将收到自动生成的验证错误.

示例无效请求

  1. {"text": null}

自动响应

  1. {
  2. "detail": [
  3. {
  4. "loc": [
  5. "body",
  6. "text"
  7. ],
  8. "msg": "none is not an allowed value",
  9. "type": "type_error.none.not_allowed"
  10. }
  11. ]
  12. }

上传文件永久链接

让我们创建一个 API 来返回上传的文件名。 上传文件时使用的密钥将是 file.

烧瓶
Flask 允许通过请求对象访问上传的文件.

  1. # app.py
  2. from flask import Flask, request
  3. app = Flask(__name__)
  4. @app.route('/upload', methods=['POST'])
  5. def upload_file():
  6. file = request.files.get('file')
  7. return {'name': file.filename}

快速API:
FastAPI 使用函数参数指定文件密钥.

  1. # app.py
  2. from fastapi import FastAPI, UploadFile, File
  3. app = FastAPI()
  4. @app.post('/upload')
  5. def upload_file(file: UploadFile = File(...)):
  6. return {'name': file.filename}

表格提交永久链接

我们想要访问如下所示定义的文本表单字段并回显该值.

  1. <input name='city' type='text'>

烧瓶
Flask 允许通过请求对象访问表单字段.

  1. # app.py
  2. from flask import Flask, request
  3. app = Flask(__name__)
  4. @app.route('/submit', methods=['POST'])
  5. def echo():
  6. city = request.form.get('city')
  7. return {'city': city}

快速API:
我们使用函数参数来定义表单字段的键和数据类型.

  1. # app.py
  2. from fastapi import FastAPI, Form
  3. app = FastAPI()
  4. @app.post('/submit')
  5. def echo(city: str = Form(...)):
  6. return {'city': city}

我们还可以使表单字段可选,如下所示

  1. from typing import Optional
  2. @app.post('/submit')
  3. def echo(city: Optional[str] = Form(None)):
  4. return {'city': city}

同样,我们可以为表单字段设置一个默认值,如下所示.

  1. @app.post('/submit')
  2. def echo(city: Optional[str] = Form('Paris')):
  3. return {'city': city}

饼干永久链接

我们想要访问一个名为 name 从请求.

烧瓶
Flask 允许通过请求对象访问 cookie.

  1. # app.py
  2. from flask import Flask, request
  3. app = Flask(__name__)
  4. @app.route('/profile')
  5. def profile():
  6. name = request.cookies.get('name')
  7. return {'name': name}

快速API:
我们使用参数来定义 cookie 的键.

  1. # app.py
  2. from fastapi import FastAPI, Cookie
  3. app = FastAPI()
  4. @app.get('/profile')
  5. def profile(name = Cookie(None)):
  6. return {'name': name}

模块化视图永久链接

我们希望将视图从单个 app.py 分解为单独的文件.

  1. - app.py
  2. - views
  3. - user.py

烧瓶:
在 Flask 中,我们使用一个称为蓝图的概念来管理它。 我们首先为用户视图创建一个蓝图,如下所示:

  1. # views/user.py
  2. from flask import Blueprint
  3. user_blueprint = Blueprint('user', __name__)
  4. @user_blueprint.route('/users')
  5. def list_users():
  6. return {'users': ['a', 'b', 'c']}

然后,这个视图注册在主 app.py 文件.

  1. # app.py
  2. from flask import Flask
  3. from views.user import user_blueprint
  4. app = Flask(__name__)
  5. app.register_blueprint(user_blueprint)

快速API:
在 FastAPI 中,蓝图的等价物称为路由器。 首先,我们创建一个用户路由器:

  1. # routers/user.py
  2. from fastapi import APIRouter
  3. router = APIRouter()
  4. @router.get('/users')
  5. def list_users():
  6. return {'users': ['a', 'b', 'c']}

然后,我们将此路由器附加到主应用程序对象:

  1. # app.py
  2. from fastapi import FastAPI
  3. from routers import user
  4. app = FastAPI()
  5. app.include_router(user.router)

数据验证永久链接

烧瓶
Flask 不提供任何开箱即用的输入数据验证功能。 通常的做法是编写自定义验证逻辑或使用库,例如 棉花糖 或者 Pydantic.

快速API:

FastAPI 将 pydantic 包装到其框架中,并通过简单地使用 pydantic 模式和 python 类型提示的组合来允许数据验证.

  1. from fastapi import FastAPI
  2. from pydantic import BaseModel
  3. app = FastAPI()
  4. class User(BaseModel):
  5. name: str
  6. age: int
  7. @app.post('/users')
  8. def save_user(user: User):
  9. return {'name': user.name,
  10. 'age': user.age}

此代码将执行自动验证以确保 name 是一个字符串并且 age 是一个整数。 如果发送任何其他数据类型,它会自动生成带有相关消息的验证错误.

以下是一些常见用例的 pydantic 模式示例.

示例 1:键值对永久链接

  1. {
  2. "name": "Isaac",
  3. "age": 60
  4. }
  1. from pydantic import BaseModel
  2. class User(BaseModel):
  3. name: str
  4. age: int

示例 2:事物的收集永久链接

  1. {
  2. "series": ["GOT", "Dark", "Mr. Robot"]
  3. }
  1. from pydantic import BaseModel
  2. from typing import List
  3. class Metadata(BaseModel):
  4. series: List[str]

示例 3:嵌套对象永久链接

  1. {
  2. "users": [
  3. {
  4. "name": "xyz",
  5. "age": 25
  6. },
  7. {
  8. "name": "abc",
  9. "age": 30
  10. }
  11. ],
  12. "group": "Group A"
  13. }
  1. from pydantic import BaseModel
  2. from typing import List
  3. class User(BaseModel):
  4. name: str
  5. age: int
  6. class UserGroup(BaseModel):
  7. users: List[User]
  8. group: str

您可以从以下位置了解有关 Python 类型提示的更多信息 这里.

自动文档永久链接

烧瓶
Flask 不提供任何用于文档生成的内置功能。 有一些扩展,例如 烧瓶招摇 或者 烧瓶宁静的 填补这一空白,但工作流程相对复杂.

快速API:
FastAPI 自动生成一个交互式 swagger 文档端点 /docs 和参考文档 /redoc.

例如,假设我们有一个下面给出的简单视图,它与用户搜索的内容相呼应.

  1. # app.py
  2. from fastapi import FastAPI
  3. app = FastAPI()
  4. @app.get('/search')
  5. def search(q: str):
  6. return {'query': q}

招摇文档永久链接

如果您运行服务器并转到端点 http://127.0.0.1:8000/docs, 你会得到一个自动生成的招摇文档.

Flask 用户的 FastAPI - 图2

您可以从浏览器本身以交互方式试用 API.

Flask 用户的 FastAPI - 图3

ReDoc 文档永久链接

除了招摇,如果你去端点 http://127.0.0.01:8000/redoc, 您将获得自动生成的参考文档。 有关于参数、请求格式、响应格式和状态码的信息.
Flask 用户的 FastAPI - 图4

跨域资源共享(CORS)永久链接

烧瓶
Flask 不提供开箱即用的 CORS 支持。 我们需要使用扩展名,例如 烧瓶-cors 配置 CORS 如下所示.

  1. # app.py
  2. from flask import Flask
  3. from flask_cors import CORS
  4. app_ = Flask(__name__)
  5. CORS(app_)

快速API:
FastAPI 提供了一个 内置中间件 处理 CORS。 我们在下面展示了一个 CORS 示例,我们允许任何来源访问我们的 API.

  1. # app.py
  2. from fastapi import FastAPI
  3. from fastapi.middleware.cors import CORSMiddleware
  4. app = FastAPI()
  5. app.add_middleware(
  6. CORSMiddleware,
  7. allow_origins=['*'],
  8. allow_credentials=True,
  9. allow_methods=["*"],
  10. allow_headers=["*"],
  11. )

结论永久链接

因此,FastAPI 是 Flask 的绝佳替代品,用于构建具有最佳实践的健壮 API。您可以参考 文件 了解更多.

参考永久链接