50

FastApi 简单入门,附生产级脚手架代码

 3 years ago
source link: https://pylixm.top/posts/2021-01-05-start.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

公司内部分享的话术稿

大家好,今天我分享的主题是Python技术栈中一个新崛起的框架FastApi。我们可以用它来快速的构建具有「异步」特性的RestAPI和web服务。

这个topic受众比较狭窄,但是还是希望今天的内容可以给到大家启发。今天分享的内容主要分下面几个模块:

  • FastApi 介绍,简单了解FastApi的背景及优势
  • FastApi 的一些预备知识
  • FastApi 的简单使用

学习好一个框架,一定要了解它的历史和它诞生的背景。FastApi,是在什么样的背景下诞生的呢?下面我们看下今天分享的第一部分「FastApi介绍」。

FastApi 介绍

FastApi 的作者是一个德国人,叫 Sebastián Ramírez(这是一个西班牙语),他的Github ID 叫 tiangolo。 这是他的个人博客:https://tiangolo.com/,现在是 Datum Consultants(一家做精品数据科学和人工智能解决方案咨询的公司) 公司的CTO。

除了工作上的title之外,在开源领域,他是 Encode 组织的成员,Encode 组织你可能不了解,说下组织下的开源项目你一定听过。

  • Django-rest-framework:Django 生态中最流行的rest api 框架。
  • httpx:支持异步请求的 http client,完全兼容 requests 库 。
  • uvicorn:最流行的 ASGI 协议的web server。

tiangolo 使用过各种不同的框架和插件,想着用把它们的优点以及一些新版本语言的优秀特性组合起来,但是所有框架都不能够满足他的需求。

Encode组织下边有个很火的异步web框架叫 Starlette ,tiangolo 在它的基础上添加了基于Pydantic的类型提示,以及依赖注入系统等功能,最后构建了FastApi。

FastApi 文档中有篇文档,详细的描述了作者从各个框架中吸收的优点,以及和各框架的对比。有兴趣的可以阅读下:

优势和特点

说了这么多,FastApi 到期具有哪些吸引人的优点呢。我们用数据来说话,截止到数据统计日期(2021年1月6日),各框架的github数据:

  • Flask 53.4K 第一个版本发布在2010年4月16日,最近一次提交昨天(20210105),issues 19 PR 5
  • Django 54.7K 2012年源码迁移到github, 最近一次提交昨天(20210105),issues 没开放 PR 179
  • Tornado 19.7K 最早的一次提交 7年前,最近一次提交是2个月前,issues 185 PR 32
  • FastApi 25.4K 2018年11月24日第一次提交,最近一次提交4天前,issues 390 PR 207
  • Sanic 14.4K 2016年11月16发布第一个版本,最近一次提交8天前,issues 66 PR 10

从上边的数据我们可以看出,FastAapi在短短的两年内就获得了25K的star数,issues 和PR 基本都是最近几个月的,数量也可以说明它的活跃程度。

再来看另一组数据:

image

这是国外一个专门做框架性能对比的网站(TechEmpower)的数据,这是对Python语言的常用web框架的测试报告,可见fastapi表现优异。

这是详细的链接,有兴趣的可以看下:

除了在上边数据方面的优异表现外,它还具有如下特点:

  • 可以自动生成API文档。
  • 框架基于类型提示构建,编辑器可以给你提供更好的支持。
  • 有一个非常强大的依赖注入系统。
  • 微框架,不限制你使用的插件
  • 基于优秀的框架Starlette和Pydantic

抛去这些,我觉着仅性能堪比Golang语言的gin框架这一条,就值得我们学习下。

下面我们来具体看下FastApi的使用。

FastApi 知识预备

在看FastApi之前,我们先来回忆下Python的异步编程和协程。

Python的异步编程

Python 语言诞生在1994年,当时对编程语言性能的要求并不像现在这么高,所以它在设计之初就更倾向于快速开发和便捷的语法。即便有性能的要求,多进程多线程也完全能够应付。但是随着现代互联网的发展,对编程语言自身的性能要求就越来越高。除了多进程和多线程模型,还逐渐演化出「协程」模型(协程,是一种用户态的更小力度的任务运行单元。基于线程,可实现多个任务的并行。)为代表的异步编程思想。

