FastApi_demo_类视图

 

 

安装模块

pip install fastapi

1.创建app

from fastapi import FastAPI

app = FastAPI()

@app.get()
async def index():
    return ok

2.静态文件路径

from fastapi.staticfiles import StaticFiles

static = StaticFiles(directory='./statics') # 静态文件目录名称可以直接写‘statics’

app.mount('/static',static,name='')    # 第一个参数是访问静态文件的uri,name的作用是方便反向解析

 

3.模板(前后端不分离)

from fastapi.templating import Jinja2Templates

template=Jinja2Templates(directory='./templates')    # 存放模板的目录

# JinJa2模板引擎  渲染时调用
template.TemplateResponse('模板文件.html',context={'request':request,***})
# 当后端渲染时,必须返回前端request对象,以及其他参数,可以是python对象
# 在html页面模板中,可以使用 {{ python对象 }} 渲染,例如  context={'request':request,'name':'test'}

# 想要渲染出name的值,只需要  {{ name }},即可在浏览器中显示name对应的值’test‘
# JinJa2模板引擎还提供了for循环、if判断等方法

# example   context={'request':request,'a':[1,2,3,4]}
{% for i in a %}
	<li>{{ i }}</li>
{% endfor %}


{% for i in a %}
	{% if i<2 %}
	<li>{{ i }}</li>
    {% else %}
    <li>{{ i+1 }}</li>
    {% endif %}
{% endfor %}

 

4.参数

4.1路径参数

from fastapi import FastAPI,Path

app = FastAPI()

@app.get('/{id}')
async def func1(id:int):    # id就是路径参数,设置了类型校验
    pass

@app.get('/test/{id}')
async def func2(id:int=Path(...,ge=1)):    # Path是路径参数的类,可以做验证,即对路径参数的校验
    pass

Path(  # noqa: N802
    default: Any,                          # None为非必传参数,...为必传,指定值为默认
    *,									   # 如果传了*,则说明后面的参数全部为关键字参数
    alias: str = None,					   # 别名,至前端传参时的参数名
    title: str = None,					   # 说明
    description: str = None,
    gt: float = None,					   # 只对int/float格式的参数,gt:大于
    ge: float = None,					   # ge :大于等于
    lt: float = None,                      # lt :小于
    le: float = None,					   # le:小于等于
    min_length: int = None,				   # 只能用于str格式的参数,最小长度
    max_length: int = None,				   # 只能用于str格式的参数,最大长度
    regex: str = None,					   # 只能用于str格式的参数,正则
    deprecated: bool = None,
    **extra: Any,
)

 

4.2查询字符串参数

from fastapi import FastAPI,Query

app = FastAPI()

@app.get('/')
async def func1(a:str,b:int):    # 直接定义要接收的参数,只能校验类型
    pass

@app.get('/1')
async def func2(a:str=Query(),b:int=Query()):    # 可以校验类型以及其他的参数,与Path类似
    pass

 

4.3表单参数

from fastapi import Form,UploadFile
from typing import List
app = FastAPI()


@app.post('/index/dc')
async def index_post(request: Request, file1: bytes = File(...), name: str = Form(None), age: int = Form(None)):
    print(name)
    return name, age

# 或者使用UploadFile进行校验
@app.post('/index/dc')
async def index_post(request: Request, file1: UploadFile = File(...), name: str = Form(None), age: int = Form(None)):
    print(name)
    return name, age

 

想要使用表单,必须先安装python-multipart 模块

<form action="./index/dc" method="post" enctype="multipart/form-data">
    <input type="file" name="file1">
    <input type="text" name="name">
    <input type="text" name="age">
    <input type="submit" >
</form>

<!--
acction指明uri,method指明方法,enctype 必须指明"multipart/form-data"
接收的参数名=表单中的name值
Form表单会返回新的页面
表单中,每个参数要接一个Form
如果是类型是file,接File
-->

 

  • ajax 传输文件

js前端

$('#submit_1').click(function () {
	var files = $('#f001').prop('files');
	var data = new FormData();
	data.append('file0', files[0]);
	data.append('age', $("#age0").val());
	$.ajax({
		url:'./index/aj',
		type:'post',
		data:data,
		contentType: false,
		processData: false,
		success:function(data) {
			alert(data)
}
})
})

<!--
ajax 发送文件必须是在FormData对象中
contentType: false,
processData: false,
必须都是false才能正确传输
-->

 

python后端

