fastapi基础
fastapi基础
1.0 第一个程序
import uvicorn
from fastapi import FastAPI
app =FastAPI()
@app.get(path='/')
async def index():
return "Hello World"
如果我们使用命令行模式
启动:
uvicorn 文件名:app
参数
--reload
如果加了参数 reload,就会实现热重载,每次更改完代码后会自动重启服务,此时刷新页面就能看到更改完的内
容了,不用手动刷新服务
--host ip
--port 端口号
而是决定服务器接受哪些网络接口的连接
如果不指定则默认为回环地址
- 如果使用回环地址,则只接受本机访问
外部设备无法连接,最安全,用于本地开发
- 如果使用0.0.0.0
则监听所有网络接口允许本机、局域网和公网(如果有)访问 最灵活,但需要注意安全
上线时采用的也是这个
- 特定网卡IP(如192.168.1.5)
只监听该特定网卡
只有通过该IP才能访问
适合多网卡环境限制访问
1.1 doc文档说明
import uvicorn
from fastapi import FastAPI
app =FastAPI()
@app.post('/',
tags=["这是标题"],
summary="总结",
description="描述",
response_description="111"
)
async def index():
return "Hello World"
if __name__=="__main__":
uvicorn.run("main:app",port=8000,reload=True)
fastapi会根据你代码中的路由自动生成api文档在/docs下
我们可以通过给装饰器方法传参来给文档添加简介
1.2 include route
该方法用于将子路由接入
子路由:
import uvicorn
from fastapi import FastAPI, APIRouter
shop =APIRouter()
@shop.get("/shop/food")
def shop_def():
return {"shop":1}
import uvicorn
from fastapi import FastAPI, APIRouter
user =APIRouter()
@user.get("/ueser/name")
def user_def():
return {"username":"bob"}
main文件
import uvicorn
from fastapi import FastAPI
from apps.app01.url1 import shop
from apps.app02.url2 import user
app =FastAPI()
app.include_router(user,prefix="",tags=["user center"])
app.include_router(shop,prefix="",tags=["shop center"])
注意,需要先将子路由import后才能加入路由中
prefix是加在路径前的路径,如果不填那就使用子路由的路径
1.3 路径参数
该方式用于解决动态路径映射
解决用路径进行查询时的问题
from fastapi import APIRouter
app1 = APIRouter()
@app1.get("/user/{userid}")
def get_user(userid):
return {"user":userid}
对于这个路由,我们可以将获得路径最后的userid作为变量
获得了路径参数
默认路径上传递给路径函数的任何值都是字符串,如果需要特定类型,就需要进行类型设置
from fastapi import APIRouter
app1 = APIRouter()
@app1.get("/user/{userid}")
def get_user(userid:int):
print(type(userid))
return {"user":userid}
我们在路径操作函数的接受参数中加入了限定,当我们接受到值后会将其转化为我们指定的类型
如果输入的类型无法转化为指定的类型,就会报错
当我们设置的路由产生重复,即同一个路径会符合不止一种路由,触发的路径操作函数由路由设置的顺序决定,在
最前面的就会被触发,因为我们路径和路由进行匹配时是按照顺序从上到下进行匹配
1.4 查询参数
查询参数就说通过查询字符串传递的参数,并非只有get方法才能传递查询参数,post也可也
在路径操作函数中,如果有传入不属于路径参数的,默认按查询参数判断
from fastapi import FastAPI, APIRouter
app01 = APIRouter()
@app01.get("/job")
def get_job(work,pay):
return {"message":"400"}
当我们在路径操作函数中传递了两个非路径参数时,打开api文档
发现出现了两个必须要传递的参数,而且乐星为query,这就是查询参数,因为我们加入到了函数中,素以当访问
该路径时必须要加入这两个参数
如果想要实现填不填的效果,一般我们会在路径操作函数中加入默认参数
from fastapi import FastAPI, APIRouter
app01 = APIRouter()
@app01.get("/job/{id}")
def get_job(id,work=1,pay=2):
return {"message":"400"}
由于路径参数必须由路由触发时传递,没有参数路由不会触发,故必须给出,不能默认
from fastapi import FastAPI, APIRouter
from typing import Union
app01 = APIRouter()
@app01.get("/job/{id}")
def get_job(id,work:Union[int,None]=None,pay=2):
return {"message":"400"}
我们通过Union可以指定多个参数类型怎加默认值
如果Union中存在None
,则可以使用Optional[其他类型]
来代替Union[其他类型,None]
此时我们的docs变成这样
1.5 post传递数据和格式转化
from fastapi import FastAPI, APIRouter
from pydantic import BaseModel
from datetime import date
from typing import List
class User(BaseModel):
name:str
age:int
birth:date
friends:List[int]
app03 = APIRouter()
@app03.post("/data")
def get_job(data:User):
return data
我们此时接受的数据是一个User类型的对象,通过继承BaseModel来做到数据类型检测,在实际使用时通过json
向目标路径传递数据,到达服务端时会自动将json格式先转化为字典,再转化为我们定义的类格式的对象
当我们直接将此对象返回时,fastapi又会将其转化为json格式
当我们指定了类内部属性的类型时,当我们传递规定以外的类型时就会报错,不过如果传递的是可以转好为指定类
型的数据,则不会报错,而是转换到指定类型,对属性类型的指定,我们可以有更多的约束方法
from fastapi import FastAPI, APIRouter
from pydantic import BaseModel,Field
from datetime import date
from typing import List, Union, Optional
class User(BaseModel):
name:str ="root"
age:int=Field(default=0,gt=0,lt=100)
birth:Union[None,date]=None
friends:List[int]
description:Optional[str]
app03 = APIRouter()
@app03.post("/data")
def get_job(user:User):
return user
我们导入pydantic的Field类,就可以指定int类型数据的范围,其中第一个参数是默认值,后面gt是最小值,lt是最
大值,通过Field还可以设置属性的值需要符合我们给定的正则表达式
如果某属性不设置默认值,此时如果实际传值的时候不给赋值则会报错
我们也可也使用装饰器对属性的类型进行限制
from fastapi import FastAPI, APIRouter
from pydantic import BaseModel,Field,field_validator
from datetime import date
from typing import List, Union, Optional
class User(BaseModel):
name:str ="root"
age:int=Field(default=0,gt=0,lt=100)
birth:Union[None,date]=None
friends:List[int]
description:Optional[str]
@field_validator("name")
def name_value(cls,value):
assert value.isalpha(),"have mistake"
return value
app03 = APIRouter()
@app03.post("/data")
def get_job(user:User):
return user
1.6 传递form格式
from fastapi import FastAPI, APIRouter,Form
app04 = APIRouter()
@app04.post("/regin")
def data(username:str=Form(),password:str=Form()):
print(f"username:{username}")
return {
"username":username
}
form指定了传递数据为表单形式
1.7 文件上传
from fastapi import FastAPI, APIRouter,Form,File
app05 = APIRouter()
@app05.post("/file")
def get_fole(file:bytes=File()):
print(file)
这种形式文件上传适用于小文件,会将文件存储到主存中,转化为bytes字节流对象
如果我们想要上传多个文件
from fastapi import FastAPI, APIRouter,Form,File
from typing import List
app05 = APIRouter()
@app05.post("/files")
def get_file(files:List[bytes]=File()):
return {
"len":len(files)
}
但这样保存到内存中肯定不合适,接下来来看文件句柄方式
from fastapi import APIRouter, File, UploadFile
from typing import List # 如果只处理单个文件,List 不是必需的
app05 = APIRouter()
@app05.post("/files")
async def get_file(file: UploadFile = File(...)):
return {"filename": file.filename, "content_type": file.content_type} # 4. 使用 file.filename
此时如果是小文件,会被保存到主存中等待下一步操作,如果是较大文件,将会作为临时文件存储在本地,等待后
续操作
我们接下来将接受的文件保存到本地:
from fastapi import APIRouter, File, UploadFile
from typing import List # 如果只处理单个文件,List 不是必需的
import os
app05 = APIRouter()
@app05.post("/files")
async def get_file(file: UploadFile = File(...)):
path=os.path.join("imgs",file.filename)
contents=await file.read()
with open(path,"wb") as f:
f.wirte(contents)
return {"filename": file.filename, "content_type": file.content_type} # 4. 使用 file.filename
File(...)中间带三个点,表示这个文件是必需的,如果不上传文件发post请求就不会正常响应
接下来看多个文件上传:
from fastapi import APIRouter, File, UploadFile
from typing import List
import os
app05 = APIRouter()
@app05.post("/uploadfiles")
async def create_upload_files(files: List[UploadFile] = File(...)):
# 1. 定义并确保保存目录存在
save_dir = "imgs"
os.makedirs(save_dir, exist_ok=True)
saved_filenames = []
for file in files:
path = os.path.join(save_dir, file.filename)
# 2. 使用 await 来异步读取文件内容
contents = await file.read()
with open(path, "wb") as f:
f.write(contents)
saved_filenames.append(file.filename)
# 3. (推荐) 返回一个包含已保存文件名的列表
return {"saved_files": saved_filenames, "message": "success"}
此时await是用来异步处理file.read() 因为这是一个异步方法
1.8 request 对象
from fastapi import FastAPI, APIRouter,Request
import os
app06 = APIRouter()
@app06.get("/request")
def get_request(request:Request):
return {
"url":request.url,
"host":request.client.host,
"header":request.headers,
}
1.9 静态文件夹
将存放css,html或者图片视频的文件夹放在项目路径中直接访问是不解析的,无法正常访问,我们想要使用和访问
这些文件时,需要将存放这些文件的文件夹作为静态文件夹挂在到我们定义的路径中,然后才能正常使用和访问
import uvicorn
from fastapi import FastAPI
from app.app01 import app01
from app.app02 import app02
from app.app03 import app03
from app.app04 import app04
from app.app05 import app05
from app.app06 import app06
from fastapi.staticfiles import StaticFiles
app=FastAPI()
app.mount("/static", StaticFiles(directory="statics"))
app.include_router(app01)
app.include_router(app02)
app.include_router(app03)
app.include_router(app04)
app.include_router(app05)
app.include_router(app06)
2.0 响应模型相关参数
response_model
response_model
的作用是指定接口响应数据的模型。FastAPI 会根据 response_model
自动对返回的数据进行校验和序列化,只返回模型中定义的字段,并自动生成接口文档。例如,这里只会返回 UserOut
中定义的字段(username
、email
、full_name
),不会返回 UserIn
里的 password
字段。
from fastapi import FastAPI, APIRouter
from pydantic import BaseModel,EmailStr
from typing import Union
import os
app07 = APIRouter()
class UserIn(BaseModel):
username:str
password:str
email:EmailStr
full_name:Union[str,None]=None
class UserOut(BaseModel):
username:str
email:EmailStr
full_name:Union[str,None]=None
@app07.post("/user",response_model=UserOut)
def creat_user(user:UserIn):
return user
使用该参数可以设置输出标准,将返回数据按设置的类的格式来进行返回,但是问题是如果我们的实际数据条数小
于我们的标准格式类的数据条数,也会按标准格式来输出,没有的数据按默认值来设置,如果缺少的数据在格式类
中没有设置默认值,则会报错
如果需要实现对于数据条数小于格式类的数据条数,直接返回原数据,则可以使用
response_model_exclude_unset
看这个例子
from fastapi import FastAPI, APIRouter
from pydantic import BaseModel,EmailStr
from typing import Union
import os
app07 = APIRouter()
class UserIn(BaseModel):
username:str
email:EmailStr
class UserOut(BaseModel):
username:str
email:EmailStr
full_name:Union[str,None]=None
@app07.post("/user",response_model=UserOut,response_model_exclude_unset=True)
def creat_user(user:UserIn):
return user
2.1 jinja2 基础
使用jinja2对模板进行替换
模板:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>username : {{user}}</h1>
<h1>age: {{age}}</h1>
<ul>
<li>{{username.0}}</li>
<li>{{username.1}}</li>
<li>{{username.2}}</li>
</ul>
<ul>
<li>
sex: {{bob.sex}}
age :{{bob.age}}
</li>
</ul>
</body>
</html>
import uvicorn
from fastapi import FastAPI,Request
from fastapi.templating import Jinja2Templates
import os
templates=Jinja2Templates(directory="date")
app =FastAPI()
@app.get("/index")
async def index(request:Request):
username=["bob","mike","lee"]
name="root"
bob={"sex":"man","age":12}
age=32
print(os.path.dirname(__file__))
return templates.TemplateResponse(
"index.html",
{
"request":request,
"user":name,
"age":32,
"username":username,
"bob":bob,
}, #上下文对象
)
显示效果为:
2.2 过滤器
使用例子:
模板变量|过滤器
举个例子
这样可以把前面变量的小写字母变成大写
<li>{{username.0|upper}}</li>
如果要传参,需要使用括号,下面的例子是将
<li>{{username|join(",")}}</li>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>username : {{user}}</h1>
<h1>age: {{age}}</h1>
<ul>
<li>{{username.0}}</li>
<li>{{username.1}}</li>
<li>{{username.2}}</li>
<li>{{username|join(",")}}</li>
</ul>
<ul>
<li>
sex: {{bob.sex}}
age :{{bob.age}}
</li>
</ul>
</body>
</html>
效果
2.3 jinjia2的控制结构
jinjia在模板嵌套时可以使用for或者if来进行结构控制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>username : {{user}}</h1>
<h1>age: {{age}}</h1>
<ul>
{%for user in username %}
<li>{{user}}</li>
{% endfor %}
</ul>
<ul>
{% if age >111 %}
<li>
sex: {{bob.sex}}
age :{{bob.age}}
</li>
{% else %}
<h1>there is nothing</h1>
{% endif %}
</ul>
</body>
</html>
效果:
2.4使用模板创建表
from tortoise.models import Model
from tortoise import fields
class Student(Model):
id=fields.IntField(pk=True)
name=fields.CharField(max_length=32)
pwd=fields.CharField(max_length=32)
snum=fields.IntField(description="student id")
cla=fields.ForeignKeyField("models.Cla",related_name="students")
courses=fields.ManyToManyField("models.Course",related_name="students")
class Cla(Model):
name=fields.CharField(max_length=32,description="class name")
class Course(Model):
id=fields.IntField(pk=True)
name=fields.CharField(max_length=32,description="course name")
teacher=fields.ForeignKeyField("models.Teacher",related_name="courses")
class Teacher(Model):
id=fields.IntField(pk=True)
name=fields.CharField(max_length=32,description="teacher's name ")
pwd=fields.CharField(max_length=32)
tnum=fields.IntField(description="teacher number")
这是名为models.py的模板类
import uvicorn
from fastapi import FastAPI
from tortoise.contrib.fastapi import register_tortoise
from orm.config import TORTOISE_ORM
app = FastAPI()
register_tortoise(
app=app,
config=TORTOISE_ORM,
generate_schemas=False,
add_exception_handlers=True,
)
@app.post("/index")
async def index():
return {"index": "index"}
这是运行的main.py,作为主类
TORTOISE_ORM = {
"connections": {"default": "sqlite://db.sqlite3"},
"apps": {
"models": {
# 确保这里的模型路径是正确的
"models": ["orm.models", "aerich.models"],
"default_connection": "default",
},
},
}
这是我们的设置信息
使用arerich来实现将模型转化为表
初始化
aerich init-db
当对model进行修改后,同步修改表需要:
先生成迁移文件
aerich migrate
再迁移到数据库
aerich upgrade
2.5 接口开发规范
这里我们使用最常见的restfulapi规范
在规范下,对某一资源的操作使用同一路径,只不过不同操作的请求方法不同
get用于查看,post用于添加,put用于更新,delete用于删除
现在看一下一些示例接口:
@student_api.get("")
async def getAllStudent():
students = await Student.all()
names = [student.name for student in students]
return {
"names": names,
"count": len(students)
}
此时使用Student.all()
得到的是一个类似于列表的queryset对象,这是一个迭代器对象,里面存储了我们定义
的模型类对象
注意,涉及到ORM数据库操作一律需要做异步处理,避免整个进程被阻塞,此时异步的意义是,当一个数据库操
作被阻塞时,其他的请求可以正常处理,而不是需要等待阻塞结束
我们也可以使用filter对查询进行过滤
@student_api.get("")
async def getAllStudent():
students = await Student.filter(cla_id=13)
names = [student.name for student in students]
return {
"names": names,
"count": len(students)
}
结果为:
得到的同样是一个迭代器对象,如果我们想直接得到符合条件的模型类对象,那就直接使用get方法
@student_api.get("")
async def getAllStudent():
students = await Student.get(cla_id=13)
names = students.name
return {
"names": names,
}
接下来我们来看模糊查询
@student_api.get("")
async def getAllStudent():
students = await Student.filter(snum__gt=2001)
names = [student.name for student in students]
return {
"names": names,
"count": len(students)
}
我们加上__gt
相当于大于号,也就是当前的过滤条件等于sum>2001
如果我们想找学号在某个列表中的学生,则可以这么写:
snum__in=[2001,2002]
如果此时想查找学号在一定范围的学生,可以这么写
snum__range=[1,200]
此时查找的是学号范围在1-200之间的学生
接下来学习values查询,values会将查询的内容以字典的形式放在列表中返回
比如
@student_api.get("")
async def getAllStudent():
students = await Student.all().values("name")
str=""
for student in students:
print(student)
return {
"names":1
}
此时返回的内容就是[{"name":"eric"},{"name":"alex"}]