在早期,Python 实现异步编程,需要地方的库,如Twisted、Tornado 等。从Python3开始,加入了对异步编程的原生支持。

  • python3.3 引入关键字yield from,用户可实现基于生成器的简易协程;
  • python3.4 引入asyncio 库,正式引入协程概念,使用关键字 @asyncio.coroutineyield from来表示协程调用;
  • python3.5 引入关键字async/await代替@asyncio.coroutineyield from,可以让我们使用同步的方式写出异步的代码;

下面这种以async def定义函数,且用await 关键字来调用函数的代码,便是协程相关的代码。

import asyncio

async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(1.0)
    return x + y

async def print_sum(x, y):
    result = await compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

协程的运行有3种方式,参考

  • aysncio.run() 用来运行最高层次的main()函数;
  • await 调用协程函数;
  • asyncio.create_task() 并发运行作为asyncio任务的多个协程;

一些其他概念:

CGI(通用网关接口, Common Gateway Interface),简单来说就是解析浏览器等客户端发送给服务端的请求,并组装需要返回的 HTTP 请求的一种通用协议,处理这个过程的程序,我们就可以叫 CGI 脚本。互联网早起的动态网页都是基于CGI标准的。

WSGI 是一种 Python 专用的 Web 服务器网关接口,它分为两部分"服务器(或网关)“和"应用程序(或应用框架)"。「服务器」,一般独立于应用框架,为应用程序运行提供环境信息和一个回调函数(Callback Function)。当应用程序完成处理请求后,透过回调函数,将结果回传给服务器。常用的WSGI服务器有: uwsgi、gunicon。「应用程序」,是各种实现了WSGI标准的 Python web 框架了,常用的有Django、Flask等。

ASGI(Asynchronous Server Gateway Interface) 是 Django 团队提出的一种具有异步功能的 Python web 服务器网关接口协议。能够处理多种通用的协议类型,包括 HTTP,HTTP2 和 WebSocket。WSGI是基于 HTTP 协议模式的,不支持WebSocket,而ASGI的诞生则是为了解决 Python 常用的 WSGI 不支持当前 Web 开发中的一些新的协议标准(WebSocket、Http2 等)。同时,ASGI向下兼容WSGI标准,可以通过一些方法跑WSGI的应用程序。常用的「服务器」有Daphne、Uvicorn。

FastApi 快速开始

Hello world

FastApi 要求Python在3.6以上,它应用了很多Python3的新特性。下面我们安装一下:

pip install fastapi uvicorn

我们使用uvicorn来启动异步的web server ,同时把uvicorn也安装上。

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}

uvicorn 有两种启动方式:

  • 使用命令启动 uvicorn main:app --reload,生产推荐,生产中把reload去掉。
  • 使用python 代码启动,python main.py,仅用于调试。

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

整体代码看下来,和Flask的Hellow world类似,有程序实例app用来启动程序,有路由和路由处理函数。

我们来看下它自带的Swagger类型的文档:

image

是不是很方便。

接下来,让我们来看些复杂点的用法。

使用get方法来请求数据:

@app.get('/user')
async def user(user_id: int = Query(..., title="The ID of the user to get", gt=0)):
    # do somethings 
    return {'user_id': user_id}

QueryString 的传递使用了一个类Query来做参数的校验,Query类继承了相关pydandic库的类,实现了对参数的类型校验,附默认值等功能。

此处...是表示user_id参数时必须的。若此处为None,则表示user_id可选。

使用put方法来更新数据:

@app.put('/user/{user_id}')
async def user(user_id: int = Path(..., title="The ID of the user to get", gt=0)):
    # do somethings 
    return {'user_id': user_id}

这里的路径参数,可以使用类Path来做类型校验,功能和Query类似。

使用post方法来创建数据:

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int

@app.post("/users/")
async def create_user(user: User):
    # do somethings 
    return user

这里定义了一个用户类,作为函数参数的类型。在请求时,FastApi会自动的将参数注入到该类型的各对应属性字段。该类也起到了参数类型校验的作用。在函数参数类型这块,User类有点类似Golang中结构体定义的函数参数类型。

使用delete方法来删除数据:

@app.delete('/user/{user_id}')
async def user(user_id: int = Path(..., title="The ID of the user to get", gt=0)):
    # do somethings 
    return {'user_id': user_id}

同put 方法的用法。

看了这些基本的用法之后,我们对FastApi的使用有了一个初步的了解。