@app.post('/index/aj')
async def index_post(request: Request, file0:bytes=File(...), age: int = Form(None)):
    print(file0)
    return age

# 接收参数还是Form和File

 

多文件

多文件只能使用表单方式传输

formdata不能传输列表,如果要实现多文件上传,必须是指定数量的,并与后端一一对应。

<form action="./index/dc" method="post" enctype="multipart/form-data">
    <input type="file" name="file1" multiple>   <!--必须添加multiple,否则无法选择多文件-->
    <input type="text" name="name">
    <input type="text" name="age">
    <input type="submit" >
</form>

 

@app.post('/index/dc')
async def index_post(request: Request, 
                     file0:List[UploadFile]=File(...),    # List[UploadFile] 或者
                     age: int = Form(None)):
    print(file0)
    return age

 

4.4请求体参数

  • pydantic 类
from pydantic import BaseModel
from typing import Optional

class AAA(BaseModel):
    name: Optional[str]
    age: Optional[int] = None  # int只能用Optional 设置为非必传

 

  • Body
@app.post('/index/pp')
async def index_post(request: Request, aaa: AAA = Body(..., )):
    print(aaa.name)
    return {'name': 000}

 

 

可以不使用Body,默认是JSON格式传输进来的,如果使用了pydantic校验类,默认就是请求体

4.5依赖 Depends

api需要接收的参数是依赖的函数或者类接收的参数,但是接收到的是函数或者类返回的对象

class User(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age


@app.get('/depends')
async def depends_test(request: Request, user: User = Depends(User)):
    return user.name, user.age
# 如果依赖的是类,可以直接user: User = Depends(), dastapi会自动识别


def depends_000(name: str, age: int):
    return {'name': name, 'age': age}


@app.get('/depends/0')
async def depends_0(request: Request, user: dict = Depends(depends_000)):
    return user

# 请求是
# requests.get('http://127.0.0.1:8000/depends/0?name=aaa&age=60')
# 请求需要传的参数是依赖的函数需要的参数,但是在api接口定义时,接收参数校验需要写依赖函数返回值

4.6 请求头

请求头与查询字符串一样,定义api时,将需要校验的请求头校验即可

@app.post('/artic/add')
async def add_artic(x_token: str = Header(..., convert_underscores=False)):
    return 
# convert_underscores 是设置下划线转义的,当请求头中存在下划线,要设置为False

 

 

可以结合依赖使用

def check_token(x_token: str = Header(..., convert_underscores=False)):
    periods = jwt.decode(x_token, SECRET_KEY, ALGORITHM)
    return periods.get('user_id', None)

@app.post('/artic/add')
async def add_artic(request: Request, user_id: str = Depends(check_token),
                    checktoken: CheckToken = Body(None)):
    return

 

 

5.响应

  • python的类型
from fastapi import FastAPI,Path

app = FastAPI()

@app.get('/')
async def func1():
    
    return '',1,[]     # 这里可以直接返回python类型的数据,fastapi会直接转成json

 

  • pydantic的类对象实例
from fastapi import FastAPI,Path
from pydantic import BaseModel
app = FastAPI()


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


@app.get('/', response_model=User,  # 此处的response_model是可选的
         response_model_include=[], # 要响应的字段
         response_model_exclude=[]) # 不响应的字段,两者二选一
async def func1():
    user=User(username='a',age=12)
    return user               # 直接返回pydantic的BaseModel的子类

 

 

  • 模板渲染
from fastapi.templating import Jinja2Templates
app = FastAPI()
template=Jinja2Templates(directory='./templates').


@app.get('/')
async def func1():
    return template.TemplateResponse('模板文件.html',context={'request':request,***})

 

  • 纯文本
from fastapi.templating import Jinja2Templates
app = FastAPI()

@app.get('/', response_model=User)
async def func1():
    return HTMLResponse('html格式的数据')

 

  • 特殊文件
@app.get('/img')
async def get_img(request: Request):

    return FileResponse(
        				'./statics/img/000.jpg',
                        media_type='image/jpg')

 

 

6.中间件

  • 装饰器方法
@app.middleware('http')
def my_middleware(request:Request, call_next):
    # 这里进行访问视图前的处理
    # 在这里给request添加属性是没有意义的,不会传递下去    具体原因还在找
    response = call_next(request)
    # 这里进行访问视图后的处理
    return response

 

7.CORS跨域请求

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost.tiangolo.com",
    "https://localhost.tiangolo.com",
    "http://localhost",
    "http://localhost:8080",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,        # 应该允许进行跨域请求的来源列表
    allow_credentials=True,       # 表示跨域请求应支持cookie
    allow_methods=["*"],          # 指出应使浏览器可以访问的所有响应标头
    allow_headers=["*"],          # 跨域请求应允许的HTTP方法列表
)

 

 

8.异常处理Haddler

from fastapi import FastAPI, HTTPException

app = FastAPI()

@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str):
    if item_id not in items:
        # 需要抛出异常时,直接抛出HTTPException,会自动转换成json格式返回给前端
        raise HTTPException(
            status_code=404,
            detail="Item not found",
            headers={"X-Error": "There goes my error"},
        )
    return {"item": items[item_id]}

# 也可以自定义异常类
class UnicornException(Exception):
    def __init__(self, name: str):
        self.name = name
   
@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException):
    return JSONResponse(
        status_code=418,
        content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."},
    )


# 当需要抛出异常时,直接抛出自定义的异常类

 

 

9.BackgroundTasks

后台任务,当需要指定的任务处理较慢,可以放在后台处理,可以先返回202(已接收的状态码)并在后台进行处理

from fastapi import BackgroundTasks, FastAPI

app = FastAPI()


def write_notification(email: str, message=""):
    with open("log.txt", mode="w") as email_file:
        content = f"notification for {email}: {message}"
        email_file.write(content)


@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
    background_tasks.add_task(write_notification, email, message="some notification")
    return {"message": "Notification sent in the background"}

 

10.类视图

fastapi本身是没有类视图的概念的,实现类视图要借助 starlette.endpoints 下的 HTTPEndpoint或者WebSocketEndpoint

  • HTTPEndpoint 针对http请求
from fastapi import FastAPI
from starlette.endpoints import HTTPEndpoint, WebSocketEndpoint
app = FastAPI()

class Test_HTTP(HTTPEndpoint):
    async def get(self, request: Request):
        return ''
    
    async def post(self, '其他参数'):
        return ''

app.add_route('/test',Test_HTTP,name='aaa')
# 这种方式添加的视图,没有docs和redoc文档

app.add_api_route('/test2',func,name='bbb')    # 这个方法只能添加函数视图,因为底层是用fastapi的APIRoute创建的,只支持函数,也没有docs/redoc文档

 

  • WebSocketEndpoint 针对websocket

11.JWT

FastAPI推荐使用JWT进行校验

from jose import JWTError, jwt


# jwt加密
# data:要加载的内容
expire = datetime.utcnow() + timedelta(minutes=expires_delta) # expires_delta : int
data.update({'exp': expire})
jwt.encode(data, SECRET_KEY, algorithm=ALGORITHM)  # 生成的就是jwt_token

# jwt解密
jwt.decode(data, SECRET_KEY, algorithm=ALGORITHM)  # 加密后就是原来的字典

# 当签名过期时会报500错误

 

from passlib.context import CryptContext

# hash加密密码
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# 加密
hashed_password =pwd_context.hash(password)
# 校验
pwd_context.verify(password,hashed_password)  # 返回Bool值

# 模拟用户
user_db = {
    'ID': 'TMXC_001',
    'username': 'TMXC_admin',
    'password': pwd_context.hash('TMXC_password')
}

# 设置加密码
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30


# 生成token
def create_token(data: dict, expires_delta: Optional[int] = None):
    if expires_delta:
        expire = datetime.utcnow() + timedelta(minutes=expires_delta)
    else:
        expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    data.update({'exp': expire})
    access_token = jwt.encode(data, SECRET_KEY, algorithm=ALGORITHM)
    return access_token


# 发送请求响应token
@app.post('/token')
async def get_token(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
    username = form_data.username
    password = form_data.password

    print(username, password)

    if username == user_db.get('username') and pwd_context.verify(password, user_db.get('password')):
        data = {'user_id': user_db.get('ID')}
    else:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
                            detail='用户名或密码错误')

    token_code = create_token(data=data, expires_delta=3)
    # token = Tokenizer(token_code)
    # 前后端分离的可以直接传入,前端接收即可
    return token_code
	# 当前后端不分离,跳转新页面时,将token按传入,前端用jinja2模板引擎的方式接收
    # return template.TemplateResponse('add_art.html', context={'request': request, 'token_dict': token})

# 校验token
# 校验时,当过期会报错
def check_token(x_token: str = Header(..., convert_underscores=False)):
    try:
        periods = jwt.decode(x_token, SECRET_KEY, ALGORITHM)
        print(datetime.utcfromtimestamp(periods.get('exp')))
    except ExpiredSignatureError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='token已过期')
    return periods.get('user_id', None)

    
# 需要密钥才能访问的页面,利用依赖和请求头进行校验
@app.post('/artic/add')
async def add_artic(request: Request, user_id: str = Depends(check_token),
                    checktoken: CheckToken = Body(None)):
    # user_id = check_token(x_token)
    print(datetime.utcnow())
    if user_id:
        return '11111111'

 

html

var token = '';
$("#btn1").click(
    function () {
        $.ajax({
            type: "POST",
            url:'./artic/add',
            headers: {'x_token':token},  // 此处添加请求头
            data: JSON.stringify({'title':$('#tt1').val(),'count':$("#cnt").val()}),
            success:function (data,status){
                alert(data)
            },
            error:function (data){
                alert(data.detail)
            }

        })

    });

 

12 WebSocket

后端搭建websocket服务器

from fastapi import WebSocket

@app.websocket('/ws')
async def websocket_dep(websocket:WebSocket):
    await websocket.accept()
    while True:
        data =await websocket.receive_text()
        websocket.send_text()    # 响应内容

 

 

前端发送 请求 和接收内容

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        ul {
            list-style-type: none;
        }
        li {
            position: relative;
            height: 40px;
        }
    </style>
</head>
<body>
    <h1>Websocket</h1>
    <form action="">
        <input type="text" name="mess" id="msg">
        <input type="button" value="submit" id="btn">
    
    </form>


    <ul id='rst'>
        <li><span style="float: left;">aaaaaa</span></li>
        <li><span style="float: right;">bbbbbb</span></li>
    </ul>
    <script>
        var ws = new WebSocket('ws://localhost:8000/ws');
        ws.onmessage = function(event){
            li = document.createElement('li');
            span = document.createElement('span');
            span.innerHTML = event.data;    // 接收消息
            span.style.float = 'left';
            li.appendChild(span);
            rst = document.getElementById('rst');
            rst.appendChild(li);
        }
        var msg = document.getElementById('msg');
        var btn = document.getElementById('btn');
        btn.onclick = function(event){
            ws.send(msg.value)      // 发送消息
            
            li = document.createElement('li');
            span = document.createElement('span');
            span.innerHTML = msg.value;
            span.style.float = 'right';
            li.appendChild(span);
            msg.value = '';
            rst = document.getElementById('rst');
            rst.appendChild(li);
            // event.preventDefault();    // 不跳转
            return false     // 不跳转
        }
    </script>
</body>
</html>

 

托管

from fastapi import WebSocket

# 用于管理websocket的连接
class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def send_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)
        
    async def send_json(self,data,websocket: WebSocket):
        await websocket.send_json(data)
	
    # 给所有连接发送文本
    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)
            
manager = ConnectionManager()

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.send_message(f"You wrote: {data}", websocket)
            await manager.broadcast(f"Client #{client_id} says: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(f"Client #{client_id} left the chat")

 

13.部署 uvicorn + supervisor

uvicorn 运行server后,退出当前终端,项目进程会自动被杀死,可以使用supervisor 进行管理

# 安装 supervisor
sudo apt-get install supervisor

# 配置文件
sudo vi /etc/supervisor/conf.d/app.conf

# 写入一下内容
[program:tmxc]
directory = /home/ubuntu/tmxc/ ; 程序的启动目录
command = /home/ubuntu/.virtualenvs/tmxc/bin/uvicorn main:app --reload ; 启动命令,与命令行启动的命令是一样的 
autostart = true ; 在 supervisord 启动的时候也自动启动 
startsecs = 5 ; 启动 5 秒后没有异常退出,就当作已经正常启动了 
autorestart = true ; 程序异常退出后自动重启 
startretries = 3 ; 启动失败自动重试次数,默认是 3 
user = ubuntu ; 用哪个用户启动
redirect_stderr = true ; 把 stderr 重定向到 stdout,默认


# 重启supervisor**** 重要
sudo /etc/init.d/supervisor restart

# 重启之后已经运行起来了,当更新了配置文件后,需要重启

sudo supervisorctl restart tmxc
posted @ 2022-03-03 14:00  小学弟-  阅读(1126)  评论(1编辑  收藏  举报