fastapi——简单快速入门

fastapi——快速入门笔记

根据慕课网视频教程
地址:https://www.bilibili.com/video/BV1iN411X72b?p=36

print("\033[31m5. --- ORM模型: 从类创建符合的ORM对象模型   ---\033[Om")#显示彩色内容

fastapi是高性能的web框架。他的主要特点是:
快速编码
减少人为bug
直观
简易
具有交互式文档
基于API的开放标准(并与之完全兼容):OpenAPI(以前称为Swagger)和JSON Schema。

1.1fastapi的安装

一、fastapi的安装

pip install fastapi

当然你可以使用国内阿里云镜像源进行安装,会快很多,上面的语句变成下面的:

pip install fastapi -i https://mirrors.aliyun.com/pypi/simple

因为fastapi启动依赖于uvicorn,所以我们还需要安装uvicorn

pip install uvicorn -i https://mirrors.aliyun.com/pypi/simple

到这里,fastapi就安装完毕了,下面我们来验证一下安装是否成功

1-2、验证是否安装成功

新建名字叫main.py的文件,将下面内容复制到里面去

from fastapi import FastAPI

app = FastAPI()

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

然后使用终端开启uvicorn服务

uvicorn main:app --reload

uvicorn main:app 命令指:

main: main.py 文件(也可理解为Python模块).
app: main.py 中app = FastAPI()语句创建的app对象.
--reload: 在代码改变后重启服务器,只能在开发的时候使用

你将会看到如下的输出:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [8438] using statreload
INFO:     Started server process [8440]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

然后打开浏览器,输入: http://127.0.0.1:8000。看看有没有打印出:"message": "Hello World" 这句话,如果有,表示安装成功。

如果你此时访问 http://127.0.0.1:8000/docs。你将会看到自动生成的API交互文档。这点很重要,也是fastapi的一个优点。

1.2.2fastapi项目启动时,提示ERROR

fastapi项目启动时,提示ERROR: Error loading ASGI app. Could not import module “main”.

文件名为 f1.py

代码如下:

from fastapi import FastAPI  # 导入FastAPI
import uvicorn

app = FastAPI()  # 创建一个app实例


@app.get("/")  # 编写一个路径操作装饰器
async def root():  # 编写一个路径操作函数
    return {"你好!": "朋友。"}


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

注意:声明app的文件路径应该是 f1:app,而不是main:app。

只需将 app=‘main:app’ 改为app='f1:app’即可。

结束!

1.3fastapi固定开头

# -*- codeing = utf-8 -*-
# @time : 2021/5/7 23:57
# @file : main.py.PY
# @Author : 夜羽
from fastapi import FastAPI  # 导入FastAPI
#from fastapi import APIRouter         #接口路由
import uvicorn       # 运行fastapi
'''内容选填
app = FastAPI()  # 创建一个app实例           
#app01 = APIRouter()

@app.get("/")  # 编写一个路径操作装饰器
async def root():  # 编写一个路径操作函数
    return {"你好!": "朋友。123"}
'''

if __name__ == '__main__':
    uvicorn.run(app='main:app', host="127.0.0.1", port=8000, reload=True, debug=True,  workers=1)
    
    # workers=1进程数量

main: main.py 文件(也可理解为Python模块).
app: main.py 中app = FastAPI()语句创建的app对象.
--reload: 在代码改变后重启服务器,只能在开发的时候使用

debug: 修改代码后不必再运行一次程序,保存后可直接运行

另一种方法

导入运行文件名在一个程序文件中:

from .chapter03 import app03
from .chapter04 import app04
from .chapter05 import app05
from .文件名 import app实例名

后只用一个程序运行多个:

from fastapi import FastAPI
import uvicorn   #运行fastapi
from fastapi知识点教程 import app03     导入app实例名称项目
from fastapi知识点教程 import app04
from fastapi知识点教程 import app05
#from 上层文件夹名称 import app实例名


app = FastAPI()


#prefix=  路径    tags=[]  名称
app.include_router(app03, prefix='/chapter03', tags=['第三章 请求参数和验证'])
app.include_router(app04, prefix='/chapter04', tags=['第四章 响应处理和fastapi设置'])
app.include_router(app05, prefix='/chapter05', tags=['第五章 fastapi的依赖注入系统'])


if __name__ == '__main__':
    uvicorn.run('RUN:app', host="127.0.0.1", port=8000, reload=True, debug=True, workers=1)
#woker=1进程数量

1.4代码练习

from typing import List, Optional        #Optional 参数可以为空或已经声明的类型

app = FastAPI()  # 创建一个app实例

class Cityinfo(BaseModel):
    province: str
    country: str
    is_affectse: Optional[bool] = None

@app.get("/")  # 编写一个路径操作装饰器
def root():  # 编写一个路径操作函数
    return {"你好!": "朋友。23"}

#@app.get("/city/{city}?q=xx")  # 编写一个路径操作装饰器   q=xx 查询参数
@app.get("/city/{city}")
async def result(city: str,query: Optional[str] = None): # 编写一个路径操作函数
    return {"city": city, "query": query}

if __name__ == '__main__':
    uvicorn.run(app='web1:app', host="127.0.0.1", port=8000, reload=True, debug=True)
# -*- codeing = utf-8 -*-
# @time : 2021/5/6 22:55
# @file : pydantic.PY
# @Author : 夜羽
# @Software : PyCharm
#导入库时一定要注意大小写
from pydantic import BaseModel, ValidationError  #可在运行时提供代码类型提示,报错是提供较为友好的错误提示
from datetime import datetime, date
from pathlib import Path         #新建Path文件
from typing import List, Optional        #Optional 参数可以为空或已经声明的类型

from sqlalchemy import Column, Integer, String     #列,征型,字符类型
from sqlalchemy.dialects.postgresql import array  #元祖
from sqlalchemy.ext.declarative import declarative_base     #声明



class user(BaseModel):
    id: int
    name: str = "john snow"     #默认值
    signup_ts: Optional[datetime] = None      #Optional 参数可以为空或已经声明的类型
    friends: list[int] = []    #列表中元素是int类型或者是可以直接转化为int 类型的值

date = {
    "id" : "123456",
    "name" : 'nima',
    "signup_ts" : datetime.today(),#现在的时间
    "friends" :[1,'2',3]
}
user = user(**date)  # **的作用是收集关键字参数到一个新的字典,并将整个字典赋值给字典_date
print(user.id,user.name)  #实例化后调用属性
print(user.json())    #字典形式打开
print(user.id)          #实例化后调用属性
print(user.dict())       #字典形式打开
print(user.schema())
print(user.schema_json())     #会提示使用的数据格式类型
print(user.__fields__.keys())     #查看定义的类型,所有的字段都表明类就不会乱


"""
Path = Path('pydanti.json')         #新建一个jsom文件
Path.write_text('{"id" : "123456","name" : "nima","signup_ts" : datetime.today(),friends" :[1,"2",3]}')
print(user.parse_file(Path))     #解析文件 parse_解析

#写入内容 write_text写入

#现在的时间"


User_data = {'{"id" : "123","name" : "nima","signup_ts" : datetime.today(),friends" :[1,"2",3]}'}
print(user.construct(**User_data))
"""

class sound(BaseModel):
    sound: str

class dog(BaseModel):
    birthday: Optional[datetime] = None
    weight: float = Optional[None]
    sound: List[sound]             # 不同的叫声. 递归模型,就是指一个嵌套一个


dogs = dog(birthday=datetime.today(), weight=6.66, sound=[{"sound": "wangwagn"}, {"sound": "yingying"}])
print(dogs.dict())


print("\033[31m5. --- ORM模型: 从类创建符合的ORM对象模型   ---\033[Om")

Base = declarative_base()  #声明性基础

class companyorm(Base):
from fastapi import APIRouter, Path       #APIRouter  api路由器, Path   路径校验
from enum import Enum          # 枚举

app03 = APIRouter()

@app03.get("/path/parametrs")
def path01():
    return {"message": "this is a message"}

@app03.get("/path/{parametrs}")        # 函数的顺序就是路由的顺序
def path01(parametrs: str):
    return {"message": parametrs}

class cityname(str,Enum):           #枚举参数类型, 使用后可以选填下方参数
    beijing = "beijing China"
    shanghai = "shanghai China"

@app03.get("/enum/{city}")                #枚举参数类型
async def latest(city: cityname):
    if city == cityname.shanghai:
        return {'city_name': city, "确诊数量": 123, "死亡": 0}
    if city == cityname.beijing:
        return {'city_name': city, "确诊数量": 1231, "死亡": 0}
    return {"city_name": city, "latest": "unknown"}



@app03.get("/files/{flie_path: path}")      # 路径参数path parameters传递文件路径
def fliepath(flie_path: str):
    return f"the file path is {flie_path}"            #传递数据

 # Path(None) 可为空 ,title="这是一个标题", description="描述", ge=1,le=10 大于1小于10
@app03.get("/path_1/{unm}")
def path03(
        #num: int =Path(None, title="这是一个标题", description="描述",ge=1,le=10)
        unm: int = Path(..., title="这是一个标题", description="描述", ge=1, le=10)
):
    return unm

run运行文件

# -*- coding = utf-8 -*-
# @Time : 2021/5/10 10:18
# @File : RUN.PY
# @Author : 夜羽
# @Software: PyCharm
import time
from fastapi import FastAPI, Request, Request
from fastapi.middleware.cors import CORSMiddleware  #跨域资源共享
from fastapi.templating import Jinja2Templates   #模板渲染网页
import uvicorn   #运行fastapi
from fastapi知识点教程 import app03
from fastapi知识点教程 import app04
from fastapi知识点教程 import app05
from fastapi知识点教程 import app06
from fastapi知识点教程 import app07
from fastapi知识点教程 import app08
#from coronavirus import application

from fastapi.staticfiles import StaticFiles             #静态文件

#应用常见配置
app = FastAPI(
    title='FastAPI 学习和 练习用的 API Docs',    #标题
    description='FastAPI教程 学习练习api,学习视频:https://www.bilibili.com/video/BV1iN411X72b  老师github库:https://github.com/liaogx/fastapi-tutorial',     #描述
    version='1.0.0',           #版本号
    docs_url='/docs',       #路由地址
    redoc_url='/redocs',   #重置地址
)
app.mount(path="/static", app=StaticFiles(directory="./static"), name="static")   #静态文件配置    # .mount()不要在分路由APIRouter().mount()调用,模板会报错

#拦截所有的请求     middleware中间键
@app.middleware('http')
async def add_process_time_header(request: Request, call_next):  # call_next将接收request请求做为参数
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers['X-Process-Time'] = str(process_time)  # 添加自定义的以“X-”开头的请求头
    return response


# 跨域资源共享
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://127.0.0.1",
        "http://127.0.0.1:8080"
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)






#prefix=  路径    tags=[]  名称
app.include_router(app03, prefix='/chapter03', tags=['第三章 请求参数和验证'])
app.include_router(app04, prefix='/chapter04', tags=['第四章 响应处理和fastapi设置'])
app.include_router(app05, prefix='/chapter05', tags=['第五章 fastapi的依赖注入系统'])
app.include_router(app06, prefix='/chapter06', tags=['第六章 fastapi的安全、认证和授权'])
app.include_router(app07, prefix='/chapter07', tags=['第七章 fastapi的数据库操作和多应用的目录结构设计'])
app.include_router(app08, prefix='/chapter08', tags=['第八章 中间键\跨域资源共享\后台任务\测试用例'])
#app.include_router(application, prefix='/chapter07', tags=['新冠病毒疫情跟踪器'])



if __name__ == '__main__':
    uvicorn.run('RUN:app', host="127.0.0.1", port=8000, reload=True, debug=True, workers=1)
#woker=1进程数量

在一个文件中实现

@app.get("/11", tags=["渲染界面"],
    responses={403: {"description": "Operation forbidden"}},)
async def root():
    return {"message": "Hello World"}

@app.get("/12", tags=["渲染界面1"],
    responses={403: {"description": "Operation forbidden"}},)
async def root():
    return {"message": "Hello World"}

@app.get("/13", tags=["渲染界面2"],
    responses={403: {"description": "Operation forbidden"}},)
async def root():
    return {"message": "Hello World"}

api备注

Path(None) 可为空 ,title=“这是一个标题”, description=“描述”, ge=1,le=10 大于1小于10, alias=‘别名’ #convert_underscores=True 转换为无下划线

default=[“默认值”],

值类型列子

def cookie(user_agent: Optional[str] = Header(None, convert_underscores=True, description="描述"),
        x_token: List[str] = Header(None, description="描述")):
    
    unm: int = Path(..., title="这是一个标题", description="描述", ge=1, le=10)
#查询:    
values: list[str] = Query(default=["v1","v2"], alias='alias_name', title="这是一个标题", description="描述",)
@app03.get("/path_1/{unm}")
def path03(
    values: list[str] = Query(default=["v1","v2"], alias='alias_name', title="这是一个标题", description="描述",),
    unm: int = Path(..., title="这是一个标题", description="描述", ge=1, le=10)
        #num: int =Path(None, title="这是一个标题", description="描述",ge=1,le=10)

api属性备注

“”"

备注内容

“”"

print("\033[31m5. --- cookkie 和 header   ---\033[Om")#

@app03.get("/header")         #不需要使用cookie
def cookie(user_agent: Optional[str] = Header(None, convert_underscores=True, description="XIANXI"), x_token: List[str] = Header(None, description="描述")):    #convert_underscores=True  转换为无下划线
    """
    有些http代理和服务器是不支持在请求中带有下划线的,convert_underscores=True转换为无下划线
    :param user_agent: convert_underscores=True
    :param x_token: 包含多个值得列表
    :return:    返还定义的属性
    """
    return {"user_agent": user_agent, "x_token": x_token}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9wLZRynr-1631113988190)(C:\Users\gouguoxiong\Desktop\py后端\批注 2021-05-10.jpg)]

枚举(可选型)

from enum import Enum 

class cityname(str,Enum):           #枚举参数类型, 使用后可以选填下方参数
    beijing = "beijing China"
    shanghai = "shanghai China"

子应用

#应用常见配置
app = FastAPI(
    title='FastAPI Tutorial and Coronavirus Tracker API Docs',    #标题
    description='FastAPI教程 新冠病毒疫情跟踪器API接口文档,项目代码:https://github.com/liaogx/fastapi-tutorial',     #描述
    version='1.0.0',           #版本号
    docs_url='/docs',       #路由地址
    redoc_url='/redocs',   #重置地址

1.5api交互文档

可以写入内容

http://127.0.0.1:8000/docs

只显示不可更改

FastAPI - ReDoc

查询参数

1、查询参数概念(前端传递的数据给后端校验)

#多查询参数的列表,参数别名alias

Query

当你声明不属于路径参数的其他函数参数时,它们将自动解释为“Query”参数,也就是查询参数。

查询参数就是一系列在URL?之后的key-value键值对,每对键值对用 & 分割开来。例如

http://127.0.0.1:8000/items/?skip=0&limit=10

查询参数有两个,一个是skip,一个是limit,它们的值分别为0,10

由于它们都是URL的一部分,所以 “本质上” 它们都是字符串。

但是当你需要使用Python类型来声明query参数的时候(例如用int),他们就会被转换为相应的类型并且依据这个类型来验证传入参数。

适用于Path参数的所有过程也适用于Query参数

  • 编辑器支持
  • 数据解析
  • 数据验证
  • 自动文档
from fastapi import APIRouter, Path, Query      #APIRouter  api路由器, Path   路径校验   Query 查询
from enum import Enum          # 枚举
from typing import List, Optional        #Optional 参数可以为空或已经声明的类型

print("\033[31m5. --- 查询参数, 和字符串验证   ---\033[Om")#


@app03.get("/quety")
def page_limit(page: int = 1 , limit: Optional[int] = None):
    if limit:
        return {"page":page, "limit": limit}
    return {"page": page,}


@app03.get("/quety/bool/conversion")
def type_conversion(param: bool = False):
    return param

@app03.post("/piery/validations")
def que(
        value: str = Query(...,min_length=8, max_length=20, regex="^a"),
        values: list[str] = Query(default=["v1","v2"], alias='alias_name')
):     #多查询参数的列表,参数别名alias
    return values, value

2.请求体和字段

当您需要将数据从客户端(例如浏览器)发送到API时,可以将其作为 “请求体” 发送。

请求体是客户端发送到您的API的数据。 响应体是您的API发送给客户端的数据。

API几乎总是必须发送一个响应体,但是客户端并不需要一直发送请求体。

定义请求体,需要使用 Pydantic 模型。注意以下几点

  • 不能通过GET请求发送请求体

  • 发送请求体数据,必须使用以下几种方法之一:POST(最常见)、PUT、DELETE、PATCH如何实现请求体

  • 当想实现全部接口的请求时:

    @app.api_route("/api", methods=("GET", "POST", "PUT"))
    async def api():
        return {"api": "GET, POST, PUT"}
    
实现请求体总共包含三个步骤。

第一步,从`pydantic`中导入`BaseModel`

```
from pydantic import BaseModel
```

第二步,创建请求体数据模型

声明请求体数据模型为一个类,且该类继承 BaseModel。所有的属性都用标准Python类。和查询参数一样:数据类型的属性如果不是必须的话,可以拥有一个默认值或者是可选None。否则,该属性就是必须的。

例如,声明了一个`JSON`对象

```python
from pydantic import BaseModel

class Item(BaseModel):
    name: str 
    description: str = None
    price: float 
    tax: float = None 
```

所以访问链接的时候传入的请求体可以是下面两种,

```python
{
    "name": "Foo",
    "description": "An optional description",
    "price": 45.2,
    "tax": 3.5
}
```

另一种可以不传递默认值或者是可选值,(注意字典后面最后一个元素不需要逗号)

```python
{
    "name": "Foo",
    "price": 45.2
}
```

第三步、将模型定义为参数

将上面定义的模型添加到你的路径操作中,就和定义Path和Query参数一样的方式:

```python
from fastapi import FastAPI
from pydantic import BaseModel


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item
```

声明参数的类型为你创建的模型 Item

这样当你使用postman选择post方法访问链接并传递一个值为

{
“name”: “Foo”,
“price”: 45.2
}

的请求体之后,会得到输出

{
“name”: “Foo”,
“price”: 45.2
}

3.cookie 和 header

安装postpai

使用:

cookie

print("\033[31m5. --- cookkie 和 header   ---\033[Om")#

@app03.get("/cookie")         #效果需要使用cookie
def cookie(cookie_id: Optional[str] = Cookie(None)):    #使用cookie参数来定义cookie类
    return {"coookie_id": cookie_id}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OWgQIWJX-1631113988192)(C:\Users\gouguoxiong\Desktop\py后端\批注 2021-05-10 22222.jpg)]

header

print("\033[31m5. --- cookkie 和 header   ---\033[Om")#

@app03.get("/header")         #不需要使用cookie
def cookie(user_agent: Optional[str] = Header(None, convert_underscores=True, description="XIANXI"), x_token: List[str] = Header(None, description="描述")):    #convert_underscores=True  转换为无下划线
    """
    有些http代理和服务器是不支持在请求中带有下划线的,convert_underscores=True转换为无下划线
    :param user_agent: convert_underscores=True
    :param x_token: 包含多个值得列表
    :return:    返还定义的属性
    """
    return {"user_agent": user_agent, "x_token": x_token}

响应数据进行规范和校验

1.响应模型类

#请求和响应的区别:返回响应不会返回密码

from pydantic import BaseModel, EmailStr   #检验是否为合法的邮箱地址
from typing import Optional, List, Union   #Union 并集方式
from fastapi import APIRouter, Path, Query, Cookie, Header    #APIRouter  api路由器, Path   路径校验   Query 查询

response_model= 路径操作(参数作什么操作或者代用)

# response_model=  路径操作(参数作什么操作或者代用)
# response_model_exclude_unset=True      对前面的路径操作使用提供的值,不使用默认值
# response_model_exclude_unset=False
#response_model=Union[userout,userin] #union并集方式
#response_model=userout,                 #定义的类
#response_model=userout, response_model_exclude_unset=True    # 对前面的路径操作使用提供的值,不使用默认值
#response_model=userin               #定义的类
response_model=list[userin]        #列表方式
#response_model_include=["username", "email"]     #必须输出某一些字段
#response_model_exclude=["username", "email"]         #必须排除
#response_model_exclude_unset=["mobile", "email"]         #必须排除默认值

练习

# -*- coding = utf-8 -*-
# @Time : 2021/5/10 10:38
# @File : chapter04.PY
# @Author : 夜羽
# @Software: PyCharm
from pydantic import BaseModel, EmailStr   #检验是否为合法的邮箱地址
from typing import Optional, List, Union   #Union 并集方式
from fastapi import APIRouter, Path, Query, Cookie, Header    #APIRouter  api路由器, Path   路径校验   Query 查询

app04 = APIRouter()


#响应模型


class userin(BaseModel):
    username:  Optional[str] = Path(None, convert_underscores=True, description="描述")
    password: str
    email: str
    mobile: str = "电话号码"
    address: str = None    #地址可为空
    full_name: Optional[str] = None

#请求和响应的区别:返回响应不会返回密码
class userout(BaseModel):
    username: Optional[str] = Path(None, convert_underscores=True, description="描述")
    email: str
    mobile: str = "电话号码"
    address: str = None  # 地址可为空
    full_name: Optional[str] = None


users ={
    "user1": {"username": "user1", "password": "123456", "email": "[email protected]", "mobile": "12345678910"},
    "user2": {"username": "user2", "password": "654321", "email": "[email protected]", "mobile": "12345678911"}
}


# response_model=  路径操作(参数作什么操作或者代用)
# response_model_exclude_unset=True      对前面的路径操作使用提供的值,不使用默认值   response_model_exclude_unset=False
@app04.post("/response_model", response_model=userout, response_model_exclude_unset=True)
async def res(user: userin):
    print(user.password)   #password  响应不会被返回
    return users["user1"]



@app04.post(
    "/response_model/union",
    #response_model=Union[userout,userin] #union并集方式
    #response_model=userout,                 #定义的类
    #response_model=userout, response_model_exclude_unset=True    # 对前面的路径操作使用提供的值,不使用默认值
    #response_model=userin               #定义的类
    response_model=list[userin]        #列表方式
    #response_model_include=["username", "email"]     #必须输出某一些字段
    #response_model_exclude=["username", "email"]         #必须排除
    #response_model_exclude_unset=["mobile", "email"]         #必须排除默认值
)
async def union(user: userin):
    return [user, user]              #列表形式两个
'''
async def union(user: userin):
    return users["user1"]'''
'''
async def Union(user: userout):
    return users["user2"]
'''

2.status code 响应状态码

status 状态码

from fastapi import APIRouter, Path, Query, Cookie, Header, status   #APIRouter  api路由器, Path   路径校验   Query 查询   status状态码
@app04.post("/status_code", status_code=200)   #status_code=200路径操作
async def status_code():
    return {"status_code": 200}

@app04.post("/status_code", status_code=status.HTTP_200_OK)   #status_code=200路径操作
async def status_attribute():
    print(type(status.HTTP_200_OK))     #显示他的状态
    return {"status_code": status.HTTP_200_OK}

3.Form data 表单数据处理

Form

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cCvJnado-1631113988194)(C:\Users\gouguoxiong\AppData\Roaming\Typora\typora-user-images\image-20210516205234203.png)]

#需要安装multipart :pip install python-multipart
from fastapi import APIRouter, Path, Query, Cookie, Header, status, Form  #APIRouter  api路由器, Path   路径校验   Query 查询   status状态码   Form 表单数据处理

print("\033[31m5. ---  Form data 表单数据处理  ---\033[Om")#
#需要安装multipart :pip install python-multipart
@app04.post("/login")
async def login(username: str = Form(...), password: str = Form(...)):   #定义表单参数
    return {"username": username, "password": password}

4.File, UploadFile 文件上传

单\多文件上传及其参数详细

print("\033[31m5. ---  File, UploadFile 单\多文件上传及其参数详细  ---\033[Om")#

@app04.post("/file")
#async def flie_1(flie: bytes = File(...)):   #使用flie类时, 文件内容会以bytes的形式读入内存, 适合传输小文件,单个
async def file_(file: List[bytes] = File(...)):  #多个文件传输
    """使用File类 文件内容会以bytes的形式读入内存 适合于上传小文件"""
    return {"file_size": len(file)}


@app04.post("/upload_files")
async def upload_files(files: List[UploadFile] = File(...)):  # 如果要上传单个文件 file: UploadFile = File(...)
    """
    使用UploadFile类的优势:
    1.文件存储在内存中,使用的内存达到阈值后,将被保存在磁盘中
    2.适合于图片、视频大文件
    3.可以获取上传的文件的元数据,如文件名,创建时间等
    4.有文件对象的异步接口
    5.上传的文件是Python文件对象,可以使用write(), read(), seek(), close()操作
    """
    for file in files:            #循环所有的文件
        contents = await file.read()      #await异步   file.read()读取
        print(contents)
    return {"filename": files[0].filename, "content_type": files[0].content_type} #文件的名称和类型

5.FastAPI项目的静态文件配置

"""【见run.py】FastAPI项目的静态文件配置"""

"""print(user.password)"""
@app04.post(
    "/path_operation_configuration",         #地址
    response_model=userout,                        #返回路由
    # tags=["Path", "Operation", "Configuration"],     #标签, docs界面显示接口
    summary="This is summary",             #接口描述
    description="This is description",        #描述
    response_description="This is response description",     #返回的结果添加描述
    # deprecated=True,           #废弃接口
    status_code=status.HTTP_200_OK          #状态码
)
async def path_operation_configuration(user: userin):
    """
    Path Operation Configuration 路径操作配置
    :param user: 用户信息
    :return: 返回结果
    """
    return users["user1"]

6.Handling Errors 错误处理

from fastapi import APIRouter, Path, Query, Cookie, Header, status, Form, File, UploadFile, HTTPException #APIRouter  api路由器, Path   路径校验   Query 查询   status状态码   Form 表单数据处理   File, UploadFile上传文件       HTTPException错误处理
run.py文件中
# from fastapi.exceptions import RequestValidationError
# from fastapi.responses import PlainTextResponse
# from starlette.exceptions import HTTPException as StarletteHTTPException

基本模型

raise HTTPException(
    status.HTTP_401_UNAUTHORIZED,
    detail="Incorrect username or password",
    headers={"WWW-Authenticate": "Bearer"},
@app.exception_handler(StarletteHTTPException)  # 重写HTTPException异常处理器
async def http_exception_handler(request, exc):
     """
     :param request: 这个参数不能省
     :param exc:
     :return:
     """
     return PlainTextResponse(str(exc.detail), status_code=exc.status_code)


 @app.exception_handler(RequestValidationError)  # 重写请求验证异常处理器
 async def validation_exception_handler(request, exc):
     """
     :param request: 这个参数不能省
     :param exc:
     :return:
     """
     return PlainTextResponse(str(exc), status_code=400)

代码练习

raise HTTPException异常处理报错开头

"""Handling Errors 错误处理"""
@app04.get("/http_exception")
async def http_exception(city: str):
    if city != "Beijing":
        raise HTTPException(status_code=404, detail="City not found!", headers={"X-Error": "Error"})         #异常处理显示报错
    return {"city": city}


@app04.get("/http_exception/{city_id}")
async def override_http_exception(city_id: int):
    if city_id == 1:
        raise HTTPException(status_code=418, detail="Nope! I don't like 1.")
    return {"city_id": city_id}

fastapi的依赖和注入系统[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6HvBwtlJ-1631113988196)(C:\Users\gouguoxiong\AppData\Roaming\Typora\typora-user-images\image-20210517105814603.png)]

导入依赖from fastapi import APIRouter, Path, Depends #Depends依赖

1.函数依赖创建

dependencies 创建\导入和声明依赖

from fastapi import APIRouter, Path, Query, Cookie, Header, status, Form, File, UploadFile, HTTPException, Depends #APIRouter  api路由器, Path   路径校验   Query 查询   status状态码   Form 表单数据处理   File, UploadFile上传文件       HTTPException错误处理     Depends依赖

#依赖的导入与申明 Depends依赖 dict = Depends(common_parameters)

from fastapi import APIRouter
from pydantic import BaseModel, EmailStr   #检验是否为合法的邮箱地址
from typing import Optional, List, Union   #Union 并集方式
from fastapi import APIRouter, Path, Query, Cookie, Header, status, Form, File, UploadFile, HTTPException, Depends #APIRouter  api路由器, Path   路径校验   Query 查询   status状态码   Form 表单数据处理   File, UploadFile上传文件       HTTPException错误处理     Depends依赖

app05 = APIRouter()


print("\033[31m5. --- dependencies 创建\导入和声明依赖   ---\033[Om")#
#函数依赖
#page: int = 1,页数为1         limit: int = 100 限制为一百条
#创建依赖
async def common_parameters(q: Optional[str] = None, page:int = 1, limit:int = 100):
    return {"q": q, "page": page, "limit": limit}


#依赖的导入与申明   Depends依赖    dict = Depends(common_parameters)字典形式导入依赖
@app05.get("/dependency01")
async def dependency01(commons: dict = Depends(common_parameters)):
    return commons

#依赖不区分同步异步
@app05.get("/dependency02")
def dependency02(commons: dict = Depends(common_parameters)):
    return commons

2.类依赖创建

class as dependencies 类依赖

async def classes_as_dependencies(commons: CommonQueryParams = Depends(CommonQueryParams)): #引用依赖

async def classes_as_dependencies(commons: CommonQueryParams = Depends()): #引用依赖

async def classes_as_dependencies(commons=Depends(CommonQueryParams)): #引用依赖

print("\033[31m5. ---class as dependencies 类依赖   ---\033[Om")#

#创建一个值列表
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


#创建类依赖
class CommonQueryParams:
    def __init__(self, q: Optional[str] = None, page: int = 1, limit: int = 100):
        self.q = q
        self.page = page
        self.limit = limit

@app05.get("/classes_as_dependencies")
# async def classes_as_dependencies(commons: CommonQueryParams = Depends(CommonQueryParams)):    #引用依赖
# async def classes_as_dependencies(commons: CommonQueryParams = Depends()):    #引用依赖
async def classes_as_dependencies(commons=Depends(CommonQueryParams)):    #引用依赖
    response = {}   #空字典
    if commons.q:
        response.update({"q": commons.q})
    items = fake_items_db[commons.page: commons.page + commons.limit]
    response.update({"items": items})   #更新到response. 字典中 ,获取数据     update更新
    return response

3.子依赖

Sub-dependencies 子依赖

use_cache=True 只调用一次依赖

print("\033[31m5. ---Sub-dependencies 子依赖   ---\033[Om")#

def query(q: Optional[str] = None):
    return q

#Depends依赖
def sub_query(q: str = Depends(query), last_query: Optional[str] = None):
    if not q:
        return last_query
    return q

#use_cache=True  只调用一次依赖
@app05.get("/sub_dependency")
async def sub_dependency(final_query: str = Depends(sub_query, use_cache=True)):
    """use_cache默认是True, 表示当多个依赖有一个共同的子依赖时,每次request请求只会调用子依赖一次,多次调用将从缓存中获取"""
    return {"sub_dependency": final_query}

4.路径操作装饰器中的多依赖

Dependencies in path operation decorators 路径操作装饰器中的多依赖

这时候不是在函数参数中调用依赖,而是在路径操作中 dependencies

async def verify_token(x_token: str = Header(...)):
    """没有返回值的子依赖"""
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def verify_key(x_key: str = Header(...)):
    """有返回值的子依赖,但是返回值不会被调用"""
    if x_key != "fake-super-secret-key":
        raise HTTPException(status_code=400, detail="X-Key header invalid")
    return x_key


@app05.get("/dependency_in_path_operation", dependencies=[Depends(verify_token), Depends(verify_key)])  # 这时候不是在函数参数中调用依赖,而是在路径操作中
async def dependency_in_path_operation():
    return [{"user": "user01"}, {"user": "user02"}]

5.全局依赖

主程序开启其余有可能报错

print("\033[31m5. --- Global Dependencies  全局依赖  ---\033[Om")#
#全局变量app05 = APIRouter 为开头api路由表 也可以放在主程序中调用
app05 = APIRouter(dependencies=[Depends(verify_token), Depends(verify_key)])

6.带yield的依赖

  try:                       #尝试
      yield db                #yield 返回
  finally:                #最后
print("\033[31m5. --- Dependencies with yield 带yield的依赖  ---\033[Om")#

# 这个需要Python3.7才支持,Python3.6需要pip install async-exit-stack async-generator
# 以下都是伪代码
async def get_db():
    db = "db_connection"
    try:                       #尝试
        yield db                #yield 返回
    finally:                #最后
        db.endswith("db_close")      #假 关闭数据库


async def dependency_a():
    dep_a = "generate_dep_a()"
    try:
        yield dep_a
    finally:
        dep_a.endswith("db_close")


async def dependency_b(dep_a=Depends(dependency_a)):
    dep_b = "generate_dep_b()"
    try:
        yield dep_b
    finally:
        dep_b.endswith(dep_a)


async def dependency_c(dep_b=Depends(dependency_b)):
    dep_c = "generate_dep_c()"
    try:
        yield dep_c
    finally:
        dep_c.endswith(dep_b)

安全、认证和授权

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5KuMPuGY-1631113988198)(C:\Users\gouguoxiong\AppData\Roaming\Typora\typora-user-images\image-20210519223624516.png)]

1.OAuth2 密码模式

导入组件

from fastapi.security import OAuth2PasswordBearer       #安全密码承载
OAuth2 密码模式和 FastAPI 的 OAuth2PasswordBearer
# -*- coding = utf-8 -*-
# @Time : 2021/5/18 23:24
# @File : chapter06.PY
# @Author : 夜羽
# @Software: PyCharm
from pydantic import BaseModel, EmailStr   #检验是否为合法的邮箱地址
from typing import Optional, List, Union   #Union 并集方式
from fastapi import APIRouter, Path, Query, Cookie, Header, status, Form, File, UploadFile, HTTPException, Depends #APIRouter  api路由器, Path   路径校验   Query 查询   status状态码   Form 表单数据处理   File, UploadFile上传文件       HTTPException错误处理     Depends依赖
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm       #安全密码承载, 表单中


app06 = APIRouter()

print("\033[31m5. --- OAuth2 密码模式和 FastAPI 的 OAuth2PasswordBearer   ---\033[Om")#

"""
OAuth2PasswordBearer是接收URL作为参数的一个类:客户端会向该URL发送username和password参数,然后得到一个Token值
OAuth2PasswordBearer并不会创建相应的URL路径操作,只是指明客户端用来请求Token的URL地址
当请求到来的时候,FastAPI会检查请求的Authorization头信息,如果没有找到Authorization头信息,或者头信息的内容不是Bearer token,它会返回401状态码(UNAUTHORIZED)
"""
#认证方案
oauth2_schema = OAuth2PasswordBearer(tokenUrl="/chapter06/token")  # 请求Token的URL地址 http://127.0.0.1:8000/chapter06/token

@app06.get("/oauth2_password_bearer")
async def oauth2_password_bearer(token: str = Depends(oauth2_schema)):
    return {"token": token}

print("\033[31m5. --- 基于 Password 和 Bearer token 的 OAuth2 认证   ---\033[Om")#


fake_users_db = {
    "john snow": {
        "username": "john snow",
        "full_name": "John Snow",
        "email": "[email protected]",
        "hashed_password": "fakehashedsecret",
        "disabled": False,                  #判断用户是否活跃, 可以作为用户分级
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Wonderson",
        "email": "[email protected]",
        "hashed_password": "fakehashedsecret2",
        "disabled": True,                 #判断用户是否活跃, 可以作为用户分级
    },
}

def fake_hash_password(password: str):
    return "fakehashed" + password       #定义密码的加密

class User(BaseModel):          #定义用户的信息格式, 返还信息
    username: str
    email: Optional[str] = None
    full_name: Optional[str] = None
    disabled: Optional[bool] = None

#继承上面的user , 用户值加入数据库中, 包括密码
class UserInDB(User):
    hashed_password: str


@app06.post("/token")       #表单中获取到用户的
async def login(form_data: OAuth2PasswordRequestForm = Depends()):    #类依赖导入
    user_dict = fake_users_db.get(form_data.username)   #.get获取\得到
    if not user_dict:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect username or password")
    user = UserInDB(**user_dict)
    hashed_password = fake_hash_password(form_data.password)    #或得加密密码
    if not hashed_password == user.hashed_password:             # 加密密码与数据库中的密码进行对比
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect username or password")     #
    return {"access_token": user.username, "token_type": "bearer"}

#获取用户
def get_user(db, username: str):      #数据库,名称
    if username in db:              #如果这个名称在数据库中
        user_dict = db[username]
        return UserInDB(**user_dict)    #python解码方式

#模拟解码token
def fake_decode_token(token: str):
    user = get_user(fake_users_db, token)
    return user

#获取当前的用户
async def get_current_user(token: str = Depends(oauth2_schema)):
    user = fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,       #没有经过授权
            detail="Invalid authentication credentials, 非法的认证信息 ",
            headers={"WWW-Authenticate": "Bearer"},  # OAuth2的规范,如果认证失败,请求头中返回“WWW-Authenticate”
        )
    return user

#获取当前活跃的用户
async def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive 未激活的用户 user")
    return current_user

#获取当前活跃的用户
@app06.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user

2.json web tokens 认证

from pydantic import BaseModel, EmailStr   #检验是否为合法的邮箱地址
from typing import Optional, List, Union   #Union 并集方式
from fastapi import APIRouter, Path, Query, Cookie, Header, status, Form, File, UploadFile, HTTPException, Depends #APIRouter  api路由器, Path   路径校验   Query 查询   status状态码   Form 表单数据处理   File, UploadFile上传文件       HTTPException错误处理     Depends依赖
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm       #安全密码承载, 表单中

from passlib.context import CryptContext    #密码加密
from jose import JWTError, jwt            #pyhton-jose库
from datetime import datetime, timedelta
print("\033[31m5. --- OAuth2 with Password (and hashing), Bearer with JWT tokens 开发基于JSON Web Tokens的认证   ---\033[Om")#


fake_users_db.update({
    "john snow": {
        "username": "john snow",
        "full_name": "John Snow",
        "email": "[email protected]",
        "hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
        "disabled": False,
    }
})

SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"  # 生成密钥 openssl rand -hex 32
ALGORITHM = "HS256"  # 算法
ACCESS_TOKEN_EXPIRE_MINUTES = 10  # 访问令牌过期分钟


class Token(BaseModel):
    """返回给用户的Token"""
    access_token: str
    token_type: str

#对用户密码进行加密     CryptContext 密码加密    schemes=["bcrypt"]加密算法
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
#认证方案
oauth2_schema = OAuth2PasswordBearer(tokenUrl="/chapter06/jwt/token")

#对密码进行校验, 明文密码和加密后密码
def verity_password(plain_password: str, hashed_password: str):
    """对密码进行校验.verify"""
    return pwd_context.verify(plain_password, hashed_password)

#获取用户
def jwt_get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)

#对用户进行校验

def jwt_authenticate_user(db, username: str, password: str):
    user = jwt_get_user(db=db, username=username)
    if not user:
        return False
    if not verity_password(plain_password=password, hashed_password=user.hashed_password):
        return False
    return user