我们从之前的代码中可以看到路由和路由函数在一块的,这种方式在路由少的情况下是非常方便的,但是随着项目复杂度的提升,路由逐渐增多,如果路由设计不合理,便会非常的不好维护。

针对这种情况,FastApi 提供了 APIRouter类来规划设计路由,以便适应大型的项目架构。

路由分解和版本管理

APIRouter 类的基本用法如下:

# 页面路由
page_routes = APIRouter()
page_routes.include_router(views.router, prefix="")

# api 相关路由
api_routes = APIRouter()
api_routes.include_router(api_v1_views.router, prefix='/v1')
api_routes.include_router(api_v2_views.router, prefix='/v2')

可以通过它来拆分管理路由,最后再将所有路由注册到根路由即可。

app = FastAPI()
app.include_router(page_routes)
app.include_router(api_routes, prefix=config.API_PREFIX)

一个真实的项目实例

接下来,让我们看一个真实的生产环境中的项目案例:

image

我们从下边几点来详细说下这个项目案例:

1/ ORM

首先在ORM 选择方面,官方推荐了强大的SQLAlchemy,它可以说是Python web 开发中最好用的第三方ORM了。在同步框架Flask中,应该说已经成为标配。

SQLAlchemy 目前为止对异步的支持还不够完成,官方推荐使用 Encode组织下的异步驱动 databases(目前支持PostgreSQL/MySQL/SQLite),只所以叫他驱动没叫他ORM,是因为它仅提供了和数据库的异步链接的管理,并没有对象模型。在使用的时候,可以结合SQLAlchemy或直接写SQL。

使用SQLAlchemy来处理同步的数据操作,使用SQLAlchemydatabases来实现异步的数据操作,完美实现了我们的需求。

2/ 数据库迁移

SQLAlchemy 还有一个数据库迁移的问题,它自己不支持数据库表的变更迁移,只能删除重建。在Flask中可以使用插件flask_migrate来实现数据库表的变更迁移。

SQLAlchemy官方推荐使用alembic。一个迁移过程大概步骤如下:

# 1/ 初始化环境 
alembic init migrations 

# 2/ 修改配置参数

# migrations/env.py
import sys 
sys.path = ['', '..'] + sys.path[1:]

from core.config import DATABASE_URL
config.set_main_option("sqlalchemy.url", str(DATABASE_URL))

...
from models.posts import PostsBase
from models.posts2 import PostsBase2
target_metadata = Base.metadata  # 一个app model 
target_metadata = [PostsBase.metadata, PostBase2.metadata]  # 多个app model

# 3/ 生成迁移脚本
alembic revision --autogenerate -m "init"

# 4/ 应用迁移脚本到数据库
alembic upgrade head 

3/ service 拆分

从项目目录可以看到有个service目录,很显然,当业务逻辑服务度升高时,我们可以提取很多共用的逻辑作为底层的共用逻辑。这个就比较灵活了,完全有你自己控制。各种设计模式,就可以往上怼了。

4/ Docker 化

最后我们来看下docker化,Dockerfile 如下:

# 我们选择了官方 slim 镜像,体积相对小
FROM python:3.9-slim-buster

LABEL maintainer="DeanWu <[email protected]>"

# stdout 无缓冲,直接输出
ENV PYTHONUNBUFFERED 1

# 复制代码,调整工作目录和脚本权限
COPY . /app

WORKDIR /app

RUN chmod +x start.sh prestart.sh start-reload.sh

# 安装用到的工具和python 包 
RUN apt-get update && \
    apt-get install -y --no-install-recommends default-libmysqlclient-dev gcc libffi-dev make && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
    pip install --no-cache-dir -r requirements.txt && \
    rm -rf requirements.txt && \
    pip install --no-cache-dir gunicorn

ENV PYTHONPATH=/app

EXPOSE 80

# 启动命令 可添加也可不添加  
#CMD ["sh", "start.sh"]

编译和启动,

docker build -t fastapi-mysql:v1.0 .

docker run -p 80:80 -d -e DB_CONNECTION="mysql://root:Root1024@xxxx/fastapi" fastapi-mysql:v1.0 ./start.sh 

项目完整的代码,可以从我的github 获取 :

到这里我们从背景到生产项目案例,已经介绍完FastApi 这个框架了,你有没有觉着这个框架很酷呢?我今天只是简单的介绍了框架的部分应用场景,还有很多好玩的功能,大家可以去参考它的官方文档。也可以关注我公众号,一起讨论学习。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK