FastAPI框架入门

FastAPI框架入门

  • 介绍
  • 安装
  • 入门
    • swagger 可交互api文档
    • redoc
  • 传参
    • get传参
    • json和form表单传参
    • 使用枚举限定参数
    • URL传递路径参数
    • 上传文件
  • 开始一个项目
    • 项目布局
    • router(类似FLASK蓝图)
      • main.py文件
      • appapi目录下__init__.py
      • config.py
      • 写一个自定义用户token验证

介绍

  • 快速:非常高的性能、异步框架,看齐的NodeJS和Go(感谢Starlette和Pydantic)。现有最快的Python框架之一。
  • 快速编码:将功能开发速度提高约200%至300%。
  • 自动:不需要开发人员去校对数据类型
  • 健壮:获取可用于生产的代码。具有自动交互式文档。
  • 基于标准:基于(并完全兼容)API的开放标准:OpenAPI(以前称为Swagger)和JSON Schema。

安装

环境: Python3.7.7,Windows10

pip install fastapi uvicorn  # fastapi以及启动用
pip install aiofiles jinja2  # 配置使用静态文件和模板文件需要
pip install python-multipart  # form表单数据需要
  • 如果pip install fastapi[all] 会安装所有模块,不建议使用

入门

from fastapi import FastAPI

app = FastAPI()


@app.get("/")  # 监听GET请求
async def read_root():
    return {"Hello": "World"}  # 返回json

然后运行即可,默认端口是8000,reload即Debug模式开

端口运行

uvicorn main:app --reload

也可以在main.py里加上这段代码,然后python main.py运行

import uvicorn

if __name__ == '__main__':
	uvicorn.run('main:app', reload=True,
                host='0.0.0.0', port=8000)

swagger 可交互api文档

  • 打开127.0.0.1:8000/docs 即可,可交互api文档

redoc

  • 127.0.0.1:8000/redoc, 接口文档

传参

get传参

  • get请求时,参数通常在url拼接参数和query方式
  • 举例:http://127.0.0.1:8000/user/10?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
    }

json和form表单传参

  • form-data方式时需要pip安装python-multipart
  • 可以使用我们可以采用 application/json 的方式传参, 并且定义一个参数类来管控参数
from fastapi import FastAPI
from fastapi import Form
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):  # 定义一个类用作参数
    name: str
    price: float
    is_offer: bool = None  # 该字段可为空

@api.post("/{item_id}")
async def get_item(item_id: int = 1, item: Item, form_param: str = Form(...)):  # item需要与Item对象定义保持一致
	# Form(...)代表必填
    return {
        "item_name": item.name,
        "item_id": item_id,
        'form_param': form_param  # 这是form参数
    }

接口文档界面可以查看参数并进行测试
FastAPI框架入门_第1张图片

使用枚举限定参数

from enum import Enum

class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"

@app.get("/model/{model_name}")
async def get_model(model_name: ModelName):  # 限定参数必须是ModelName枚举中的
	pass
	return model_name

URL传递路径参数

  • 有时候需要传递路径参数给后端,例如 http://127.0.0.1:8000/files//home/johndoe/myfile.txt
  • 这样file_path收到的内容为‘/home/johndoe/myfile.txt’
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {
        "file_path": file_path
    }

上传文件

具体可以看:请求文件
这里简单使用下

from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/files/")
async def create_file(
    file: bytes = File(...)
):
    return {"file_size": len(file)}

开始一个项目

项目布局

  • 一个简单的项目布局,adminapi、appapi、home分别是三个router用来管理路由
  • common和util放通用和当前项目工具类文件
  • model是数据库模型,static、templates静态文件

FastAPI框架入门_第2张图片

router(类似FLASK蓝图)

main.py文件

# coding=utf-8
# !/usr/bin/python -u
import codecs
import time
import sys
from fastapi import FastAPI, Depends, Header, HTTPException, Request
import uvicorn
from fastapi.middleware.cors import CORSMiddleware
from starlette.templating import Jinja2Templates
from starlette.staticfiles import StaticFiles

from common.Config import app_config
from appapi import api

sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())

app = FastAPI(title='workapp',
              description='图片转文字工具',
              version='1.0.0')


async def get_token_header(x_token: str = Header(...)):   # 这里可以做鉴权使用
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")



@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
	# 中间件
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

# 挂载静态文件,指定目录
app.mount("/static", StaticFiles(directory="static"),
          name="static")
# 模板目录
templates = Jinja2Templates(directory="templates")
# app.include_router(home, prefix='/home', tags=['home'],
#                    responses={404: {'description': 'NOT FOUND'}})
# 配置router
app.include_router(api, prefix='/api',
                   responses={404: {'description': 'NOT FOUND'}})
# cors
app.add_middleware(CORSMiddleware,
                   allow_origins=["*"],
                   allow_credentials=True,
                   allow_methods=["*"],
                   allow_headers=["*"], )


if __name__ == '__main__':
	# app_config是创建在Config文件里
    uvicorn.run('main:app', reload=app_config.DEBUG,
                host='0.0.0.0', port=app_config.PORT)

appapi目录下__init__.py

from fastapi import APIRouter

api = APIRouter()

from . import Index, User, Convert, UserMembership, UserConversion

config.py


class Config(object):
	# 通用配置
    ...