#                                       #令牌过期时间
#datetime.utcnow():读取的时间一直都是系统的“世界标准时间”,不管系统的本地时区是否设置,读取的时间不会随这些设置变化;
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy() #备份一个data 后边会调用
    if expires_delta:   # 当前时间加上传递的时间, 主要是传递的时间
        expire = datetime.utcnow() + expires_delta
    else:     #,如果没有传递时间就默认15分钟
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})  #过期时间.更新到 exp(to_encode) 中
    #jwt.encode编码, claims需要编码的字符, key秘钥, algorithm算法
    encoded_jwt = jwt.encode(claims=to_encode, key=SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

#登录接口   response_model=路径操作
@app06.post("/jwt/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
    user = jwt_authenticate_user(db=fake_users_db, username=form_data.username, password=form_data.password) #form表单中的用户名, 密码
    if not user:
        raise HTTPException(
            status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )
    #获取到过期时间, expires_delta过期时间
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

# 获取用户
async def jwt_get_current_user(token: str = Depends(oauth2_schema)):
    credentials_exception = HTTPException(
        status.HTTP_401_UNAUTHORIZED,
        detail="证书校验失败",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token=token, key=SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    user = jwt_get_user(db=fake_users_db, username=username)
    if user is None:
        raise credentials_exception
    return user



#获取用户是否活跃
async def jwt_get_current_active_user(current_user: User = Depends(jwt_get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user")
    return current_user
#获取用户是否活跃
@app06.get("/jwt/users/me")
async def jwt_read_users_me(current_user: User = Depends(jwt_get_current_active_user)):
    return current_user

数据库操作和多应用的目录结构设计

1.在fastapi中配置和使用数据库

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = 'sqlite:///./coronavirus.sqlite3'
# SQLALCHEMY_DATABASE_URL = "postgresql://username:password@host:port/database_name"  # MySQL或PostgreSQL的连接方法

engine = create_engine(
    # echo=True表示引擎将用repr()函数记录所有语句及其参数列表到日志
    # 由于SQLAlchemy是多线程,指定check_same_thread=False来让建立的对象任意线程都可使用。这个参数只在用SQLite数据库时设置
    SQLALCHEMY_DATABASE_URL, encoding='utf-8', echo=True, connect_args={'check_same_thread': False}
)

# 在SQLAlchemy中,CRUD都是通过会话(session)进行的,所以我们必须要先创建会话,每一个SessionLocal实例就是一个数据库session
# flush()是指发送数据库语句到数据库,但数据库不一定执行写入磁盘;commit()是指提交事务,将变更保存到数据库文件
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, expire_on_commit=True)

# 创建基本映射类
Base = declarative_base(bind=engine, name='Base')

2.创建表

  1. 在model文件的同目录下生成一个first名字的db文件【这个最简单,最推荐】
    sqlite:///first.db
    也就是app.config【SQLALCHEMY_DATABASE_URI】 = sqlite:/// + first.db

  2. 指定文件的地址,比如我想指定F盘下的pycharm_code文件夹下的flasktest文件夹下,并且名字是foo.db
    那么就这样子sqlite:/// + F:\pycharm_code\flasktest\foo.db
    代码上配置就可以这样子,自己可以参考一下哦~
    app.config【SQLALCHEMY_DATABASE_URI】 = sqlite:/// + F:\pycharm_code\flasktest\foo.db

from sqlalchemy import Column, String, Integer, BigInteger, Date, DateTime, ForeignKey, func
from sqlalchemy.orm import relationship       #关系型字段

from .database import Base
from sqlalchemy import Column, String, Integer, BigInteger, Date, DateTime, ForeignKey, func
from sqlalchemy.orm import relationship       #关系型字段

from .database import Base


class City(Base):
    __tablename__ = 'city'  # 数据表的表名
    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    province = Column(String(100), unique=True, nullable=False, comment='省/直辖市')
    country = Column(String(100), nullable=False, comment='国家')
    country_code = Column(String(100), nullable=False, comment='国家代码')
    country_population = Column(BigInteger, nullable=False, comment='国家人口')
    data = relationship('Data', back_populates='city')  # 'Data'是关联的类名;back_populates来指定反向访问的属性名称

    created_at = Column(DateTime, server_default=func.now(), comment='创建时间')
    updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间')

    __mapper_args__ = {"order_by": country_code}  # 默认是正序,倒序加上.desc()方法

    def __repr__(self):
        return f'{self.country}_{self.province}'


class Data(Base):
    __tablename__ = 'data'

    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    city_id = Column(Integer, ForeignKey('city.id'), comment='所属省/直辖市')  # ForeignKey里的字符串格式不是类名.属性名,而是表名.字段名
    date = Column(Date, nullable=False, comment='数据日期')
    confirmed = Column(BigInteger, default=0, nullable=False, comment='确诊数量')
    deaths = Column(BigInteger, default=0, nullable=False, comment='死亡数量')
    recovered = Column(BigInteger, default=0, nullable=False, comment='痊愈数量')
    city = relationship('City', back_populates='data')  # 'City'是关联的类名;back_populates来指定反向访问的属性名称

    created_at = Column(DateTime, server_default=func.now(), comment='创建时间')
    updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now(), comment='更新时间')

    __mapper_args__ = {"order_by": date.desc()}  # 按日期降序排列

    def __repr__(self):
        return f'{repr(self.date)}:确诊{self.confirmed}例'

“”" 附上三个SQLAlchemy教程

SQLAlchemy的基本操作大全
http://www.taodudu.cc/news/show-175725.html

Python3+SQLAlchemy+Sqlite3实现ORM教程
https://www.cnblogs.com/jiangxiaobo/p/12350561.html

SQLAlchemy基础知识 Autoflush和Autocommit
https://zhuanlan.zhihu.com/p/48994990
“”"

Jinjia2模板渲染网页

1.简单实现

首先,必须注意的是,FastAPI这个Python Web框架并没有带渲染的网页模板引擎,但是也正因为如此,它可以使用任何网页模板。
官方例子是jinjia2 。

依赖库安装

pip install jinja2 aiofiles

aiofiles是静态网页需要的包

特别说明的是,Starlette 是一个轻量级 ASGI 框架/工具包,FastAPI一大特色。

# -*- coding = utf-8 -*-
# @Time : 2021/5/26 12:37
# @File : jinja2渲染.PY
# @Author : 夜羽
# @Software: PyCharm
from pydantic import BaseModel, EmailStr   #检验是否为合法的邮箱地址
from typing import Optional, List, Union   #Union 并集方式
from fastapi import APIRouter, Path, Query, Cookie, Header, status, Form, File, UploadFile, HTTPException, Depends #APIRouter  api路由器, Path   路径校验   Query 查询   status状态码   Form 表单数据处理   File, UploadFile上传文件       HTTPException处理     Depends依赖
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm       #安全密码承载, 表单中

from passlib.context import CryptContext    #密码加密
from jose import JWTError, jwt            #pyhton-jose库
from datetime import datetime, timedelta
from fastapi import FastAPI, Request, Request
import uvicorn       # 运行fastapi
from fastapi.templating import Jinja2Templates   #模板渲染网页
from starlette.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates


app = FastAPI()

app.mount(path="/static", app=StaticFiles(directory="../static"), name="static")   #静态文件配置    # .mount()不要在分路由APIRouter().mount()调用,模板会报错      ,放置css.js文件

# 创建一个templates(模板)对象,以后可以重用。
templates = Jinja2Templates(directory="html")


@app.get("/items/")
async def read_item(request: Request, userid: str = Form(...)):
    print('userid', userid)
    return templates.TemplateResponse("item.html", {"request": request, "userid": userid})

@app.post("/user/")
async def form_text(request: Request, username: str = Form(...), password: str = Form(...), userid: str = Form(...)):
    print('username', username)
    print('password', password)
    print('userid', userid)

    # return {'text_1':text_1 , 'text_2': text_2}
    return templates.TemplateResponse('item.html', {'request': request, 'username': username, 'password': password, 'userid': userid})


@app.get("/")
async def main(request: Request):
    return templates.TemplateResponse('form.html', {'request': request})

if __name__ == '__main__':
    uvicorn.run('jinja2渲染:app', host="127.0.0.1", port=8000, reload=True, debug=True, workers=1)
#woker=1进程数量

tiem.html

<html>
<head>
    <title>Item Detailstitle>
head>
<body>
<div class="container">
    <h1>Item ID: {{ userid }}h1>
<h1>hello: {{ username }}h1>
<h1>你好: {{ password }}h1>
div>
body>
html>

form.html


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<div class="container">
            <form action="/user/" enctype="application/x-www-form-urlencoded" method="post">
                <label>usernamelabel>
                <br>
                <input name="username" type="username" >
                <br>
                <label>useridlabel>
                <br>
                <input name="userid" type="userid" >
                <br>
                <label>passwordlabel>
                <br>
                <input name="password" type="password" >
                <br><br>
                <input type="submit">
            form>

div>

body>
html>

子应用的全局依赖

大型工程应用设计:和目录拆分

# -*- coding = utf-8 -*-
# @Time : 2021/5/23 15:27
# @File : chapter07.PY
# @Author : 夜羽
# @Software: PyCharm
from fastapi import APIRouter, Depends, Request

"""【见coronavirus应用】SQL (Relational) Databases FastAPI的数据库操作"""

"""Bigger Applications - Multiple Files 多应用的目录结构设计"""


async def get_user_agent(request: Request):
    print(request.headers["User-Agent"])

# app07 子应用的全局依赖
app07 = APIRouter(
    prefix="/bigger_applications",
    tags=["第七章 FastAPI的数据库操作和多应用的目录结构设计"],  # 与run.py中的tags名称相同
    dependencies=[Depends(get_user_agent)],    #返回你的头标
    responses={200: {"description": "Good job!"}},
)


@app07.get("/bigger_applications")
async def bigger_applications():
    return {"message": "Bigger Applica

中间件

带yield的依赖的退出部分的代码 和 后台任务 会在中间件后运行

import time
from fastapi import FastAPI, Request, Request

在主程序中:

#拦截所有的请求     middleware中间键
@app.middleware('http')
async def add_process_time_header(request: Request, call_next):  # call_next将接收request请求做为参数
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers['X-Process-Time'] = str(process_time)  # 添加自定义的以“X-”开头的请求头
    return response

跨域资源共享

跨域主要是指域名不同的

from fastapi.middleware.cors import CORSMiddleware  #跨域资源共享

在主程序中:

# 跨域资源共享
app.add_middleware(
    CORSMiddleware,       #加载中间件
    allow_origins=[           #信任的列表
        "http://127.0.0.1"
        "http://137.0.0.1:8080"
    ],
    allow_creentials=True,       #允许使用证书
    allow_methods=['*'],         #允许跨域的方法,gat\post "*"允许全部
    allow_headers=['*']          #允许的请求头

)


# 跨域资源共享
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://127.0.0.1",
        "http://127.0.0.1:8080"
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

后台任务

from typing import Optional

from fastapi import APIRouter, BackgroundTasks, Depends

app08 = APIRouter()
def bg_task(framework: str):
    with open("README.md", mode="a") as f:       #写入文件, 追加格式
        f.write(f"## {framework} 框架精讲")   #写入的内容


@app08.post("/background_tasks")
async def run_bg_task(framework: str, background_tasks: BackgroundTasks):
    """
    :param framework: 被调用的后台任务函数的参数
    :param background_tasks: FastAPI.BackgroundTasks
    :return:
    """
    background_tasks.add_task(bg_task, framework)       #.add_task添加任务            bg_task 定义的bg任务
    return {"message": "任务已在后台运行"}

#依赖注入
def continue_write_readme(background_tasks: BackgroundTasks, q: Optional[str] = None):
    if q:
        background_tasks.add_task(bg_task, "\n> 整体的介绍 FastAPI,快速上手开发,结合 API 交互文档逐个讲解核心模块的使用\n")          #bg_task 定义的bg任务
    return q


@app08.post("/dependency/background_tasks")
async def dependency_run_bg_task(q: str = Depends(continue_write_readme)):
    if q:
        return {"message": "README.md更新成功"}

测试用例

from fastapi.testclient import TestClient

from run import app

"""Testing 测试用例"""

client = TestClient(app)  # 先pip install pytest

def test_run_bg_task():  # 函数名用“test_”开头是 pytest 的规范。注意不是async def
    response = client.post(url="/chapter08/background_tasks?framework=FastAPI")
    assert response.status_code == 200
    assert response.json() == {"message": "任务已在后台运行"}


def test_dependency_run_bg_task():
    response = client.post(url="/chapter08/dependency/background_tasks")
    assert response.status_code == 200
    assert response.json() is None


def test_dependency_run_bg_task_q():
    response = client.post(url="/chapter08/dependency/background_tasks?q=1")
    assert response.status_code == 200
    assert response.json() == {"message": "README.md更新成功"}

运行方式:

在终端运行cd 项目名称

+pytest

2. pydantic

pydantic库是一种常用的用于数据接口schema定义与检查的库。

通过pydantic库,我们可以更为规范地定义和使用数据接口,这对于大型项目的开发将会更为友好。

当然,除了pydantic库之外,像是valideer库、marshmallow库、trafaret库以及cerberus库等都可以完成相似的功能,但是相较之下,pydantic库的执行效率会更加优秀一些。

因此,这里,我们仅针对pydantic库来介绍一下如何规范定义标准schema并使用。

pydantic库用法考察

  1. 基本使用方法
  2. schema基本定义方法
    pydantic库的数据定义方式是通过BaseModel类来进行定义的,所有基于pydantic的数据类型本质上都是一个BaseModel类,它最基本的使用方式如下:
from pydantic import BaseModel

class Person(BaseModel):
    name: str
  1. 基本的schema实例化方法
    调用时,我们只需要对其进行实例化即可,实例化方法有以下几种:

1.直接传值

p = Person(name="Tom")
print(p.json()) # {"name": "Tom"}

2.通过字典传入

p = {"name": "Tom"}
p = Person(**p)
print(p.json()) # {"name": "Tom"}

3.通过其他的实例化对象传入

p2 = Person.copy(p)
print(p2.json()) # {"name": "Tom"}

当传入值错误的时候,pydantic就会抛出报错,例如:

Person(person="Tom")

pydantic会抛出异常:

ValidationError: 1 validation errors for Person
name
  field required (type=value_error.missing)

另一方面,如果传入值多于定义值时,BaseModel也会自动对其进行过滤。如:

p = Person(name="Tom", gender="man", age=24)
print(p.json()) # {"name": "Tom"}

可以看到,额外的参数gender与age都被自动过滤了。

通过这种方式,数据的传递将会更为安全,但是,同样的,这也要求我们在前期的schema定义中必须要尽可能地定义完全。

此外,pydantic在数据传输时会直接进行数据类型转换,因此,如果数据传输格式错误,但是可以通过转换变换为正确的数据类型是,数据传输也可以成功,例如:

p = Person(name=123)
print(p.json()) # {"name": "123"}

pydantic基本数据类型

下面,我们来看一下pydantic中的一些常用的基本类型。

from pydantic import BaseModel
from typing import Dict, List, Sequence, Set, Tuple

class Demo(BaseModel):
    a: int # 整型
    b: float # 浮点型
    c: str # 字符串
    d: bool # 布尔型
    e: List[int] # 整型列表
    f: Dict[str, int] # 字典型,key为str,value为int
    g: Set[int] # 集合
    h: Tuple[str, int] # 元组

# 使用 Optional[] 表示可能为 None 的值

x: Optional[str] = some_function()

具有任意类型的动态类型值

  • Union

    联合类型

  • Optional

    参数可以为空或已经声明的类型

  • Mapping

    映射,是 collections.abc.Mapping 的泛型

  • MutableMapping

    Mapping 对象的子类,可变

  • Generator

    生成器类型, Generator[YieldType、SendType、ReturnType]

  • NoReturn

    函数没有返回结果

  • Set

    集合 set 的泛型, 推荐用于注解返回类型

  • AbstractSet

    collections.abc.Set 的泛型,推荐用于注解参数

  • Sequence

    collections.abc.Sequence 的泛型,list、tuple 等的泛化类型

  • TypeVar

    自定义兼容特定类型的变量

  • NewType

    声明一些具有特殊含义的类型

  • Callable

    可调用类型, Callable[[参数类型], 返回类型]

代码

#导入库时一定要注意大小写

from pydantic import BaseModel #可在运行时提供代码类型提示,报错是提供较为友好的错误提示
from datetime import datetime
from typing import List,Optional #Optional 参数可以为空或已经声明的类型

# -*- codeing = utf-8 -*-
# @time : 2021/5/6 22:55
# @file : pydantic.PY
# @Author : 夜羽
# @Software : PyCharm
#导入库时一定要注意大小写
from pydantic import BaseModel   #可在运行时提供代码类型提示,报错是提供较为友好的错误提示
from datetime import datetime
from typing import List,Optional        #Optional 参数可以为空或已经声明的类型

class user(BaseModel):
    id: int
    name: str = "john snow"     #默认值
    signup_ts: Optional[datetime] = None      #Optional 参数可以为空或已经声明的类型
    friends: list[int] = []    #列表中元素是int类型或者是可以直接转化为int 类型的值

date = {
    "id" : "123456",
    "name" : 'nima',
    "friends" :[1,'2',3]
}
user = user(**date)  # **的作用是收集关键字参数到一个新的字典,并将整个字典赋值给字典_date
print(user.id,user.name)  #实例化后调用属性
print(user.json())    #字典形式打开
print(user.id)          #实例化后调用属性
print(user.dict())       #字典形式打开
print(user.schema())
print(user.schema_json())     #会提示使用的数据格式类型
print(user.__fields__.keys())     #查看定义的类型,所有的字段都表明类就不会乱


"""
Path = Path('pydanti.json')         #新建一个jsom文件
Path.write_text('{"id" : "123456","name" : "nima","signup_ts" : datetime.today(),friends" :[1,"2",3]}')
print(user.parse_file(Path))     #解析文件 parse_解析

#写入内容 write_text写入

#现在的时间"


User_data = {'{"id" : "123","name" : "nima","signup_ts" : datetime.today(),friends" :[1,"2",3]}'}
print(user.construct(**User_data))
"""

class 嵌套调用

from pydantic import BaseModel, ValidationError  #可在运行时提供代码类型提示,报错是提供较为友好的错误提示
from datetime import datetime, date
from pathlib import Path         #新建Path文件
from typing import List, Optional        #Optional 参数可以为空或已经声明的类型



class sound(BaseModel):
    sound: str

class dog(BaseModel):
    birthday: Optional[datetime] = None
    weight: float = Optional[None]
    sound: List[sound]             # 不同的叫声. 递归模型,就是指一个嵌套一个


dogs = dog(birthday=datetime.today(), weight=6.66, sound=[{"sound": "wangwagn"}, {"sound": "yingying"}])
print(dogs.dict())

3.SQLAlchemy - Column详解

SQLAlchemy - Column详解

Column常用参数:

primary_key:设置某个字段为主键。
autoincrement:设置这个字段为自动增长的。
default:设置某个字段的默认值。在发表时间这些字段上面经常用。
nullable:指定某个字段是否为空。默认值是True,就是可以为空。
unique:指定某个字段的值是否唯一。默认是False。
onupdate:在数据更新的时候会调用这个参数指定的值或者函数。在第一次插入这条数据的时候,不会用onupdate的值,只会使用default的值。常用的就是update_time(每次更新数据的时候都要更新的值)。
name:指定ORM模型中某个属性映射到表中的字段名。如果不指定,那么会使用这个属性的名字来作为字段名。如果指定了,就会使用指定的这个值作为参数。这个参数也可以当作位置参数,在第1个参数来指定。
comment:设置该字段的注释

sqlalchemy常用数据类型:

Integer:整形
Float:浮点类型
Boolean:传递True/False
DECIMAL:定点类型
enum:枚举类型
Date:传递datetime.date()进去
Time:传递datatime.time()
String:字符类型,使用时需要指定长度,区别于Text类型
Text:文本类型
LONGTEXT:长文本类型
query可以参数:

Integer:整形,映射到数据库中是int类型。

Float:浮点类型,映射到数据库中是float类型。他占据的32位。

Double:双精度浮点类型,映射到数据库中是double类型,占据64位。

String:可变字符类型,映射到数据库中是varchar类型.

Boolean:布尔类型,映射到数据库中的是tinyint类型。

DECIMAL:定点类型。是专门为了解决浮点类型精度丢失的问题的。在存储钱相关的字段的时候建议大家都使用这个数据类型。并且这个类型使用的时候需要传递两个参数,第一个参数是用来标记这个字段总能能存储多少个数字,第二个参数表示小数点后有多少位。

Enum:枚举类型。指定某个字段只能是枚举中指定的几个值,不能为其他值。在ORM模型中,使用Enum来作为枚举,示例代码如下:

聚合函数:

func.count:统计行的数量
func.avg:求平均值
func.max:求最大值
func.min:求最小值
func.sum:求和

result = session.query(func.count(User.id)).all()
print(result)

resultavg = session.query(func.avg(User.price)).all()
print(resultavg)

resultmax = session.query(func.max(User.price)).all()
print(resultmax)

resultmin = session.query(func.min(User.price)).all()
print(resultmin)

resultsum = session.query(func.sum(User.price)).all()
print(resultsum)

过滤方法:
过滤是数据 提取的一个很重要的功能,以下对一些常用的过滤条件进行详解,并且这些过滤条件都是只能通过filter方法实现的:

1.equals:
query.filter(User.name == ‘ed’)

2.not equals:
query.filter(User.name != ‘ed’)

3.like:
query.filter(User.name.like(’%ed%’))

4.in:
query.filter(User.name.in_([‘ed’,‘wendy’,‘jack’]))
#同时
query.filter(User.name.in_(session.query(User.name).filter(User.name.like(’%ed%’))))

5.not in:
query.filter(~User.name.in_(‘ed’,‘wendy’,‘jack’))

6.is null:
query.filter(User.name==None)
query.filter(User.name.is_(None))

7.is not null:
query.filter(User.name != None)
query.filter(User.name.isnot(None)

8.and:
from sqlalchemy import and_
query.filter(and_(User.name==‘ed’, User.fullname==‘Ed Jones’))
或者
query.filter(User.name==‘ed’, User.fullname==‘Ed Jones’)
或者
query.filter(User.name==‘ed’,).filter(User.fullname==‘Ed Jones’)
复制代码

9.or:
from sqlalchemy import or_
query.filter(or_(User.name=‘ed’, User.name=‘wendy’)

3.1mysql的连接

参考文档作者奋斗吧,青年!

文档连接:(3条消息) Python FastAPI 框架 操作 MySQL 数据库_lilygg的博客-CSDN博客

参考视频:FastAPI教程第二季(四):数据库(ORM+SQLite)(最快python异步并发web框架之一)_哔哩哔哩_bilibili

数据库表的连接

# -*- coding = utf-8 -*-
# @Time : 2021/5/31 22:49
# @File : mysqldb.PY
# @Author : 夜羽
# @Software: PyCharm
from sqlalchemy import Boolean, Column, Integer, String, DateTime
from sqlalchemy import Column, Integer, String
# 创建数据库模型(定义表结构:表名称,字段名称以及字段类型)
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base   #定义映射
from sqlalchemy.orm import sessionmaker      #建立会话
import pymysql
from fastapi import FastAPI, Request, Request
import datetime
import uvicorn

app = FastAPI()
#app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:3306/school'
#DB_CONNECT = 'mysql+pymysql://root:102@localhost/tset'
#engine = create_engine('mysql+pymysql://root:root@localhost:3306/school')       #用户名\密码\数据库的地址\端口号\数据库名称
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@localhost:3306/school'
# 生成一个SQLAlchemy引擎
engine = create_engine(SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
# 生成sessionlocal类--工厂,这个类的每一个实例都是一个数据库的会话
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
session = SessionLocal()
Base = declarative_base()
# Base是用来给模型类继承的,类似django中的models.Model

#以上基本为固定

# 模型类,tablename指表名,如果数据库中没有这个表会自动创建,有表则会沿用
"""
class order_order(Base):         #申明数据库某表的属性结构
    __tablename__ = "order_order"
    order_id = Column(String(18), primary_key=True, index=True)
    state_type = Column(Integer, default=1)
    submit_time = Column(DateTime)
    cus_phone = Column(String(11))
    cabinet_id = Column(String(4))
    Base.metadata.create_all(bind=engine)
"""


class User(Base):
    # 定义表名
    __tablename__ = 'tbluser'
    # 定义字段
    # primary_key=True 设置为主键
    userid = Column(Integer, primary_key=True)
    username = Column(String(255))

    # 构造函数
    def __init__(self, userid, username):
        self.userid = userid
        self.username = username

    # 打印形式
    def __str__(self):
        return "id:%s, name:%s" % (str(self.userid), self.username)


# 在数据库中生成表
Base.metadata.create_all(bind=engine)

练习

"""↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓    数据库操作(依赖项)    ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"""
def get_db():
    try:
        db = SessionLocal() # 这时,才真正产生一个'会话',并且用完要关闭
        yield db            # 生成器
    finally:
        db.close()
        print('数据库已关闭')


"""↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓    数据库操作方法    ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"""
# 通过id查询用户信息
def get_user(db: Session, user_id: int):
    CCCCCC = db.query(M_User).filter(M_User.id == user_id).first()
    print(CCCCCC)           # 过滤器
    return CCCCCC

# 新建用户(数据库)
def db_create_user(db: Session, user: UserCreate):
    fake_hashed_password = user.password + "notreallyhashed"
    db_user = M_User(email=user.email, hashed_password=fake_hashed_password)
    db.add(db_user)
    db.commit()     # 提交即保存到数据库
    db.refresh(db_user) # 刷新
    return db_user

"""↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓    post和get请求    ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"""
# 新建用户(post请求)
@app.post("/users/", response_model=User)
def create_user(user: UserCreate, db: Session = Depends(get_db)):
    # Depends(get_db)使用依赖关系可防止不同请求意外共享同一链接
    return db_create_user(db=db, user=user)

# 用ID方式读取用户
@app.get("/users/{user_id}", response_model=User)
def read_user(user_id: int, db: Session = Depends(get_db)):
    db_user = get_user(db, user_id=user_id)
    print(db_user)
    if db_user is None:
        raise HTTPException(status_code=404, detail="User not found")

    return db_user


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

3.2数据库的添加

# 定义数据模型
class CreatUser(BaseModel):
    userid: int
    username: str

## 添加单个
@app.post("/user/addUser")
async def InserUser(user: CreatUser):
    try:
        # 添加数据
        dataUser = User(userid=user.userid, username=user.username)
        session.add(dataUser)
        session.commit()
        session.close()
    except ArithmeticError:
        return {"code": "0002", "message": "数据库异常"}
    return {"code": "0000", "message": "添加成功"}


## 添加多个
@app.post("/user/addUserList")
async def addUserList(*, user: List[CreatUser]):
    try:
        # user是一个列表,每个内部元素均为CreatUser类型
        for u in user:
            # 自定义的数据模型可以用.访问属性
            dataUser = User(userid=u.userid, username=u.username)
            session.add(dataUser)
        session.commit()
        session.close()
    except ArithmeticError:
        return {"code": "0002", "message": "数据库异常"}
    return {"code": "0000", "message": "添加成功"}


if __name__ == '__main__':
    uvicorn.run('mysqldb:app', host="127.0.0.1", port=8000, reload=True, debug=True, workers=1)
#woker=1进程数量

code

[
  {
    "userid": 4,
    "username": "老马"
  },
  {
    "userid": 5,
    "username": "小马"
  }
]

3.3查询

查询数据

one与first的区别
one:要求结果集中只有一个结果;如果数据库返回0或2个或更多结果,并且将引发异常,则为错误。
first:返回可能更大的结果集中的第一个,如果没有结果,则返回None。不会引发异常。
filter_by与filter的区别
filter_by接收的参数形式是关键字参数,而filter接收的参数是更加灵活的SQL表达式结构

## 按照user_id查询

@app.get("/user/{user_id}")
async def queryUserByUserId(user_id: int):
    # 创建Query查询,filter是where条件,调用one返回唯一行,调用all则是返回所有行
    #filter_by接收的参数形式是关键字参数,而filter接收的参数是更加灵活的SQL表达式结构
          #会以.查询(定义名称表的).where(表中.数据==输入的).结果集
    try:
        # user1 = session.query(User).filter_by(userid=user_id).first()
        # user1 = session.query(User).filter(User.userid==user_id).one()
        user1 = session.query(User).filter(User.userid==user_id).first()
        session.close()
        # 由于user1只有一个值,所以它直接是一个字典
        if user1:
            return {"code":"0000","message":"请求成功","data":user1}
        else:
            return {"code":"0001","message":"查询无结果"}
    except ArithmeticError:

查询全部

## 查询所有
@app.get("/user/selectall/")
async def queryUserByUserId():
    # 创建Query查询,filter是where条件,调用one返回唯一行,调用all则是返回所有行
    try:
        user1 = session.query(User).all()
        session.close()
        # user1 是一个列表,内部元素为字典
        return {"code": "0000", "message": "请求成功", "data": user1}
    except ArithmeticError:
            return {"code":"0002","message":"数据库异常"}

3.4删除

删除命令

方法1

@app.delete("/user/deleteUser/{user_id}")
async def deleteUser(user_id: int):
    try:
        user1 = session.query(User).filter(User.userid == user_id).first()
        if user1:
            session.delete(user1)
            session.commit()
            session.close()
            return {"code": "0000", "message": "删除成功"}
        else:
            return {"code": "0001", "message": "参数错误"}
    except ArithmeticError:
        return {"code": "0002", "message": "数据库错误"}

方法2

本质上与方法1没区别

class AlterUser(BaseModel):
    userid: int
    username: str


@app.put("/user/updateUser01/")
async def deleteUser(user: AlterUser):
    try:
        user1 = session.query(User).filter(User.userid == user.userid).first()
        if user1:
            user1.username = user.username
            session.commit()
            session.close()
            return {"code": "0000", "message": "修改成功"}
        else:
            return {"code": "0001", "message": "参数错误"}
    except ArithmeticError:
        return {"code": "0002", "message": "数据库错误"}

3.5完整版

# -*- coding = utf-8 -*-
# @Time : 2021/5/31 22:49
# @File : mysqldb.PY
# @Author : 夜羽
# @Software: PyCharm
from sqlalchemy import Boolean, Column, Integer, String, DateTime
from sqlalchemy import Column, Integer, String
# 创建数据库模型(定义表结构:表名称,字段名称以及字段类型)
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base   #定义映射
from sqlalchemy.orm import sessionmaker,Session    #建立会话
import pymysql
from fastapi import FastAPI, Request, Request, Depends, HTTPException
import datetime
import uvicorn
from pydantic import BaseModel
from typing import List


app = FastAPI()
#app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:root@localhost:3306/school'
#DB_CONNECT = 'mysql+pymysql://root:102@localhost/tset'
#engine = create_engine('mysql+pymysql://root:root@localhost:3306/school')       #用户名\密码\数据库的地址\端口号\数据库名称
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@localhost:3306/school'
# 生成一个SQLAlchemy引擎
engine = create_engine(SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
# 生成sessionlocal类--工厂,这个类的每一个实例都是一个数据库的会话
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
session = SessionLocal()
Base = declarative_base()
# Base是用来给模型类继承的,类似django中的models.Model

# 模型类,tablename指表名,如果数据库中没有这个表会自动创建,有表则会沿用
"""
class order_order(Base):         #申明数据库某表的属性结构
    __tablename__ = "order_order"
    order_id = Column(String(18), primary_key=True, index=True)
    state_type = Column(Integer, default=1)
    submit_time = Column(DateTime)
    cus_phone = Column(String(11))
    cabinet_id = Column(String(4))
    Base.metadata.create_all(bind=engine)
"""


class User(Base):
    # 定义表名
    __tablename__ = 'tbluser'
    # 定义字段
    # primary_key=True 设置为主键
    userid = Column(Integer, primary_key=True)
    username = Column(String(255))



    # 构造函数
    def __init__(self, userid, username):
        self.userid = userid
        self.username = username

    # 打印形式
    def __str__(self):
        return "id:%s, name:%s" % (str(self.userid), self.username)


# 在数据库中生成表
Base.metadata.create_all(bind=engine)


# 定义数据模型
class CreatUser(BaseModel):
    userid: int
    username: str

## 添加单个
@app.post("/user/addUser")
async def InserUser(user: CreatUser):
    try:
        # 添加数据
        #数据库定义中(定义名称=赋值后.id, 赋值后.name)
        dataUser = User(userid=user.userid, username=user.username)
        session.add(dataUser)   #添加
        session.commit()     #提交会提
        session.close()         #关闭
    except ArithmeticError:
        return {"code": "0002", "message": "数据库异常"}
    return {"code": "0000", "message": "添加成功"}



## 添加多个
@app.post("/user/addUserList")
async def addUserList(*, user: List[CreatUser]):
    try:
        # user是一个列表,每个内部元素均为CreatUser类型
        for u in user:
            # 自定义的数据模型可以用.访问属性
            dataUser = User(userid=u.userid, username=u.username)
            session.add(dataUser)
        session.commit()
        session.close()
    except ArithmeticError:
        return {"code": "0002", "message": "数据库异常"}
    return {"code": "0000", "message": "添加成功"}


## 按照user_id查询
@app.get("/user/{user_id}")
async def queryUserByUserId(user_id: int):
    # 创建Query查询,filter是where条件,调用one返回唯一行,调用all则是返回所有行
    #filter_by接收的参数形式是关键字参数,而filter接收的参数是更加灵活的SQL表达式结构
                   #会以.查询(定义名称表的).where(表中.数据==输入的).结果集
    try:
        # user1 = session.query(User).filter_by(userid=user_id).first()
        # user1 = session.query(User).filter(User.userid==user_id).one()
        user1 = session.query(User).filter(User.userid==user_id).first()
        session.close()
        # 由于user1只有一个值,所以它直接是一个字典
        if user1:
            return {"code": "0000", "message": "请求成功", "data": user1}
        else:
            return {"code":"0001","message":"查询无结果"}
    except ArithmeticError:
            return {"code":"0002","message":"数据库异常"}


## 查询所有
@app.get("/user/selectall/")
async def queryUserByUserId():
    # 创建Query查询,filter是where条件,调用one返回唯一行,调用all则是返回所有行
    try:
        user1 = session.query(User).all()
        session.close()
        # user1 是一个列表,内部元素为字典
        return {"code": "0000", "message": "请求成功", "data": user1}
    except ArithmeticError:
            return {"code":"0002","message":"数据库异常"}


@app.delete("/user/deleteUser/{user_id}")
async def deleteUser(user_id: int):
    try:
        user1 = session.query(User).filter(User.userid == user_id).first()
        if user1:
            session.delete(user1)
            session.commit()
            session.close()
            return {"code": "0000", "message": "删除成功"}
        else:
            return {"code": "0001", "message": "参数错误"}
    except ArithmeticError:
        return {"code": "0002", "message": "数据库错误"}





# 删除多个
@app.delete("/user/deleteUserList")
async def deleteUser(user_ids: List[int]):
    try:
        for user_id in user_ids:
            user1 = session.query(User).filter(User.userid == user_id).first()
            if user1:
                session.delete(user1)
                session.commit()
                session.close()
        return {"code": "0000", "message": "删除成功"}
    except ArithmeticError:
        return {"code": "0002", "message": "数据库错误"}







## 根据user_id修改user_name
@app.put("/user/updateUser/")
# 定义查询参数user_id和user_name
async def deleteUser(user_id: int, user_name: str):
    try:
        user1 = session.query(User).filter(User.userid == user_id).first()
        if user1:
            user1.username = user_name
            session.commit()
            session.close()
            return {"code": "0000", "message": "修改成功"}
        else:
            return {"code": "0001", "message": "参数错误"}
    except ArithmeticError:
        return {"code": "0002", "message": "数据库错误"}


class AlterUser(BaseModel):
    userid: int
    username: str


@app.put("/user/updateUser01/")
async def deleteUser(user: AlterUser):
    try:
        user1 = session.query(User).filter(User.userid == user.userid).first()
        if user1:
            user1.username = user.username
            session.commit()
            session.close()
            return {"code": "0000", "message": "修改成功"}
        else:
            return {"code": "0001", "message": "参数错误"}
    except ArithmeticError:
        return {"code": "0002", "message": "数据库错误"}


if __name__ == '__main__':
    uvicorn.run('mysqldb:app', host="127.0.0.1", port=8000, reload=True, debug=True, workers=1)
#woker=1进程数量

3.6返回对象转化成python字典

固定

方法1:

#对象转化成python字典
def class_to_dict(obj):
    is_list = obj.__class__ == [].__class__
    is_set = obj.__class__ == set().__class__
    if is_list or is_set:
        obj_arr = []
        for o in obj:
            dict = {}
            a = o.__dict__
            if "_sa_instance_state" in a:
                del a['_sa_instance_state']
            dict.update(a)
            obj_arr.append(dict)
        return obj_arr
    else:
        dict = {}
        a = obj.__dict__
        if "_sa_instance_state" in a:
            del a['_sa_instance_state']
        dict.update(a)
        return dict

方法2:

加上了时间判断

def class_to_dict_all(obj):
    """
    例如 GlobalRegion.query.all()
    :param obj:
    :return:
    """
    is_list = obj.__class__ == [].__class__
    is_set = obj.__class__ == set().__class__
    if is_list or is_set:
        obj_arr = []
        for o in obj:
            for k, v in vars(o).items():      #时间
                if isinstance(v, datetime.datetime):
                    o.__dict__[k] = v.strftime('%Y-%m-%d %H:%M:%S')   
            dict = {}
            a = o.__dict__
            if "_sa_instance_state" in a:
                del a['_sa_instance_state']
            dict.update(a)
            obj_arr.append(dict)
        return obj_arr
    else:
        dict = {}
        for k, v in vars(obj).items():
            if isinstance(v, datetime.datetime):
                obj.__dict__[k] = v.strftime('%Y-%m-%d %H:%M:%S')
        a = obj.__dict__
        if "_sa_instance_state" in a:
            del a['_sa_instance_state']
        dict.update(a)
        return dict

输出

user1 = session.query(data).all()
print(class_to_dict(user1))
session.close()

datetime

datetime.now() 和 datetime.utcnow()

from datetime import datetime, timedelta
>>> from datetime import datetime
>>> now_time = datetime.now()
>>> utc_time = datetime.utcnow()
>>> now_time
datetime.datetime(2019, 4, 12, 14, 14, 48, 155744)
>>> utc_time
datetime.datetime(2019, 4, 12, 6, 15, 9, 989156)

utcnow():读取的时间一直都是系统的“世界标准时间”,不管系统的本地时区是否设置,读取的时间不会随这些设置变化;
now():读取的时间是系统的本地时间,也就是说,如果系统时区默认没有设置,那么读取的就是世界标准

对象转化成python字典

4.渲染网页

1.新建static文件夹下,新建images文件夹,新建图片一张
2.新建templates文件夹,新建index.html find.html login.html文件
3.新建app.py文件

index.html

<html lang="en">
<head>
head>
<body>
<a href="{{ url_for('login') }}">logina>
<br>
<a href="{{ url_for('find') }}">finda>
<img src="{{ url_for('static', filename='images/20180819002220436.png') }}">
body>
html>
123456789101112
login.html
<h1>  ============= loginh1>
find.html
<h1>  ============= findh1>



#-*- coding:utf-8 -*-
from flask import Flask, render_template
 
app = Flask(__name__)
@app.route("/")
def index():
    return render_template("index.html")
 
@app.route("/login/")
def login():
    return render_template("login.html")

@app.route("/find/")
def find():
    return render_template("find.html")
 
if __name__ == "__main__":
    app.run(debug=True)

5.字典

数据获得转为字典数据

#对象转化成python字典
def class_to_dict_all(obj):
    """
    例如 GlobalRegion.query.all()
    :param obj:
    :return:
    """
    is_list = obj.__class__ == [].__class__
    is_set = obj.__class__ == set().__class__
    if is_list or is_set:
        obj_arr = []
        for o in obj:
            for k, v in vars(o).items():
                if isinstance(v, datetime.datetime):
                    o.__dict__[k] = v.strftime('%Y-%m-%d %H:%M:%S')
            dict = {}
            a = o.__dict__
            if "_sa_instance_state" in a:
                del a['_sa_instance_state']
            dict.update(a)
            obj_arr.append(dict)
        return obj_arr
    else:
        dict = {}
        for k, v in vars(obj).items():
            if isinstance(v, datetime.datetime):
                obj.__dict__[k] = v.strftime('%Y-%m-%d %H:%M:%S')
        a = obj.__dict__
        if "_sa_instance_state" in a:
            del a['_sa_instance_state']
        dict.update(a)
        return dict

调用方法

try:
    user1 = session.query(data).all()
    user2 = class_to_dict_all(user1)  #转为字典类型
    session.close()
    # user1 是一个列表,内部元素为字典
    return templates.TemplateResponse('ui.html', {'request': request, "code": "0000", "message": "刷新成功", "data": user2})
except ArithmeticError:
        return {"code":"0002","message":"数据库异常"}

6.图片保存

图片保存测试

            
   

fastapi表单获取保存并且输出方式:

async def adduser(request: Request,
                    name2: str= Form(...),
                    xx: str = Form(...),
                    textfield: str = Form(...),
                    files_list: UploadFile = File(...)):  #可以为空
    start = time.time()
    try:
        res = await files_list.read()
        # 第一个参数 文件存储路径+文件名称,存储路径目录需要提前创建好,如果没有指定,则默认会保存在本文件的同级目录下
        # 第二个参数 wb,表示以二进制格式打开文件,用于只写
        with open("static/file//" + files_list.filename, "wb") as f:
            f.write(res)
            #   ../static/file/2.jpg
            # src="../file/313a57f502f3429182bd8cac060aa004.png"
            img = '../static/file/' + files_list.filename  # 数据库图片保存地址
            print(img)
        # 添加数据
        #数据库定义中(定义名称=赋值后.id, 赋值后.name)
        dataUser = data(label=xx, user_name=name2, content=textfield, img=img)
        session.add(dataUser)  # 添加
        session.commit()  # 提交会提
        session.close()  # 关闭
        return templates.TemplateResponse('ui.html', {'request': request, "code": "0000", "message": "添加成功" , "username1": name2, 'time': time.time() - start, 'filename': files_list.filename})
    except ArithmeticError:
        return templates.TemplateResponse('fail.html', {'request': request, "code": "0002", "message": "数据库异常"})
    

具体实现:

files_list: UploadFile = File(...)):  #可以为空
    start = time.time()
    try:
        res = await files_list.read()
        # 第一个参数 文件存储路径+文件名称,存储路径目录需要提前创建好,如果没有指定,则默认会保存在本文件的同级目录下
        # 第二个参数 wb,表示以二进制格式打开文件,用于只写
        with open("static/file//" + files_list.filename, "wb") as f:
            f.write(res)
            #   ../static/file/2.jpg
            # src="../file/313a57f502f3429182bd8cac060aa004.png"
            img = '../static/file/' + files_list.filename  # 数据库图片保存地址
            print(img)
        # 添加数据
        #数据库定义中(定义名称=赋值后.id, 赋值后.name)
        dataUser = data(label=xx, user_name=name2, content=textfield, img=img)
        session.add(dataUser)  # 添加
        session.commit()  # 提交会提
        session.close()  # 关闭

web前端实现:

<td style="height:137px; width:148px">
   <div style="height:100%;width:100%;overflow:auto">
        <img alt="" src="{{ d.img }}" style="overflow:scroll;" />
   div>
td>



前端

7.返回图片定时返回信息

from fastapi.responses import JSONResponse, HTMLResponse, FileResponse #返回json, html, 文件

from fastapi.responses import JSONResponse, HTMLResponse, FileResponse #返回json, html, 文件
#返回一个前端文件
@app.get("/responses")
async def responses():
    html_content = """
     
    内容
    
    """
    return HTMLResponse(content=html_content)


#返回一个图片/下载图片
@app.get("/img")
async def img():
    avatar = "static/img/1.png"
    return FileResponse(avatar, filename="1.png") #下载图片
      #return FileResponse(avatar)   #只显示预判不下载

8.超链接渲染

#超链接渲染
@app.route("/12138宠物网/img")
async def index(request: Request):
        return templates.TemplateResponse("index.html", {'request': request})

@app.route("/docs")
async def jm(request: Request):
        return templates.TemplateResponse("index.html", {'request': request})

9.FastAPI 定制Response

默认情况下,FastAPI会基于*JSONResponse*来返回Response。

如果我们直接返回*Response*,数据格式不会被自动转换,并且交互式文档也不会自动生成。

下面是一些常用的Response类型。

Response

Response主类,所有其他的Response都继承自这个类。

它接收以下参数信息:

  • content - str 或者 bytes.
  • status_code - HTTP 状态码.
  • headers - 字符串字典.
  • media_type - media type. 例如"text/html".

FastAPI会自动包含Content-Length,以及Content-Type,charset等头信息。

from fastapi import FastAPI, Response

app = FastAPI()


@app.get("/legacy/")
def get_legacy_data():
    data = """
    
    
Apply shampoo here.
You'll have to use soap here.
""" return Response(content=data, media_type="application/xml")

我们可以在路径操作装饰器中声明要返回的具体*Response类型,返回的内容会被放入到指定的Response*

并且如果我们指定的*Response*支持JSON media类型,例如*JSONResponse*或者*UJSONResponse*,那么返回的数据就会被自动转换成Pydantic模型(通过*response_model*指定)。

HTMLResponse

接收文本或者字节内容,返回HTML reponse。

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return """
    
        
            Some HTML in here
        
        
            

Look ma! HTML!

"""
PlainTextResponse

接收文本或者字节内容,返回纯文本 reponse。

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()


@app.get("/", response_class=PlainTextResponse)
async def main():
    return "Hello World"
JSONResponse

返回application/json格式的response。FastAPI默认返回的response。

RedirectResponse重定向

返回HTTP重定向。默认状态码为307(临时重定向)。

#添加操作后直接返回前一个路径
from fastapi.responses import RedirectResponse

from fastapi.responses import RedirectResponse  #重定向响应
datas = ["学习数据结构", "学习高数", "学习英语"]

@app.get("/")
def data(request: Request):
    return templates.TemplateResponse("3.html", {"request": request, "data": datas})

@app.post("/data")
def add(data=Form(None)):
    datas.insert(0, data)
    return RedirectResponse("http://127.0.0.1:8000/", status_code=302)     #重定向响应路径,需要注意前一个路径的状态码( POST请求到get请求建议使用302状态码 )


# @app.get("/typer")
# async def read_typer():
#     return RedirectResponse("http://127.0.0.1:8000")
StreamingResponse

接收一个异步的发生器或者普通的发生器/枚举器,对返回结果流式输出。

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


async def fake_video_streamer():
    for i in range(10):
        yield b"some fake video bytes"


@app.get("/")
async def main():
    return StreamingResponse(fake_video_streamer())

如果我们有一个file-like对象(比如用open()打开),我们就可以用*StreamingResponse*来返回该对象。

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/")
def main():
    file_like = open(some_file_path, mode="rb"return StreamingResponse(file_like, media_type="video/mp4")
FileResponse

异步流式输出一个文件。

不同于其他response的初始化参数信息:

  • path - 文件路径
  • headers - 定制头信息,字典格式
  • media_type - media type,如果没有设置则会根据文件名或文件路径来推断media type
  • filename - 文件名。如果设置,会被包含到response的Content-Disposition中

文件response会包含合适的Content-Length, Last-Modified 以及 ETag 头信息内容。

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/")
async def main():
    return FileResponse(some_file_path)
xt/html"`.

FastAPI会自动包含Content-Length,以及Content-Type,charset等头信息。

from fastapi import FastAPI, Response

app = FastAPI()

@app.get("/legacy/")
def get_legacy_data():
data = “”"


Apply shampoo here.


You’ll have to use soap here.


“”"
return Response(content=data, media_type=“application/xml”)




我们可以在路径操作装饰器中声明要返回的具体*`Response`*类型,返回的内容会被放入到指定的*`Response`*`中`。

并且如果我们指定的*`Response`*`支持JSON media类型,例如*`JSONResponse`*或者*`UJSONResponse`*,那么返回的数据就会被自动转换成Pydantic模型(通过*`response_model`*指定)。`

#### `HTMLResponse`

接收文本或者字节内容,返回HTML reponse。

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/items/", response_class=HTMLResponse)
async def read_items():
return “”"


Some HTML in here


Look ma! HTML!




“”"


#### `PlainTextResponse`

接收文本或者字节内容,返回纯文本 reponse。

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()

@app.get("/", response_class=PlainTextResponse)
async def main():
return “Hello World”


#### `JSONResponse`

返回`application/json`格式的response。FastAPI默认返回的response。

#### `RedirectResponse`重定向

返回HTTP重定向。默认状态码为307(临时重定向)。

#添加操作后直接返回前一个路径
from fastapi.responses import RedirectResponse

from fastapi.responses import RedirectResponse #重定向响应
datas = [“学习数据结构”, “学习高数”, “学习英语”]

@app.get("/")
def data(request: Request):
return templates.TemplateResponse(“3.html”, {“request”: request, “data”: datas})

@app.post("/data")
def add(data=Form(None)):
datas.insert(0, data)
return RedirectResponse(“http://127.0.0.1:8000/”, status_code=302) #重定向响应路径,需要注意前一个路径的状态码( POST请求到get请求建议使用302状态码 )

@app.get("/typer")

async def read_typer():

return RedirectResponse(“http://127.0.0.1:8000”)


#### `StreamingResponse`

接收一个异步的发生器或者普通的发生器/枚举器,对返回结果流式输出。

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

async def fake_video_streamer():
for i in range(10):
yield b"some fake video bytes"

@app.get("/")
async def main():
return StreamingResponse(fake_video_streamer())


如果我们有一个file-like对象(比如用open()打开),我们就可以用*`StreamingResponse`*来返回该对象。

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

some_file_path = “large-video-file.mp4”
app = FastAPI()

@app.get("/")
def main():
file_like = open(some_file_path, mode="rb"return StreamingResponse(file_like, media_type=“video/mp4”)


#### `FileResponse`

异步流式输出一个文件。

不同于其他response的初始化参数信息:

-   `path` - 文件路径
-   `headers` - 定制头信息,字典格式
-   `media_type` - media type,如果没有设置则会根据文件名或文件路径来推断media type
-   `filename` - 文件名。如果设置,会被包含到response的`Content-Disposition中`

文件response会包含合适的`Content-Length`, `Last-Modified` 以及 `ETag` 头信息内容。

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = “large-video-file.mp4”
app = FastAPI()

@app.get("/")
async def main():
return FileResponse(some_file_path)

你可能感兴趣的:(fastapi,python,flask,后端)