class DevConfig(Config):
    ENV_NAME = "DEV"
    # 开发环境配置
    DEBUG = True
    PORT = 8000

    # 关系型数据库的配置
    MYSQL_HOST = "127.0.0.1"
    MYSQL_USER = "..."
    MYSQL_PASSWORD = "..."
    MYSQL_PORT = 3369
    # 非关系型数据库的配置
    REDIS_HOST = "127.0.0.1"
    REDIS_PORT = 6379
    REDIS_PASSWORD = "..."


class ReleaseConfig(Config):
    ENV_NAME = "Release"
    # 发布环境
    ...


# TODO 更改配置需要修改这里 LocalConfig ReleaseConfig
app_config = DevConfig

写一个自定义用户token验证

appapi目录下User.py

  • 这里使用的数据库orm是peewee,已经配置好了
  • 导入Depends,这个是fastapi提供的异步验证方法,自动帮你验证是否登录成功
  • router装饰器中参数summary是简介直接显示在目录上,description是详细介绍,tags是给接口分组
from . import api
import time
import random
from fastapi import Query, Form, Depends
from starlette.requests import Request
from pydantic import BaseModel

from model.WAModel import WaUser, WaUserMembership
from model.ParamModel import User
from common import Func, Utils, Redis


async def verifyToken(user_token: str=Form(...)):
    """ 验证token并返回 """
    redis = Redis.getRedis()
    cache_key = "user_auth_token_" + user_token
    user_dict = redis.hgetall(cache_key)
    new_data = {key.decode('utf-8'): value.decode('utf-8') for (key, value) in user_dict.items()}
    if new_data == {}:
        raise HTTPException(status_code=401, detail='verify token failed',
                            headers={'X-Error': "There goes my error"})
    else:
        return new_data

# summary是简介直接显示在目录上,description是详细介绍
@api.post('/user/register', tags=['用户'], description='用户注册或重设密码', summary='注册或修改密码')
async def userRegister(u_phone: str = Form(...), u_password: str = Form(...),
                       verify_code: str = Form(...)):
    user = WaUser.get_or_none(u_phone=u_phone)
	# redis对象
    redis = Redis.getRedis()
    # 手机验证码
    cache_key = '%s_verify_code' % u_phone
    redis_verify_code = redis.get(cache_key)
    if not redis_verify_code:
        return Func.jsonResult({'verify_code': verify_code}, '验证码已过期,请重新请求', 200000500)
    if redis.get(cache_key).decode('utf-8') != verify_code:
        return Func.jsonResult({'verify_code': verify_code}, '验证码不正确', 200000500)
    salt = Func.md5(Func.randomStr(20))
    # MD5加盐加密
    enctyped_password = Func.md5(u_password + salt)
    if user:
        # 修改密码
        user.u_password = enctyped_password
        user.u_password_salt = salt
        user.save()
    else:
    	# 新用户创建
        user = WaUser.create(u_phone=u_phone, u_password=enctyped_password,
                             u_password_salt=salt)
    # 这个是自定义的返回格式
    return Func.jsonResult({'u_id': user.u_id})


@api.post('/sign_in', tags=['用户'], summary='登录获取token', description='登录获取token')
async def signIn(*, request: Request, u_phone: str = Form(...), u_password: str = Form(...)):
    try:
        wa_user = WaUser.get(WaUser.u_phone == u_phone)
        if wa_user.u_password != Func.md5(u_password + wa_user.u_password_salt):
        	# jsonResult这个函数是公司通用定义的response格式,这里写成自己的即可
            return Func.jsonResult({}, '密码错误', 200000500)
        token_expire_at = int(time.time()) + 86400 * 30
        token = "USER:" + Func.md5((wa_user.u_phone + wa_user.u_password + str(token_expire_at)))
        dt = {
            "user_id": wa_user.u_id,
            "user_nickname": wa_user.u_phone,
            "user_token": token,
            "user_token_expired_at": token_expire_at,
        }
		# 修改
        wa_user.u_token = token
        wa_user.u_token_expire_time = token_expire_at
        wa_user.u_login_time = int(time.time())
        client_ip = request.client.host
        wa_user.u_login_ip = Utils.ip2long(client_ip)
        wa_user.save()
		# token存入redis
        rds = Redis.getRedis()
        cache_key = "user_auth_token_" + token
        rds.hmset(cache_key, dt)
        rds.expire(cache_key, 30 * 86400)
        return Func.jsonResult({"user": dt})
    except Exception as e:
        print(e)
        return Func.jsonResult({}, '用户不存在', 200000500)
    

@api.post('/user/info', tags=['用户'], description='用户信息', summary='用户信息')
async def userInfo(*, sign_in_user: dict = Depends(verifyToken)):
    """ 用token用户信息 """
    user_id = sign_in_user.get('user_id')
    _where = (WaUser.u_id == user_id)
    user_info = WaUser.select(WaUser.u_id,
                              WaUser.u_avatar, WaUser.u_phone,
                              WaUser.u_reg_time,
                              WaUserMembership.um_grade,
                              WaUserMembership.um_total_num,
                              WaUserMembership.um_rest_num,
                              WaUserMembership.um_expire_time).join(WaUserMembership,
                                                                    on=(WaUser.u_id == WaUserMembership.um_u_id)).where(_where).dicts()
    user_info = user_info[0]

    return Func.jsonResult(user_info)

你可能感兴趣的:(FastAPI)