Documentación en inglés: https://fastapi.tiangolo.com/
Documentación en chino: https://fastapi.tiangolo.com/zh/
¿Por qué el marco de Sanic que se está promocionando se atreve a afirmar que es el más fuerte? : https://zhuanlan.zhihu.com/p/360513398
Clasificación de marcos web: https://github.com/the-benchmarker/web-frameworks
1. Tutorial de API rápida
breve introducción
FastAPI, como Sanic, es un marco web asíncrono en Python. En comparación con Sanic, FastAPI es más maduro y la comunidad es más activa.
¿FastAPI de pie sobre los hombros de gigantes?
En gran medida, este gigante se refiere al marco Flask.
FastAPI es muy similar a Flask en términos de sintaxis y tiene el mismo propósito.
De hecho, no solo FastAPI, sino que incluso Sanic es un marco de API web basado en Flask para un desarrollo rápido.
FastAPI es un marco web moderno, rápido (de alto rendimiento) para crear API, que utiliza Python 3.6+ y se basa en sugerencias de tipo estándar de Python.
Tiene las siguientes ventajas:
- Rápido : rendimiento extremo comparable a NodeJS y Go (gracias a Starlette y Pydantic).
Starlette 用于路由匹配,Pydantic 用于数据验证
- Codificación eficiente : aumente la velocidad de desarrollo de funciones entre un 200 % y un 300 %
- Menos errores : alrededor de un 40 % menos de errores inducidos por humanos (desarrolladores).
- Inteligente : excelente compatibilidad con el editor. Autocompletado en todas partes, lo que reduce el tiempo de depuración
- Simple : diseñado para ser fácil de usar y aprender, menos tiempo dedicado a leer la documentación
- Corto : Minimiza la duplicación de código. Las funciones enriquecidas se realizan a través de diferentes declaraciones de parámetros. menos errores
- Robusto : Produce niveles utilizables de código. También hay documentación interactiva generada automáticamente.
- Estandarización : Basado en (y totalmente compatible con) los estándares abiertos relevantes de la API: OpenAPI (anteriormente conocido como Swagger) y JSON Schema .
La característica más importante de FastAPI es que utiliza anotaciones de tipo Python, y el uso de FastAPI requiere una versión de Python superior o igual a 3.6.
Instalar FastAPI
pip install fastapi instalará automáticamente Starlette y Pydantic; luego pip install uvicorn, porque uvicorn es el servidor que ejecuta las aplicaciones relacionadas. O vaya al estómago en un solo paso: pip install fastapi[all] instalará todas las dependencias.
Tutorial
Marco web de alto rendimiento de Python: guía completa de FastApi: https://zhuanlan.zhihu.com/p/397029492
El marco asíncrono más popular: https://www.cnblogs.com/tradicional/p/14733610.html
Tutorial del sitio web oficial: https://fastapi.tiangolo.com/zh/tutorial/
- primer paso
- parámetros de ruta
- parámetros de consulta
- cuerpo de solicitud
- Parámetros de consulta y validación de cadenas
- Parámetros de ruta y validación de valores
- cuerpo de solicitud - múltiples parámetros
- cuerpo de solicitud - campos
- cuerpo de solicitud - modelo anidado
- Información adicional para el esquema - ejemplo
- tipo de datos adicionales
- Parámetros de cookies
- Parámetros de encabezado
- modelo de respuesta
- modelos adicionales
- código de estado de respuesta
- datos del formulario
- archivo de solicitud
- Solicitud de Formularios y Documentos
- Manejar errores
- Configuración de operación de ruta
- Codificador compatible con JSON
- cuerpo de solicitud - actualizar datos
- software intermedio
- CORS (intercambio de recursos de origen cruzado)
- Base de datos SQL (relacional)
- Aplicaciones más grandes: varios archivos
- tarea de fondo
- URL de metadatos y documentación
- archivos estáticos
- prueba
- depuración
Guía de usuario avanzada: https://fastapi.tiangolo.com/zh/advanced/
- Configuración avanzada para operaciones de ruta
- código de estado adicional
- devolver la respuesta directamente
- Respuestas personalizadas: HTML, flujo, archivo y otros
- Adjuntar respuestas adicionales
- Cookies de respuesta
- encabezado de respuesta
- Respuesta - cambiar código de estado
- dependencias de alto nivel
- Utilice la solicitud directamente
- Uso de clases de datos
- software intermedio avanzado
- Bases de datos SQL (relacionales) y Peewee
- Base de datos SQL asíncrona (relacional)
- Bases de datos NoSQL (distribuidas/grandes datos)
- subaplicación - montaje
- interino
- plantilla
- GráficoQL
- WebSockets
- eventos del ciclo de vida
- Solicitud personalizada, clase APIRoute
- Probar WebSockets
- Eventos de prueba: inicio-apagado
- dependencias de prueba
- Base de datos de prueba
- prueba asíncrona
- Configuraciones y variables de entorno
- OpenAPI condicional
- Ampliación de OpenAPI
- Devoluciones de llamadas de OpenAPI
- Webhooks de OpenAPI
- Incluye WSGI - Flask, Django, otros
- Generar Clientes
Concurrencia, implementación, plantillas de proyectos de compilación
- Concurrencia, async/await
- Generación de Proyectos - Plantilla
- Selección, Inspiración y Comparación
- Historia, diseño y futuro
- Enlaces externos y artículos
- Puntos de referencia
Introducción a las sugerencias de tipos de Python
:https://fastapi.tiangolo.com/zh/python-types/
Anotaciones de tipo Python, todo lo que necesita saber está aquí: https://zhuanlan.zhihu.com/p/419955374
Mejores prácticas para atributos de anotación de objetos: https://docs.python.org/zh-cn/3/howto/ anotaciones.html
2. Uso de FastAPI
Ejemplo de uso sencillo
Ejemplo: código síncrono
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
Ejemplo: código asíncrono
Crear un nuevo archivo main.py
import os
import uvicorn
from pathlib import Path
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
if __name__ == '__main__':
# os.system('uvicorn main_test:app --reload')
uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
pass
FastAPI recomienda usar uvicorn para ejecutar el servicio, Uvicorn es un servidor ASGI ultrarrápido construido sobre uvloop y httptools.
El significado del comando uvicorn main:app es el siguiente:
- main: El archivo main.py (un "módulo" de Python).
- app: El objeto creado por app = FastAPI() en el archivo main.py.
- --reload: Hace que el servidor se reinicie después de actualizar el código. Use esta opción solo cuando esté desarrollando.
Visite http://127.0.0.1:5555 para ver la respuesta en formato JSON: {"message": "Hello World"}
solicitar parámetros de consulta
Una "ruta de solicitud" también se conoce comúnmente como "punto final" o "ruta".
import uvicorn
from pathlib import Path
from fastapi import FastAPI
app = FastAPI() # 创建 api 对象
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
return fake_items_db[skip: skip + limit]
@app.get("/") # 根路由。「请求路径」也通常被称为「端点」或「路由」。
async def root():
return {"name": "king", 'age': 100}
@app.get("/say/{data}")
async def say(data: str, q: int = None):
return {"data": data, "q": q}
if __name__ == '__main__':
# os.system('uvicorn main_test:app --reload')
uvicorn.run(f'{Path(__file__).stem}:app', host="127.0.0.1", port=5555)
pass
Acceso al navegador
http://127.0.0.1:5555/
http://127.0.0.1:5555/say/test
http://127.0.0.1:5555/items/?skip=0&limit=10
¿Cuál es la consulta? URL Un conjunto de pares clave-valor después de la palabra clave, separados por el carácter &.
Consulta en url:
omitir: el parámetro de inicio de la consulta
límite: el parámetro final de la consulta
Acerca de la diferencia entre @app.get() y @router.get()
Ambos se utilizan para definir controladores para solicitudes HTTP GET, pero existen algunas diferencias.
- @app.get(): este método es una abreviatura para definir rutas directamente en la instancia de la aplicación FastAPI.
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, World!"}
- @router.get(): Este método es una forma de definir rutas en el objeto de enrutador FastAPI (Router). Puede crear un objeto de enrutador independiente y usar @router.get() para definir rutas para rutas específicas.
from fastapi import FastAPI, APIRouter
router = APIRouter()
@router.get("/items")
async def read_items():
return {"message": "Read all items"}
El ejemplo anterior crea un objeto de enrutador llamado enrutador y define un controlador de solicitud GET para la ruta "/items" usando enrutador.get(). Cabe señalar que si utiliza varios objetos de enrutador, debe agregarlos a la instancia de la aplicación FastAPI para que surtan efecto.
# Agregue el objeto de enrutador a la instancia de la aplicación FastAPI a través del método app.include_router(), para que sus rutas definidas se procesen correctamente.
app.include_router(enrutador)
Resumen: app.get() se usa para definir rutas en instancias de aplicaciones, mientras que router.get() se usa para definir rutas en objetos de enrutador. La diferencia entre los dos es dónde se definen y cómo se aplican, pero ambos se pueden usar para manejar solicitudes HTTP GET.
Resumir
- importar
FastAPI
_ - Cree una
app
instancia. - Escriba un decorador de manipulación de rutas (p. ej.
@app.get("/")
, . - Escriba una función de manipulación de ruta (como arriba
def root(): ...
). - Ejecute un servidor de desarrollo (por ejemplo
uvicorn main:app --reload
, .
Los "parámetros de ruta" de la solicitud
Los parámetros de ruta deben reflejarse en los parámetros.
Una "ruta de solicitud" también se conoce comúnmente como "punto final" o "ruta".
FastAPI escribe una aplicación simple:
import os
import uvicorn
from pathlib import Path
from fastapi import FastAPI
# 类似于 app = Flask(__name__)
app = FastAPI()
# 绑定路由和视图函数
@app.get("/")
async def root():
return {"message": "Hello World"}
# 在 Windows 中必须加上 if __name__ == "__main__",
# 否则会抛出 RuntimeError: This event loop is already running
if __name__ == '__main__':
# 启动服务,因为我们这个文件叫做 main_test.py,所以需要启动 main_test.py 里面的 app
# os.system('uvicorn main_test:app --reload')
# uvicorn.run("main_test:app", host="0.0.0.0", port=8000)
uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
pass
Al ingresar "localhost: 5555" en el navegador, se mostrará la salida correspondiente. Vemos que se puede devolver un diccionario directamente en la función de vista. Por supuesto, además de los diccionarios, también son posibles otros tipos de datos.
# -*- coding:utf-8 -*-
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/int")
async def index1():
return 666
@app.get("/str")
async def index2():
return "佛祖保佑,佛祖保佑"
@app.get("/bytes")
async def index3():
return b"satori"
@app.get("/tuple")
async def index4():
temp_tuple = ("佛祖保佑", "佛祖保佑")
return temp_tuple
@app.get("/list")
async def index5():
return [{"name": "擒贼先擒王", "age": 18}, {"name": "捉奸先捉双", "age": 16}]
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Enviar solicitudes directamente usando solicitudes
solicitudes de importación
print(peticiones.get("http://localhost:5555/int").texto)
print(peticiones.get("http://localhost:5555/str").texto)
print(peticiones.get("http ://localhost:5555/bytes").texto)
print(solicitudes.get("http://localhost:5555/tuple").texto)
print(solicitudes.get("http://localhost:5555/list ").texto)
Sin embargo, la tupla se convierte automáticamente en una lista y se devuelve. Aquí especificamos la ruta en la ruta. Puede ver que el formulario de ruta en FastAPI es el mismo que en otros marcos, pero la ruta actual está codificada. ¿Qué pasa si queremos declarar dinámicamente los parámetros de la ruta?
parámetros de ruta
Los "parámetros" o las "variables" de la ruta se pueden declarar usando la misma sintaxis que las cadenas de formato de Python , FastAPI es suficiente para distinguir entre los parámetros de ruta y los parámetros de consulta.
desde fastapi importar FastAPI
aplicación = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id):
return {"item_id": item_id}
El valor del parámetro de ruta item_id
se pasará item_id
como argumento a su función. Ejecute y visite http://127.0.0.1:5555/items/foo y verá la respuesta: {"item_id":"foo"}
Ejemplo:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str = None, short: bool = False):
item = {"item_id": item_id}
if q:
item.update({"q": q})
if not short:
item.update(
{"description": "This is an amazing item that has a long description"}
)
return item
El valor del parámetro de ruta item_id se pasará a su función como el parámetro item_id. Al declarar otros parámetros de función que no son parámetros de ruta, se interpretarán automáticamente como parámetros de "cadena de consulta":
Mire su ruta de acceso, ejecute cualquiera de los siguientes métodos de acceso a la URL
http://127.0.0.1:8000/items/Lao Wang duerme al lado?short=1
http://127.0.0.1:8000/items/ Lao Wang duerme Al lado?short=True
http://127.0.0.1:8000/items/Lao Wang duerme al lado?short=true
http://127.0.0.1:8000/items/Lao Wang duerme al lado?short=on
http: //127.0 .0.1:8000/items/Lao Wang duerme al lado?short=yes
Puede encontrar que cualquier letra mayúscula y minúscula se convertirá en un parámetro bool Verdadero, que es el llamado parámetro de verificación difusa, que es bueno novedades para desarrolladores. Nota: si el parámetro corto no tiene un valor predeterminado, debe pasarse; de lo contrario, FastAPI informará un error.
{ "detalle": [ { "loc": [ "consulta", "necesitado" ], "mensaje": "campo obligatorio", "tipo": "valor_error.perdido" } ] }
Ejemplo:
from fastapi import FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
return fake_items_db[skip : skip + limit]
Una cadena de consulta es una colección de pares clave-valor separados por los símbolos & después de ? en la URL.
Se puede realizar una validación adicional en consultas usando Query :
from typing import Optional
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[str] = Query(None, max_length=50)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
La consulta tiene las siguientes comprobaciones de campo:
- min_length longitud mínima
- max_length longitud máxima
- coincidencia regular de expresiones regulares
- El primer parámetro de Consulta
...
es el valor predeterminado, lo que indica que es obligatorio
Path y Query se usan de la misma manera, y el campo de consulta también se puede verificar.
Y también puedes declarar la validación numérica:
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
*,
item_id: int = Path(..., title="The ID of the item to get", ge=0, le=1000),
q: str,
size: float = Query(..., gt=0, lt=10.5)
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
gt
:más que elge
:mayor o igual alt
: menos quele
: Menos que o igual a
También hay cookies similares :
from typing import Optional
from fastapi import Cookie, FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(ads_id: Optional[str] = Cookie(None)):
return {"ads_id": ads_id}
y encabezado :
from typing import Optional
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(user_agent: Optional[str] = Header(None)):
return {"User-Agent": user_agent}
Cuerpo de solicitud + ruta + parámetros de consulta, agregue parámetros de ruta dinámica de URL y parámetros de consulta en función del cuerpo de la solicitud
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
app = FastAPI()
@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item, q: str = None):
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result
Acerca de los motores de plantillas
FastAPI no tiene su propio motor de plantillas (Jinja2) como Flask, es decir, no hay un motor de plantillas predeterminado.Desde otra perspectiva, FastAPI se ha vuelto más flexible y extremadamente cómodo en la elección del motor de plantillas.
Tome la plantilla Jinja2 como ejemplo, instale dependencias
pip install jinja2
pip install aiofiles # Archivos estáticos asíncronos para fastapi
# -*- coding:utf-8 -*-
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import uvicorn
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static") # 挂载静态文件,指定目录
templates = Jinja2Templates(directory="templates") # 模板目录
@app.get("/data/{data}")
async def read_data(request: Request, data: str):
return templates.TemplateResponse("index.html", {"request": request, "data": data})
if __name__ == '__main__':
uvicorn.run(app, host="127.0.0.1", port=8000)
representación de archivos html
<html>
<head>
<title>士可杀不可辱(puedes matarme, pero no puedes follarme)</title>
</head>
<body>
<h1>高呼: { { data }} </h1>
</cuerpo>
</html>
Escriba http://127.0.0.1:8000/data/ en el navegador
Vale la pena señalar que cuando se devuelve la respuesta de TemplateRespone, se debe traer el objeto de contexto de la solicitud y los parámetros entrantes se colocan en el mismo diccionario. De esta forma, Jinja2 puede usarse como Flask.
Establezca la etiqueta de etiquetas para la ruta al grupo :
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
@app.post("/items/", response_model=Item, tags=["items"])
async def create_item(item: Item):
return item
@app.get("/items/", tags=["items"])
async def read_items():
return [{"name": "Foo", "price": 42}]
@app.get("/users/", tags=["users"])
async def read_users():
return [{"username": "johndoe"}]
También puede configurar el resumen y la descripción :
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
@app.post(
"/items/",
response_model=Item,
summary="Create an item",
description="Create an item with all the information, name, description, price, tax and a set of unique tags",
)
async def create_item(item: Item):
return item
Comentarios de varias líneas:
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
@app.post("/items/", response_model=Item, summary="Create an item")
async def create_item(item: Item):
"""
Create an item with all the information:
- **name**: each item must have a name
- **description**: a long description
- **price**: required
- **tax**: if the item doesn't have tax, you can omit this
- **tags**: a set of unique tag strings for this item
"""
return item
Ruta obsoleta :
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/", tags=["items"])
async def read_items():
return [{"name": "Foo", "price": 42}]
@app.get("/users/", tags=["users"])
async def read_users():
return [{"username": "johndoe"}]
@app.get("/elements/", tags=["items"], deprecated=True)
async def read_elements():
return [{"item_id": "Foo"}]
Parámetros de ruta escritos
Los tipos se pueden declarar para parámetros de ruta en funciones usando anotaciones de tipo estándar de Python.
desde fastapi importar FastAPI
aplicación = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
En este ejemplo, item_id se declara como tipo int.
Ejecute el ejemplo y abra un navegador para acceder a http://127.0.0.1:5555/items/3,
obtendrá la siguiente respuesta: {"item_id":3}
Tenga en cuenta que el valor recibido (y devuelto) por la función es 3, que es un valor int de Python, en lugar de la cadena "3".
Por lo tanto, FastAPI proporciona un "análisis" automático de la solicitud a través de la declaración de tipo anterior.
# -*- coding:utf-8 -*-
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/apple/{item_id}")
async def get_item(item_id: int):
"""
Flask 定义类型是在路由当中,也就是在 <> 里面,变量和类型通过 : 分隔
FastAPI 是使用类型注解的方式,此时的 item_id 要求一个整型(准确的说是一个能够转成整型的字符串)
"""
return {"item_id": item_id}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
el orden importa
Dado que las operaciones de ruta se ejecutan secuencialmente, debe prestar atención al orden al definir rutas
desde fastapi importar FastAPI
aplicación = FastAPI()
@app.get("/usuarios/yo")
async def read_user_me():
return {"user_id": "el usuario actual"}
@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}
Debido a que la operación de ruta se realiza en orden, debe estar /users/me
delante /users/{user_id}
de la ruta aquí, de lo contrario, solo se comparará. /users/{user_id}
Si accede a ella en este momento /users/me
, se devolverá un error de análisis porque la cadena "yo" no se puede analizar en un entero.
Valor predeterminado (enumeración)
Un determinado parámetro de ruta se puede declarar como un tipo específico a través de anotaciones de tipo (hablando con precisión, se puede convertir en un tipo específico, porque el valor predeterminado es una cadena), pero si queremos que sean solo unos pocos valores que estipulamos ¿Qué se debe hacer?
En este punto, puede usar Enum
tipos estándar de Python. Importe Enum
y cree una subclase que herede de str
y . Enum
Al str
heredar de , la documentación de la API podrá saber que estos valores deben escribirse string
y poder mostrarlos correctamente. Luego cree propiedades de clase con valores fijos que serán valores válidos disponibles:
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}
ruta contiene /
Supongamos que existe una ruta de este tipo: /files/{file_path}, y la ruta del archivo pasada por el usuario obviamente puede contener /, suponiendo que la ruta del archivo sea /root/test.py, entonces la ruta se convierte en /files//root/test.py , aparentemente esto es problemático.
OpenAPI no admite ninguna forma de declarar parámetros de ruta para incluir rutas dentro de ellos , ya que esto podría conducir a situaciones que son difíciles de probar y definir. Sin embargo, aún es posible implementarlo en FastAPI a través de una herramienta interna de Starlette .
Un parámetro de ruta que contiene una ruta se puede declarar usando opciones directamente desde Starlette :/files/{file_path:path}
En este caso, el nombre del parámetro va seguido de file_path
una :path
declaración de que el parámetro debe coincidir con cualquier ruta .
Por lo tanto, puedes usarlo así:
desde fastapi importar FastAPI
aplicación = FastAPI()
@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}
Es posible que necesite inclusiones de parámetros /home/johndoe/myfile.txt
, /
comenzando con una barra inclinada ( ).
En este caso, la URL será /files//home/johndoe/myfile.txt
, con una doble barra ( ) entre files
y .home
//
from fastapi import FastAPI
import uvicorn
app = FastAPI()
# 声明 file_path 的类型为 path,这样它会被当成一个整体
@app.get("/files/{file_path:path}")
async def get_file(file_path: str):
return {"file_path": file_path}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
establecer 127.0.0.1:8000/docs
En cuanto a la página "127.0.0.1:8000/docs", también se puede configurar:
from fastapi import FastAPI
import uvicorn
app = FastAPI(
title="测试文档",
description="这是一个简单的 demo",
docs_url="/my_docs",
openapi_url="/my_openapi"
)
@app.get("/apple/{item_id}")
async def get_item(item_id: int):
return {"item_id": item_id}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Parámetros de ruta, validación de datos
La verificación de datos de parámetros de consulta usa Consulta, y la verificación de datos de parámetros de ruta usa Ruta. El uso de los dos es exactamente el mismo y no hay diferencia.
from fastapi import FastAPI, Path
import uvicorn
app = FastAPI()
@app.get("/items/{item-id}")
async def read_items(item_id: int = Path(..., alias="item-id")):
return {"item_id": item_id}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Debido a que el parámetro de la ruta es obligatorio, es parte de la ruta, por lo que debemos usar ... para marcarlo como un parámetro obligatorio. Por supuesto, no importa si no hace esto, porque no puede usar el valor predeterminado si lo especifica, porque si no especifica el parámetro de ruta, no podrá hacer coincidir el ruta correspondiente en absoluto. En cuanto a otras comprobaciones, son exactamente iguales a los parámetros de consulta, por lo que no las repetiré aquí.
Sin embargo, como dijimos antes, el parámetro de ruta debe estar delante del parámetro de consulta, aunque FastAPI no tiene este requisito, obviamente es más cómodo escribir de esta manera. Pero aquí viene el problema, si el parámetro de ruta necesita especificar un alias, pero cierto parámetro de consulta no, surgirá un problema en este momento:
@app.get("/items/{item-id}")
async def read_items(q: str,
item_id: int = Path(..., alias="item-id")):
return {"item_id": item_id, "q": q}
Obviamente, la sintaxis de Python en este momento determina que item_id debe colocarse después de q. Por supuesto, no hay problema en hacerlo. FastAPI no tiene ningún requisito para el orden de los parámetros, porque se declara a través del nombre, tipo y el valor predeterminado de los parámetros para detectar parámetros, independientemente del orden de los parámetros. Pero en este momento, ¿qué debemos hacer si queremos que item_id esté delante de q?
@app.get("/items/{item-id}")
async def read_items(*, item_id: int = Path(..., alias="item-id"),
q: str):
return {"item_id": item_id, "q": q}
No hay problema en este momento. Al establecer el primer parámetro en *, tanto item_id como q deben pasarse a través de palabras clave, por lo que en este momento, los parámetros predeterminados se permiten antes que los parámetros no predeterminados. Por supuesto, no tenemos que preocuparnos por pasar parámetros en FastAPI, puede pensar que todos sus parámetros se pasan a través de parámetros de palabras clave.
El "parámetro de consulta" de la solicitud
- Los parámetros de consulta se pueden declarar a través de anotaciones de tipo en FastAPI, y los parámetros de consulta también se pueden omitir, luego toda la información relacionada con la solicitud ingresará al objeto Solicitud
- Si se definen parámetros que no son parámetros de ruta en la función, se interpretarán automáticamente como parámetros de consulta.
Ejemplo:
desde fastapi importar FastAPI
aplicación = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
@app.get("/items/")
asíncrono def read_item(omitir: int = 0, límite: int = 10):
return fake_items_db[omitir: omitir + límite]
Una cadena de consulta es una colección de pares clave-valor que siguen a la URL ?
, separados por &
símbolos.
Por ejemplo, en la siguiente url: http://127.0.0.1:5555/items/?skip=0&limit=10
Los parámetros de consulta son:
skip
: El valor correspondiente es0
limit
: El valor correspondiente es10
Ejemplo:
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/user/{user_id}")
async def get_user(user_id: str, name: str, age: int):
"""
函数中参数定义了 user_id、name、age 三个参数
显然 user_id 和 路径参数中的 user_id 对应,
然后 name 和 age 会被解释成查询参数
这三个参数的顺序没有要求,但是一般都是路径参数在前,查询参数在后
"""
return {"user_id": user_id, "name": name, "age": age}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
parámetro opcional, parámetro requerido
- Cuando el valor predeterminado del parámetro que no es de ruta se establece en Ninguno, el parámetro es opcional
- Cuando el parámetro que no es de ruta no tiene un valor predeterminado, el parámetro debe pasarse
None
Declare parámetros de consulta opcionales estableciendo sus valores predeterminados en :
de escribir importación Unión
desde fastapi importar FastAPI
aplicación = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Union[str, None] = None):
if q:
return {"item_id": item_id, "q": q }
devolver {"item_id": item_id}
En este ejemplo, el parámetro de función q tiene como valor predeterminado Ninguno, por lo que q es opcional. FastAPI puede decir que el parámetro item_id es un parámetro de ruta y q no lo es, por lo que q es un parámetro de consulta.
Parámetros de tipo múltiple (Unión)
Especifique varios tipos. Por ejemplo, user_id se analiza como un número entero y, si el análisis falla, degenera en una cadena.
from typing import Union, Optional
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/user/{user_id}")
async def get_user(user_id: Union[int, str], name: Optional[str] = None):
"""
通过 Union 来声明一个混合类型,int 在前、str 在后。会先按照 int 解析,解析失败再变成 str
然后是 name,它表示字符串类型、但默认值为 None(不是字符串),那么应该声明为 Optional[str]
"""
return {"user_id": user_id, "name": name}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Por lo tanto, el diseño de FastAPI sigue siendo muy bueno. Se puede decir que es muy inteligente darse cuenta de la limitación de los tipos de parámetros a través de las anotaciones de tipo Python, por lo que esto también requiere que dominemos las anotaciones de tipo Python con soltura.
el tipo bool se convierte automáticamente
Para tipos booleanos, FastAPI admite la conversión automática
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/{flag}")
async def get_flag(flag: bool):
return {"flag": flag}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
print(requests.get("http://localhost:5555/1").json())
print(requests.get("http://localhost:5555/True").json())
print(requests. get("http://localhost:5555/true").json())
print(solicitudes.get("http://localhost:5555/False").json())
print(solicitudes.get("http ://localhost:5555/false").json())
print(requests.get("http://localhost:5555/on").json())
print(requests.get("http://localhost :5555/sí").json())
print(solicitudes.get("http://localhost:5555/off").json())
print(solicitudes.get("http://localhost:5555/no ").json())
Múltiples rutas, parámetros de consulta
Se pueden declarar varios parámetros de ruta y parámetros de consulta al mismo tiempo, y FastAPI los reconoce. Y no necesita declararlos en ningún orden en particular. Serán detectados por su nombre:
de escribir importación Unión
desde fastapi importar FastAPI
aplicación = FastAPI()
@app.get("/users/{user_id}/items/{item_id}")
async def read_user_item(
user_id: int, item_id: str, q: Union[str, None] = None, short: bool = False
):
item = {"item_id": item_id, "owner_id": user_id}
if q:
item.update({"q": q})
if not short:
item.update(
{"description": "Este es un artículo increíble que tiene una descripción larga"}
)
devolver artículo
FastAPI puede definir cualquier cantidad de parámetros de ruta, siempre que los parámetros de ruta dinámicos aparezcan en los parámetros de la función. Por supuesto, puede haber cualquier cantidad de parámetros de consulta y FastAPI puede manejarlos muy bien.
from typing import Optional
from fastapi import FastAPI
import uvicorn
app = FastAPI()
@app.get("/postgres/{schema}/v1/{table}")
async def get_data(
schema: str,
table: str,
select: str = "*",
where: Optional[str] = None,
limit: Optional[int] = None,
offset: Optional[int] = None):
"""
标准格式是:路径参数按照顺序在前,查询参数在后
但其实对顺序是没有什么要求的
"""
query = f"select {select} from {schema}.{table}"
if where:
query += f" where {where}"
if limit:
query += f" limit {limit}"
if offset:
query += f" offset {offset}"
return {"query": query}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
http://localhost:5555/postgres/ods/v1/staff
http://localhost:5555/postgres/ods/v1/staff?select=id, nombre&where=id > 3&limit=100
Depende (Inyección de dependencia)
from typing import Optional
import uvicorn
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
# common_parameters 接收三个参数:q、skip、limit
# 然后在解析请求的时候,会将 q、skip、limit 传递到 common_parameters 中,然后将返回值赋值给 commons
# 但如果解析不到某个参数时,那么会判断函数中参数是否有默认值,没有的话就会返回错误,而不是传递一个 None 进去
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
solicitudes.get("http://localhost:5555/elementos").json()
{q': Ninguno,'saltar':@,límite': 100}
solicitudes.get("http://localhost:5555/ artículos?g=id, nombre").json()
{g':id, nombre', omitir': 0, límite': 100}
Depends puede implementar muy bien la inyección de dependencias, y escribimos especialmente dos rutas para mostrar que son independientes entre sí. Por lo tanto, será muy práctico cuando haya lógica compartida, o conexión de base de datos compartida, seguridad mejorada, autenticación, permisos de rol, etc.
FastAPI proporciona un sistema de inyección de dependencia fácil de usar pero potente que permite a los desarrolladores integrar fácilmente componentes en FastAPI .
¿Qué es la "inyección de dependencia"?
La inyección de dependencia es un patrón de diseño que elimina las dependencias entre clases. Poner clases dependientes en contenedores y analizar instancias de estas clases es inyección de dependencia. El propósito es lograr el desacoplamiento de clases.
Ejemplo:
from typing import Optional
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Las dependencias de este ejemplo esperan recibir los siguientes parámetros:
str
Un parámetro de consulta opcional de tipoq
int
Un parámetro de consulta opcional de tiposkip
, con un valor predeterminado de0
int
Un parámetro de consulta opcional de tipolimit
, con un valor predeterminado de100
La función de dependencia luego devuelve un dict
.
Use Clase como dependencia:
from typing import Optional
from fastapi import Depends, FastAPI
app = FastAPI()
fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]
class CommonQueryParams:
def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
response = {}
if commons.q:
response.update({"q": commons.q})
items = fake_items_db[commons.skip: commons.skip + commons.limit]
response.update({"items": items})
return response
Utilice subdependencias anidadas:
from typing import Optional
from fastapi import Cookie, Depends, FastAPI
app = FastAPI()
def query_extractor(q: Optional[str] = None):
return q
def query_or_cookie_extractor(
q: str = Depends(query_extractor), last_query: Optional[str] = Cookie(None)
):
if not q:
return last_query
return q
@app.get("/items/")
async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
return {"q_or_cookie": query_or_default}
Usa dependencias en la ruta:
from fastapi import Depends, FastAPI, Header, HTTPException
app = FastAPI()
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
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
return [{"item": "Foo"}, {"item": "Bar"}]
Una dependencia global que se puede aplicar para todas las operaciones de ruta :
from fastapi import Depends, FastAPI, Header, HTTPException
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
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
@app.get("/items/")
async def read_items():
return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
@app.get("/users/")
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
Parámetros de consulta, validación de datos
FastAPI nos ayuda a realizar una verificación de datos más inteligente, como una cadena, esperamos que el usuario solo pueda pasar una cadena con una longitud de 6 a 15 al pasarla, ¿qué debemos hacer?
from typing import Optional
from fastapi import FastAPI, Query
import uvicorn
app = FastAPI()
@app.get("/user")
async def check_length(
# 默认值为 None,应该声明为 Optional[str],当然声明 str 也是可以的。只不过声明为 str,那么默认值应该也是 str
# 所以如果一个类型允许为空,那么更规范的做法应该是声明为 Optional[类型]。
password: Optional[str] = Query(None, min_length=6, max_length=15)
):
return {"password": password}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
La contraseña es opcional, pero si se pasa, debe ser una cadena, y nuevamente una cadena de longitud de 6 a 15. Entonces, si se pasa None, entonces None y Query(None) son equivalentes al declarar el valor predeterminado, pero Query también admite otros parámetros para limitar los parámetros.
solicitudes.get("http://localhost:5555/usuario?contraseña=12345").json()
solicitudes.get("http://localhost:5555/usuario?contraseña=123456").json()
Además de limitar la longitud mínima y la longitud máxima en Query, existen otras funciones
Parámetros de consulta y validación de cadenas
desde fastapi importar FastAPI
aplicación = FastAPI()
@app.get("/items/")
async def read_items(q: str | Ninguno = Ninguno):
resultados = {"items": [{"item_id": "Foo"}, {"item_id": "Bar" }]}
si q:
resultados.update({"q": q})
devuelve resultados
El parámetro de consulta q
es de tipo str
, cuyo valor predeterminado es None
, por lo que es opcional.
suma de control adicional
Agregar restricciones: aunque q
es opcional, siempre que se proporcione este parámetro, el valor del parámetro no puede exceder los 50 caracteres de longitud .
Importar consulta desde fastapi:
de escribir importación Unión
de fastapi importar FastAPI, consulta
aplicación = FastAPI()
@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=50)):
resultados = {"items": [{"item_id": " Foo"}, {"item_id": "Bar"}]}
si q:
resultados.update({"q": q})
devuelve resultados
Agregar más cheques
También puede agregar min_length
parámetros:
de escribir importación Unión
de fastapi importar FastAPI, consulta
aplicación = FastAPI()
@app.get("/items/")
async def read_items(
q: Union[str, None] = Query(predeterminado=Ninguno, min_length=3, max_length=50)
):
resultados = {"items": [{" item_id": "Foo"}, {"item_id": "Bar"}]}
si q:
resultados.update({"q": q})
devuelve resultados
añadir expresión regular
Es posible definir una expresión regular que el valor del parámetro debe coincidir:
de escribir importación Unión
de fastapi importar FastAPI, consulta
aplicación = FastAPI()
@app.get("/items/")
async def read_items(
q: Union[str, None] = Query(
predeterminado=Ninguno, min_length=3, max_length=50, pattern="^fixedquery$"
) )
:
resultados = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
si q:
resultados.update({"q": q})
devuelve resultados
declarado como un parámetro requerido
... es un objeto especial en Python, a través del cual se puede dar cuenta de que el parámetro es un parámetro obligatorio.
from fastapi import FastAPI, Query
import uvicorn
app = FastAPI()
@app.get("/user")
async def check_length(password: str = Query(..., min_length=6)):
"""将第一个参数换成 ... 即可实现该参数是必传参数
"""
return {"password": password}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
El parámetro de consulta se convierte en una lista.
Si especificamos a=1&a=2
, ¿cómo podemos obtener una lista cuando obtenemos a?
from typing import Optional, List
from fastapi import FastAPI, Query
import uvicorn
app = FastAPI()
@app.get("/items")
async def read_items(
a1: str = Query(...),
a2: List[str] = Query(...),
b: List[str] = Query(...)
):
return {"a1": a1, "a2": a2, "b": b}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Primero, "a2" y "b" son listas correspondientes, luego "a1" solo obtiene el último valor. Además, algunas personas pueden pensar que somos un poco extensos, ¿podemos escribir así en la declaración de la función?
@app.get("/items")
async def read_items(
a1: str,
a2: List[str],
b: List[str]
):
return {"a1": a1, "a2": a2, "b": b}
Está bien para a1, pero no para a2 y b. Para los parámetros de consulta de tipo lista, sin importar si hay un valor predeterminado o no, debe agregar explícitamente Query para indicar los parámetros requeridos. Si se permite que sea Ninguno (o tiene un valor predeterminado), debe escribirse así:
from typing import Optional, List
from fastapi import FastAPI, Query
import uvicorn
app = FastAPI()
@app.get("/items")
async def read_items(
a1: str,
a2: Optional[List[str]] = Query(None),
b: List[str] = Query(["1", "嘿嘿"])
):
return {"a1": a1, "a2": a2, "b": b}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
alias el parámetro
Suponiendo que el parámetro de consulta que definimos se llame item-query, ya que debe reflejarse en el parámetro de función, que obviamente no se ajusta a la convención de nomenclatura de las variables de Python, ¿qué debemos hacer en este momento? La respuesta es un alias.
from typing import Optional, List
from fastapi import FastAPI, Query
import uvicorn
app = FastAPI()
@app.get("/items")
async def read_items(
# 通过 url 的时候使用别名即可
item1: Optional[str] = Query(None, alias="item-query"),
item2: str = Query("哈哈", alias="@@@@"),
item3: str = Query(..., alias="$$$$") # item3 是必传的
):
return {"item1": item1, "item2": item2, "item3": item3}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Detección numérica
Query no solo admite la verificación de cadenas, sino que también admite la verificación de valores. Puede pasar parámetros como gt, ge, lt y le. Creo que sabe lo que hacen estos parámetros sin decirlo. Démosle un ejemplo ilustrativo. :
from fastapi import FastAPI, Query
import uvicorn
app = FastAPI()
@app.get("/items")
async def read_items(
# item1 必须大于 5
item1: int = Query(..., gt=5),
# item2 必须小于等于 7
item2: int = Query(..., le=7),
# item3 必须必须等于 10
item3: int = Query(..., ge=10, le=10)
):
return {"item1": item1, "item2": item2, "item3": item3}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
Solicitar objeto
¿Qué es Solicitud? En primer lugar, sabemos que cualquier solicitud corresponde a un objeto Solicitud, y toda la información solicitada está en este objeto Solicitud, y FastAPI no es una excepción.
El parámetro de ruta debe estar reflejado en el parámetro, pero el parámetro de consulta no se puede escribir porque definimos solicitud: Solicitud, luego toda la información relacionada con la solicitud ingresará al objeto Solicitud
from fastapi import FastAPI, Request
import uvicorn
app = FastAPI()
@app.get("/girl/{user_id}")
async def read_girl(user_id: str,
request: Request):
"""路径参数是必须要体现在参数中,但是查询参数可以不写了
因为我们定义了 request: Request,那么请求相关的所有信息都会进入到这个 Request 对象中"""
header = request.headers # 请求头
method = request.method # 请求方法
cookies = request.cookies # cookies
query_params = request.query_params # 查询参数
return {"name": query_params.get("name"), "age": query_params.get("age"), "hobby": query_params.getlist("hobby")}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Toda la información relacionada con la solicitud se puede obtener a través del objeto Solicitud. Cuando pasábamos parámetros incorrectos antes, FastAPI automáticamente nos devolvía un mensaje de error, pero a través de la Solicitud podemos analizarlo nosotros mismos y especificar el mensaje de error devuelto.
Objeto de respuesta
Dado que hay una Solicitud, debe haber una Respuesta. Aunque podemos devolver un diccionario directamente, FastAPI lo convertirá en un objeto de Respuesta para nosotros.
La respuesta recibe internamente los siguientes parámetros:
content:返回的数据
status_code:状态码
headers:返回的请求头
media_type:响应类型(就是 HTML 中 Content-Type,只不过这里换了个名字)
background:接收一个任务,Response 在返回之后会自动异步执行
ejemplo
from fastapi import FastAPI, Request, Response
import uvicorn
import orjson
app = FastAPI()
@app.get("/girl/{user_id}")
async def read_girl(user_id: str, request: Request):
query_params = request.query_params # 查询参数
data = {
"name": query_params.get("name"),
"age": query_params.get("age"),
"hobby": query_params.getlist("hobby")
}
# 实例化一个 Response 对象
response = Response(
# content,我们需要手动转成 json 字符串,如果直接返回字典的话,那么在包装成 Response 对象的时候会自动帮你转
orjson.dumps(data),
# status_code,状态码
201,
# headers,响应头
{"Token": "xxx"},
# media_type,就是 HTML 中的 Content-Type
"application/json",
)
# 如果想设置 cookie 的话,那么通过 response.set_cookie 即可
# 删除 cookie 则是 response.delete_cookie
return response
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
A través de Response, podemos personalizar encabezados de solicitud, códigos de estado, cookies, etc.
Además de Response, hay muchos otros tipos de respuestas, todas están en fastapi.responses, como: FileResponse, HTMLResponse, PlainTextResponse, etc. Todos heredan Response, pero establecerán automáticamente el tipo de respuesta por usted, por ejemplo:
from fastapi import FastAPI
from fastapi.responses import Response, HTMLResponse
import uvicorn
app = FastAPI()
@app.get("/index")
async def index():
response1 = HTMLResponse("<h1>你好呀</h1>")
response2 = Response("<h1>你好呀</h1>", media_type="text/html")
# 以上两者是等价的,在 HTMLResponse 中会自动将 media_type 设置成 text/html
return response1
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Use response_model
parámetros para declarar el modelo a usar para la respuesta:
from typing import List, Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: List[str] = []
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
return item
response_model_exclude_unset=True
: Esos valores predeterminados no se incluirán en la respuesta, solo los valores realmente establecidosresponse_model_include
Qué atributos se incluyenresponse_model_exclude
omitir ciertos atributos
status_code
parámetro para declarar el código de estado HTTP que se usará para la respuesta:
from fastapi import FastAPI
app = FastAPI()
@app.post("/items/", status_code=201)
async def create_item(name: str):
return {"name": name}
Para los campos de formulario, utilice Form
:
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
return {"username": username}
File
El archivo de carga utilizado para definir el cliente (recibir el archivo de carga, para ser preinstalado
):python-multipart
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: bytes = File(...)):
return {"file_size": len(file)}
@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
return {"filename": file.filename}
Devuelve una respuesta de error HTTP al cliente, lista para usar HTTPException
.
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}
Utilice response_description
la descripción de la respuesta de configuración:
from typing import Optional, Set
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
@app.post(
"/items/",
response_model=Item,
summary="Create an item",
response_description="The created item",
)
async def create_item(item: Item):
"""
Create an item with all the information:
- **name**: each item must have a name
- **description**: a long description
- **price**: required
- **tax**: if the item doesn't have tax, you can omit this
- **tags**: a set of unique tag strings for this item
"""
return item
cuerpo de solicitud
Cuando es necesario enviar datos desde un cliente (como un navegador) a una API, se envían como un "cuerpo de solicitud".
El cuerpo de la solicitud son los datos enviados por el cliente a la API.
El cuerpo de la respuesta son los datos que la API envía al cliente.
Para enviar datos, debe usar uno de los siguientes métodos:
POST
(más común)PUT
,DELETE
oPATCH
.
Obviamente, correspondiente a POST, PUT y otros tipos de solicitudes, debemos poder analizar el cuerpo de la solicitud y construir un cuerpo de respuesta.
Modelo
En FastAPI, tanto el cuerpo de la solicitud como el cuerpo de la respuesta corresponden a un Modelo
from typing import Optional, List
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn
app = FastAPI()
class Girl(BaseModel):
"""数据验证是通过 pydantic 实现的,我们需要从中导入 BaseModel,然后继承它"""
name: str
age: Optional[str] = None
length: float
hobby: List[str] # 对于 Model 中的 List[str] 我们不需要指定 Query(准确的说是 Field)
@app.post("/girl")
async def read_girl(girl: Girl):
# girl 就是我们接收的请求体,它需要通过 json 来传递,并且这个 json 要有上面的四个字段(age 可以没有)
# 通过 girl.xxx 的方式我们可以获取和修改内部的所有属性
return dict(girl) # 直接返回 Model 对象也是可以的
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
Ejemplo:
desde fastapi importar FastAPI
desde pydantic importar BaseModel
elemento de clase (modelo base):
nombre: str
descripción: str | Ninguno = Ninguno
precio: flotante
impuesto: flotante | Ninguno = Ninguno
aplicación = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": precio_con_impuestos})
devuelve item_dict
Al igual que cuando se declaran parámetros de consulta, cuando una propiedad del modelo tiene un valor predeterminado, no es necesario. De lo contrario, es una propiedad requerida. Configuración predeterminada None
para que sea una propiedad opcional.
Use el objeto Request para pasar el cuerpo
Si no desea utilizar modelos de Pydantic, también puede utilizar el parámetro Cuerpo . Consulte la documentación Cuerpo de la solicitud - Parámetros múltiples: valor único en el cuerpo de la solicitud .
from fastapi import FastAPI, Request
import uvicorn
app = FastAPI()
@app.post("/girl")
async def read_girl(request: Request):
# 是一个协程,所以需要 await
data = await request.body()
print(data)
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Cuando se usa el módulo de solicitudes para enviar una solicitud posterior, se puede pasar a través del parámetro de datos o el parámetro json.
- Cuando se pasa a través de json={"name": "satori", "age": 16, "length": 155.5}, se convertirá en una cadena json para la transmisión, y la impresión en el programa se imprime de la siguiente manera : b'{ "nombre": "satori", "edad": 16, "longitud": 155,5}'
- Si el parámetro de datos se usa para enviar la solicitud (el valor permanece sin cambios), se empalmará en la forma de k1=v1&k2=v2 y luego se transmitirá (equivalente al envío de formulario, que se describirá más adelante), y el el programa se imprimirá de la siguiente manera: b'name=satori&age= 16&length=155.5'
Entonces vemos que lo que obtiene await request.body() es el flujo de bytes más primitivo, y además de await request.body(), también hay un await request.json(), pero este último llama al primero internamente para obtener Después de alcanzar el flujo de bytes, automáticamente lo ayudará a cargarlo en un diccionario. Por lo tanto, el uso de await request.json() también requiere que debemos usar el parámetro json para pasar al enviar la solicitud (se pasa el json convertido del diccionario, por lo que también se puede analizar en un diccionario), de lo contrario, usar await request.json() no se pudo analizar correctamente.
Parámetros de ruta, parámetros de consulta, cuerpo de solicitud
- Si este parámetro también se declara en la ruta , se utilizará como parámetro de ruta.
- Si el parámetro es de un solo tipo (p. ej.
int
, ,float
,str
,bool
etc.) se interpretará como un parámetro de consulta . - Si el tipo del parámetro se declara como un modelo Pydantic , se interpretará como el cuerpo de la solicitud .
# -*- coding:utf-8 -*-
# @Author: komeiji satori
from typing import Optional, List
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn
app = FastAPI()
class Girl(BaseModel):
name: str
age: Optional[str] = None
length: float
hobby: List[str]
@app.post("/girl/{user_id}")
async def read_girl(user_id, q: str, girl: Girl):
return {"user_id": user_id, "q": q, **dict(girl)}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
Después de especificar los parámetros de ruta, los parámetros de consulta y el cuerpo de la solicitud, FastAPI aún puede distinguir correctamente, por supuesto, también podemos usar el objeto Solicitud.
from typing import Optional, List, Dict
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn
app = FastAPI()
@app.post("/girl/{user_id}")
async def read_girl(user_id, request: Request):
q = request.query_params.get("q")
data: Dict = await request.json()
data.update({"user_id": user_id, "q": q})
return data
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Incrustar un solo parámetro de cuerpo de solicitud
Supongamos que solo tiene un Item
parámetro de cuerpo de solicitud de un modelo de Pydantic item
. De forma predeterminada, FastAPI esperará directamente dicho cuerpo de solicitud. Sin embargo, si desea esperar un item
JSON con claves y contenido de modelo en los valores, como ocurre cuando se declaran parámetros de cuerpo adicionales, puede usar un Body
parámetro especial embed
:item: Item = Body(embed=True)
de escribir import Anotado
desde fastapi import Body, FastAPI
desde pydantic import BaseModelaplicación = FastAPI()
elemento de clase (modelo base):
nombre: str
descripción: str | Ninguno = Ninguno
precio: flotante
impuesto: flotante | Ninguno = Ninguno
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Anotado[Item, Body(embed=True)]):
resultados = {"item_id": item_id, "item": artículo}
resultados devueltos
En este caso, FastAPI esperará un cuerpo de solicitud como este:
{ "artículo": { "nombre": "Foo", "descripción": "El pretendiente", "precio": 42,0, "impuesto": 3,2 } }
en lugar de:
{ "nombre": "Foo", "descripción": "El pretendiente", "precio": 42,0, "impuesto": 3,2 }
Se pueden agregar varios parámetros de cuerpo de solicitud a una función de operación de ruta , aunque una solicitud solo puede tener un cuerpo de solicitud.
También es posible declarar un único valor que se recibirá como parte del cuerpo de la solicitud.
También es posible indicar a FastAPI que incruste el cuerpo de la solicitud original en una clave cuando solo se declara un parámetro del cuerpo de la solicitud.
Múltiples parámetros de cuerpo de solicitud
# -*- coding:utf-8 -*-
# @Author: komeiji satori
from typing import Optional, List
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn
app = FastAPI()
class Girl(BaseModel):
name: str
age: Optional[str] = None
class Boy(BaseModel):
name: str
age: int
@app.post("/boy_and_girl")
async def read_boy_and_girl(girl: Girl, boy: Boy):
return {"girl": dict(girl), "boy": dict(boy)}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
En este momento, al pasar, se debe pasar de la siguiente manera:
Los dos json deben anidarse juntos para formar un json más grande, y la clave es el nombre de nuestro parámetro de función. Así que este método es en realidad equivalente a:
# -*- coding:utf-8 -*-
# @Author: komeiji satori
from typing import Optional, List, Dict
from fastapi import FastAPI, Request, Response
from pydantic import BaseModel
import uvicorn
app = FastAPI()
class BoyAndGirl(BaseModel):
girl: Dict
boy: Dict
@app.post("/boy_and_girl")
async def read_boy_and_girl(boy_and_girl: BoyAndGirl):
return dict(boy_and_girl)
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Este método también se puede lograr, pero no hay restricciones en los diccionarios dentro del diccionario. Por supuesto, aún podemos usar el objeto Solicitud para juzgar por nosotros mismos después de obtener el diccionario, porque para json, los campos internos pueden cambiar y lo más importante es que puede haber muchos campos. En este momento, personalmente prefiero usar el objeto Solicitud.
Uso mixto Path
y Query
parámetros del cuerpo de la solicitud
Siéntase libre de mezclar use Path
y Query
solicitar la declaración de parámetros del cuerpo, FastAPI sabrá cómo manejarlo.
de escribir import Anotado
desde fastapi importar FastAPI, Ruta
desde pydantic importar BaseModelaplicación = FastAPI()
elemento de clase (modelo base):
nombre: str
descripción: str | Ninguno = Ninguno
precio: flotante
impuesto: flotante | Ninguno = Ninguno
@app.put("/items/{item_id}")
async def update_item(
item_id: Anotado[int, Path(title="El ID del elemento a obtener", ge=0, le=1000)], q
: str | Ninguno = Ninguno,
elemento: Elemento | Ninguno = Ninguno,
):
resultados = {"item_id": item_id}
if q:
resultados.update({"q": q})
if item:
resultados.update({"item ": elemento})
resultados devueltos
item
Tenga en cuenta que lo que se obtendrá del cuerpo de la solicitud es opcional en este caso . porque su valor predeterminado es None
.
Múltiples parámetros de cuerpo de solicitud
desde fastapi importar FastAPI
desde pydantic importar BaseModelaplicación = FastAPI()
elemento de clase (modelo base):
nombre: str
descripción: str | Ninguno = Ninguno
precio: flotante
impuesto: flotante | Ninguno = Ninguno
clase Usuario (Modelo base):
nombre de usuario: str
full_name: str | Ninguno = Ninguno
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
resultados = {"item_id": item_id, "item": item, "user": usuario}
resultados devueltos
cuerpo de solicitud
{ "artículo": { "nombre": "Foo", "descripción": "El pretendiente", "precio": 42,0, "impuesto": 3,2 }, "usuario": { "nombre de usuario": "dave", " nombre_completo": "Dave Grohl" } }
Un solo valor en el cuerpo de la solicitud
de escribir import Anotado
desde fastapi import Body, FastAPI
desde pydantic import BaseModelaplicación = FastAPI()
elemento de clase (modelo base):
nombre: str
descripción: str | Ninguno = Ninguno
precio: flotante
impuesto: flotante | Ninguno = Ninguno
clase Usuario (Modelo base):
nombre de usuario: str
full_name: str | Ninguno = Ninguno
@app.put("/items/{item_id}")
asíncrono def update_item(
item_id: int, item: Item, usuario: Usuario, importancia: Anotado[int, Body()]
):
resultados = {"item_id": item_id , "elemento": elemento, "usuario": usuario, "importancia": importancia}
resultados devueltos
Cuerpo de la solicitud:
{ "artículo": { "nombre": "Foo", "descripción": "El pretendiente", "precio": 42,0, "impuesto": 3,2 }, "usuario": { "nombre de usuario": "dave", " nombre_completo": "Dave Grohl" }, "importancia": 5 }
Múltiples parámetros de cuerpo de solicitud y parámetros de consulta
De forma predeterminada, los valores individuales se interpretan como parámetros de consulta, por lo que no es necesario agregarlos explícitamente Query
de escribir import Anotado
desde fastapi import Body, FastAPI
desde pydantic import BaseModelaplicación = FastAPI()
elemento de clase (modelo base):
nombre: str
descripción: str | Ninguno = Ninguno
precio: flotante
impuesto: flotante | Ninguno = Ninguno
clase Usuario (Modelo base):
nombre de usuario: str
full_name: str | Ninguno = Ninguno
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Item,
usuario: Usuario,
importancia: Anotado[int, Body(gt=0)],
q: str | Ninguno = Ninguno,
):
resultados = {"item_id": item_id, "item": item, "usuario": usuario, "importancia": importancia}
if q:
resultados.update({"q": q})
devuelve resultados
formulario formulario
Llamar a requestes.post, si el parámetro se pasa a través de datos, es equivalente a enviar un formulario de formulario, por lo que en FastAPI, se puede obtener a través de await request.form (). Nota: internamente, await request.body () también es llamó primero.
from fastapi import FastAPI, Request, Response
import uvicorn
app = FastAPI()
@app.post("/girl")
async def girl(request: Request):
# 此时 await request.json() 报错,因为是通过 data 参数传递的,相当于 form 表单提交
# 如果是通过 json 参数传递,那么 await request.form() 会得到一个空表单
form = await request.form()
return [form.get("name"), form.getlist("age")]
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
También puedes usar otros métodos:
# -*- coding:utf-8 -*-
# @Author: komeiji satori
from fastapi import FastAPI, Form
import uvicorn
app = FastAPI()
@app.post("/user")
async def get_user(username: str = Form(...),
password: str = Form(...)):
return {"username": username, "password": password}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
Al igual que el formulario, los parámetros de consulta, los parámetros de ruta, etc., se pueden usar junto con el objeto Solicitud. Al igual que en el ejemplo anterior, si definimos una solicitud más: Solicitud, entonces aún podemos obtener el formulario relevante a través de espera de solicitud.formulario () información. Entonces, si cree que un determinado parámetro no es adecuado para la anotación de tipo, puede analizarlo solo a través del objeto Solicitud.
Subir archivo
¿Cómo recibe FastAPI las cargas de archivos de los usuarios? En primer lugar, si desea utilizar la función de carga de archivos, debe instalar un paquete python-multipart, solo pip install python-multipart directamente.
from fastapi import FastAPI, File, UploadFile
import uvicorn
app = FastAPI()
@app.post("/file1")
async def file1(file: bytes = File(...)):
return f"文件长度: {len(file)}"
@app.post("/file2")
async def file1(file: UploadFile = File(...)):
return f"文件名: {file.filename}, 文件大小: {len(await file.read())}"
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
Vemos que uno toma un flujo de bytes directamente y el otro toma un objeto similar a un identificador de archivo. ¿Qué debo hacer si se suben varios archivos?
from typing import List
from fastapi import FastAPI, UploadFile, File
import uvicorn
app = FastAPI()
@app.post("/file")
async def file(files: List[UploadFile] = File(...)):
"""指定类型为列表即可"""
for idx, f in enumerate(files):
files[idx] = f"文件名: {f.filename}, 文件大小: {len(await f.read())}"
return files
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
En este punto, se implementa la carga del archivo FastAPI. Por supuesto, la carga del archivo no afecta nuestro procesamiento del formulario. Puede intentar procesar el archivo y el formulario al mismo tiempo.
devolver recursos estáticos
Necesita instalar aiofiles, simplemente instálelo directamente con pip.
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
import uvicorn
app = FastAPI()
# name 参数只是起一个名字,FastAPI 内部使用
app.mount("/static", StaticFiles(directory=r"C:\Users\satori\Desktop\bg"), name="static")
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Entrada del navegador: localhost:5555/static/1.png, luego se devolverá el archivo 1.png en C:\Users\satori\Desktop\bg.
subaplicación
Si tiene 2 aplicaciones FastAPI independientes, puede establecer una como aplicación principal y la otra como aplicación secundaria:
from fastapi import FastAPI
app = FastAPI()
@app.get("/app")
def read_main():
return {"message": "Hello World from main app"}
subapi = FastAPI()
@subapi.get("/sub")
def read_sub():
return {"message": "Hello World from sub API"}
app.mount("/subapi", subapi)
interino
Se puede utilizar root_path
para configurar el proxy.
Use la línea de comando: uvicorn main:app --root-path /api/v1
O configurarlo en código:
from fastapi import FastAPI, Request
app = FastAPI(root_path="/api/v1")
@app.get("/app")
def read_main(request: Request):
return {"message": "Hello World", "root_path": request.scope.get("root_path")}
usar plantilla
Puede usar cualquier plantilla en FastAPI , una opción común es Jinja2. Instalación: pip instalar jinja2
usar:
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
return templates.TemplateResponse("item.html", {"request": request, "id": id})
Archivo de plantilla templates/item.html
:
<html>
<head>
<title>Detalles del artículo</title>
<link href="{ { url_for('static', path='/styles.css') }}" rel="stylesheet">
</head>
<cuerpo><h1>Id. del artículo: { { id }}</h1>
</cuerpo>
</html>
manejo de errores
from fastapi import FastAPI, HTTPException
import uvicorn
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id != "foo":
# 里面还可以传入 headers 设置响应头
raise HTTPException(status_code=404, detail="item 没有发现")
return {"item": "bar"}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
HTTPException es una clase de excepción ordinaria de Python (heredada de Exception), que lleva la información relevante de la API. Como es una excepción, no podemos devolver, sino subir. Este método devuelve un error porque puede contener muy poca información.
excepción personalizada
FastAPI proporciona una HTTPException internamente, pero también podemos personalizarla, pero tenga en cuenta: después de personalizar la excepción, debemos definir un controlador, vincular la excepción y el controlador, y luego desencadenar la excepción correspondiente cuando se lanza la excepción.
from fastapi import FastAPI, Request
from fastapi.responses import ORJSONResponse
import uvicorn
app = FastAPI()
class ASCIIException(Exception):
""""""
pass
# 通过装饰器的方式,将 ASCIIException 和 ascii_exception_handler 绑定在一起
@app.exception_handler(ASCIIException)
async def ascii_exception_handler(request: Request, exc: ASCIIException):
"""当引发 ASCIIException 的时候,会触发 ascii_exception_handler 的执行
同时会将 request 和 exception 传过去"""
return ORJSONResponse(status_code=404, content={"code": 404, "message": "你必须传递 ascii 字符串"})
@app.get("/items/{item_id}")
async def read_item(item_id: str):
if not item_id.isascii():
raise ASCIIException
return {"item": f"get {item_id}"}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
En cuanto a Solicitud y Respuesta, podemos importarlas no solo a través de fastapi, sino también a través de starlette, porque el mapeo de rutas de fastapi se realiza a través de starlette.
personalizado 404
Al acceder a una URL inexistente, deberíamos preguntarle al usuario, por ejemplo: Vas a ir a Marte a encontrar la página.
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
from fastapi.exceptions import StarletteHTTPException
import uvicorn
app = FastAPI()
@app.exception_handler(StarletteHTTPException)
async def not_found(request, exc):
return ORJSONResponse({"code": 404, "message": "您要找的页面去火星了。。。"})
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
En este punto, cuando accedemos a una URL que no existe, se devolverá nuestra cadena JSON personalizada.
Tarea en segundo plano
Las tareas en segundo plano son tareas que se ejecutan inmediatamente después de que se devuelve la respuesta.
Si una solicitud lleva mucho tiempo, podemos ejecutarla en segundo plano y FastAPI ya lo ha hecho por nosotros. Vamos a ver:
import time
from fastapi import FastAPI, BackgroundTasks
from starlette.background import BackgroundTask
from fastapi import Response, Request
import uvicorn
import orjson
app = FastAPI()
def send_email(email: str, message: str = ""):
"""发送邮件,假设耗时三秒"""
time.sleep(3)
print(f"三秒之后邮件发送给 {email!r}, 邮件信息: {message!r}")
@app.get("/user/{email}")
async def order(email: str, bg_tasks: BackgroundTasks):
"""这里需要多定义一个参数
此时任务就被添加到后台,当 Response 对象返回之后触发"""
bg_tasks.add_task(send_email, email, message="这是一封邮件")
# 我们在之前介绍 Response 的时候说过,里面有一个参数 background
# 所以我们也可以将任务放在那里面
# 因此我们还可以:
# return Response(
# orjson.dumps({"message": "邮件发送成功"}),
# background=BackgroundTask(send_email, email, message="这是一封邮件")
# )
return {"message": "邮件发送成功"}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
Enrutador API
APIRouter es similar al blueprint en Flask, que puede organizar mejor proyectos grandes, por ejemplo:
En mi directorio de proyectos actual, hay un directorio de aplicaciones y un main.py, y hay un app01.py en el directorio de aplicaciones, y luego veamos cómo están organizados.
aplicación/aplicación01.py
# app/app01.py
from fastapi import APIRouter
router = APIRouter(prefix="/router")
# 以后访问的时候要通过 /router/v1 来访问
@router.get("/v1")
async def v1():
return {"message": "hello world"}
principal.py
# main.py
from fastapi import FastAPI
from app.app01 import router
import uvicorn
app = FastAPI()
# 将 router 注册到 app 中,相当于 Flask 中的 register_blueprint
app.include_router(router)
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Luego se puede acceder desde el mundo exterior a través de /router/v1.
Ejemplo:
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
Haz lo mismo para todos los caminos:
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header
router = APIRouter(
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put(
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
Este ejemplo agrega prefijos, etiquetas, dependencias y vuelve a todas las rutas, en lugar de declarar cada ruta por separado, lo que simplifica el código.
software intermedio
Se puede decir que el middleware es muy común en el desarrollo web. Para decirlo sin rodeos, el middleware es una función o una clase. Antes de que la solicitud ingrese a la función de vista, pasará por el middleware (llamado middleware de solicitud), y en el middleware, podemos realizar algún procesamiento previo en la solicitud, o implementar un interceptor, etc.; de manera similar, cuando la función de vista devuelve una respuesta Después de eso, también pasará por el middleware (llamado middleware de respuesta), y en el middleware, también podemos pulir la respuesta.
software intermedio personalizado
FastAPI también admite middleware personalizado como Flask, pero hay middleware de solicitud y middleware de respuesta en Flask, pero en FastAPI, los dos se combinan en uno, echemos un vistazo al uso.
from fastapi import FastAPI, Request, Response
import uvicorn
import orjson
app = FastAPI()
@app.get("/")
async def view_func(request: Request):
return {"name": "古明地觉"}
@app.middleware("http")
async def middleware(request: Request, call_next):
"""
定义一个协程函数,然后使用 @app.middleware("http") 装饰,即可得到中间件
"""
# 请求到来时会先经过这里的中间件
if request.headers.get("ping", "") != "pong":
response = Response(content=orjson.dumps({"error": "请求头中缺少指定字段"}),
media_type="application/json",
status_code=404)
# 当请求头中缺少 "ping": "pong",在中间件这一步就直接返回了,就不会再往下走了
# 所以此时就相当于实现了一个拦截器
return response
# 然后,如果条件满足,则执行 await call_next(request),关键是这里的 call_next
# 如果该中间件后面还有中间件,那么 call_next 就是下一个中间件;如果没有,那么 call_next 就是对应的视图函数
# 这里显然是视图函数,因此执行之后会拿到视图函数返回的 Response 对象
# 所以我们看到在 FastAPI 中,请求中间件和响应中间件合在一起了
response: Response = await call_next(request)
# 这里我们在设置一个响应头
response.headers["status"] = "success"
return response
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
software intermedio incorporado
Al personalizar el middleware, podemos extender la función sin modificar la función de vista. Pero además del middleware personalizado, FastAPI también proporciona una gran cantidad de middleware integrado.
from fastapi import FastAPI
app = FastAPI()
# 要求请求协议必须是 https 或者 wss,如果不是,则自动跳转
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
app.add_middleware(HTTPSRedirectMiddleware)
# 请求中必须包含 Host 字段,为防止 HTTP 主机报头攻击,并且添加中间件的时候,还可以指定一个 allowed_hosts,那么它是干什么的呢?
# 假设我们有服务 a.example.com, b.example.com, c.example.com
# 但我们不希望用户访问 c.example.com,就可以像下面这么设置,如果指定为 ["*"],或者不指定 allow_hosts,则表示无限制
from starlette.middleware.trustedhost import TrustedHostMiddleware
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["a.example.com", "b.example.com"])
# 如果用户的请求头的 Accept-Encoding 字段包含 gzip,那么 FastAPI 会使用 GZip 算法压缩
# minimum_size=1000 表示当大小不超过 1000 字节的时候就不压缩了
from starlette.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000)
Además de estos, hay otro middleware incorporado, puede comprobarlo usted mismo, pero no se usa con mucha frecuencia.
CORS (Configuración de dominio cruzado)
CORS es demasiado importante, tenemos que hablar de ello por separado.
CORS (Cross-Origin Resource Sharing) se refiere a la situación en la que el front-end que se ejecuta en el navegador tiene un código JavaScript que se comunica con el back-end, pero el front-end y el back-end se encuentran en fuentes diferentes. Fuente: protocolo (http, https), dominio (baidu.com, app.com, localhost) y puerto (80, 443, 8000), siempre que haya una diferencia, es una fuente diferente. Por ejemplo, las siguientes son todas fuentes diferentes:
http://localhost
https://localhost
http://localhost:8080
Aunque ambos son host local, utilizan diferentes protocolos o puertos, por lo que tienen orígenes diferentes. Suponga que su frontend se ejecuta en localhost: 8080 e intenta comunicarse con localhost: 5555; luego, el navegador enviará una solicitud de OPCIONES HTTP al backend, y el backend enviará los encabezados apropiados para autorizar esta fuente; por lo tanto, el backend debe Hay un " fuente permitida". Si la fuente correspondiente al front-end está permitida, el navegador permitirá que el front-end envíe una solicitud al back-end, de lo contrario, se producirá una falla entre dominios.
De forma predeterminada, el front-end y el back-end deben estar en el mismo origen. Si el origen es diferente, la solicitud del front-end fallará. La separación de front-end y back-end ya se ha convertido en la corriente principal, por lo que se debe resolver el problema de los dominios cruzados.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
app = FastAPI()
app.add_middleware(
CORSMiddleware,
# 允许跨域的源列表,例如 ["http://www.example.org"] 等等,["*"] 表示允许任何源
allow_origins=["*"],
# 跨域请求是否支持 cookie,默认是 False,如果为 True,allow_origins 必须为具体的源,不可以是 ["*"]
allow_credentials=False,
# 允许跨域请求的 HTTP 方法列表,默认是 ["GET"]
allow_methods=["*"],
# 允许跨域请求的 HTTP 请求头列表,默认是 [],可以使用 ["*"] 表示允许所有的请求头
# 当然 Accept、Accept-Language、Content-Language 以及 Content-Type 总之被允许的
allow_headers=["*"],
# 可以被浏览器访问的响应头, 默认是 [],一般很少指定
# expose_headers=["*"]
# 设定浏览器缓存 CORS 响应的最长时间,单位是秒。默认为 600,一般也很少指定
# max_age=1000
)
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Lo anterior puede resolver el problema de dominio cruzado.
Úselo CORSMiddleware
para configurar varios dominios:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost.tiangolo.com",
"https://localhost.tiangolo.com",
"http://localhost",
"http://localhost:8080",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def main():
return {"message": "Hello World"}
Soporta los siguientes parámetros:
allow_origins
- Una lista de orígenes que permiten solicitudes de origen cruzado. ej['https://example.org', 'https://www.example.org']
. Puede usar['*']
permitir cualquier origen.allow_origin_regex
- Una cadena de expresión regular que coincide con orígenes que permiten solicitudes de origen cruzado. ej'https://.*\.example\.org'
.allow_methods
- Una lista de métodos HTTP permitidos para solicitudes de origen cruzado. El valor predeterminado es['GET']
. Puede utilizar['*']
para permitir todos los métodos estándar.allow_headers
- Una lista de encabezados HTTP que permiten solicitudes de origen cruzado. El valor predeterminado es[]
. Puede utilizar['*']
el encabezado de solicitud permitir todo.Accept
Los encabezados de solicitudAccept-Language
,Content-Language
yContent-Type
siempre permiten solicitudes CORS.allow_credentials
- Indica que las solicitudes de origen cruzado admiten cookies. El valor predeterminado esFalse
. Además,allow_origins
no se puede configurar cuando el certificado está permitido['*']
y se debe especificar la fuente.expose_headers
- Indica los encabezados de respuesta a los que puede acceder el navegador. El valor predeterminado es[]
.max_age
- Establezca el tiempo máximo para que el navegador almacene en caché las respuestas CORS, en segundos. El valor predeterminado es600
.
operación avanzada
Mire algunas operaciones de alto nivel de FastAPI. Algunas de estas operaciones pueden no estar disponibles, pero será mucho más conveniente usarlas.
otras respuestas
Los datos json devueltos pueden ser: JSONResponse, UJSONResponse, ORJSONResponse, el tipo de contenido es application/json; el html devuelto es HTMLResponse, el tipo de contenido es text/html; el PlainTextResponse devuelto, el tipo de contenido es text/plain. Pero también podemos tener tres tipos de respuestas, a saber, redirección de retorno, flujo de bytes y archivo.
redirigir
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
import uvicorn
app = FastAPI()
@app.get("/index")
async def index():
return RedirectResponse("https://www.bilibili.com")
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Al visitar /index en la página, saltará a bilibili.
flujo de bytes
Devolver un flujo de bytes requiere el uso de un generador asíncrono:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import uvicorn
app = FastAPI()
async def some_video():
for i in range(5):
yield f"video {i} bytes ".encode("utf-8")
@app.get("/index")
async def index():
return StreamingResponse(some_video())
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
Si hay un objeto de archivo, también se puede devolver directamente.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import uvicorn
app = FastAPI()
@app.get("/index")
async def index():
return StreamingResponse(open("main.py", encoding="utf-8"))
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
documento
Si devuelve un archivo, también puede pasar FileResponse:
from fastapi import FastAPI
from fastapi.responses import FileResponse
import uvicorn
app = FastAPI()
@app.get("/index")
async def index():
# filename 如果给出,它将包含在响应的 Content-Disposition 中。
return FileResponse("main.py", filename="这不是main.py")
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
autenticación HTTP
¿Qué pasa si queremos que el usuario introduzca un nombre de usuario y una contraseña para confirmar su identidad al acceder a una solicitud?
from fastapi import FastAPI, Depends
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import uvicorn
app = FastAPI()
security = HTTPBasic()
@app.get("/index")
async def index(credentials: HTTPBasicCredentials = Depends(security)):
return {"username": credentials.username, "password": credentials.password}
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
prueba
Una vez completada la entrada, la información se guardará en las credenciales y podremos obtenerla para su verificación.
enchufe web
Cómo FastAPI implementa websocket:
from fastapi import FastAPI
from fastapi.websockets import WebSocket
import uvicorn
app = FastAPI()
@app.websocket("/ws")
async def ws(websocket: WebSocket):
await websocket.accept()
while True:
# websocket.receive_bytes()
# websocket.receive_json()
data = await websocket.receive_text()
await websocket.send_text(f"收到来自客户端的回复: {data}")
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=5555)
Luego nos comunicamos a través del navegador:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
ws = new WebSocket("ws://localhost:5555/ws");
//如果连接成功, 会打印下面这句话, 否则不会打印
ws.onopen = function () {
console.log('连接成功')
};
//接收数据, 服务端有数据过来, 会执行
ws.onmessage = function (event) {
console.log(event)
};
//服务端主动断开连接, 会执行.
//客户端主动断开的话, 不执行
ws.onclose = function () { }
</script>
</body>
</html>
prueba
Ejemplo:
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
app = FastAPI()
html = """
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<form action="" onsubmit="sendMessage(event)">
<input type="text" id="messageText" autocomplete="off"/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var ws = new WebSocket("ws://localhost:8000/ws");
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""
@app.get("/")
async def get():
return HTMLResponse(html)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message text was: {data}")
Despliegue del servicio FastAPI
Cuando se usa un marco asíncrono, lo más importante es usar un controlador asíncrono para acceder a la base de datos, porque el cuello de botella de los servicios web está en la base de datos.
La mayoría de los contenidos de FastAPI se han presentado anteriormente, y luego echemos un vistazo a la implementación de los servicios de FastAPI. De hecho, la implementación es muy simple, simplemente ejecute uvicorn.run. Pero hay muchos parámetros aquí, principalmente queremos presentar estos parámetros.
def run(app, **kwargs):
config = Config(app, **kwargs)
server = Server(config=config)
...
...
Vemos que tanto app como **kwargs se pasan a Config, por lo que solo necesitamos ver qué parámetros hay en Config. Aquí hay una selección:
app:第一个参数,不需要解释
host:监听的ip
port:监听的端口
uds:绑定的 unix domain socket,一般不用
fd:从指定的文件描述符中绑定 socket
loop:事件循环实现,可选项为 auto|asyncio|uvloop|iocp
http:HTTP 协议实现,可选项为 auto|h11|httptools
ws:websocket 协议实现,可选项为 auto|none|websockets|wsproto
lifespan:lifespan 实现,可选项为 auto|on|off
env_file:环境变量配置文件
log_config:日志配置文件
log_level:日志等级
access_log:是否记录日志
use_colors:是否带颜色输出日志信息
interface:应用接口,可选 auto|asgi3|asgi2|wsgi
debug:是否开启 debug 模式
reload:是否自动重启
reload_dirs:要自动重启的目录
reload_delay:多少秒后自动重启
workers:工作进程数
limit_concurrency:并发的最大数量
limit_max_requests:能 hold 住的最大请求数
3. Ejemplo: interfaz de desarrollo fastapi (solo interfaz api, sin representación web)
Ejemplo: Top250 películas de Douban
import requests
from scrapy.http import HtmlResponse
import uvicorn
from pathlib import Path
from fastapi import FastAPI
app = FastAPI()
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.62",
}
def douban_movie_top250(page_num: int):
index = (page_num - 1) * 25
# https://movie.douban.com/top250?start=50&filter=
url = f"https://movie.douban.com/top250?start={index}&filter="
__resp = requests.get(url, headers=headers)
if 200 == __resp.status_code:
resp = HtmlResponse(url, body=__resp.content, encoding='utf-8')
movie_name_list = resp.xpath('//div[@class="item"]//div[@class="hd"]/a/span[1]/text()').extract()
movie_url_list = resp.xpath('//div[@class="item"]//div[@class="hd"]/a/@href').extract()
movie_info_list = list(zip(movie_name_list, movie_url_list))
return movie_info_list
else:
return {'请求失败': f" status_code ---> {__resp.status_code}"}
@app.get("/douban/movie_top250")
async def get_item(page_num):
"""和 Flask 不同,Flask 是使用 <>,而 FastAPI 使用 {}"""
try:
page_num_int = int(page_num)
except BaseException as be:
return {"错误信息": "页码必须是数字"}
data = douban_movie_top250(page_num_int)
return {"data": data}
if __name__ == '__main__':
'''
http://127.0.0.1:5555/douban/movie_top250?page_num=1
'''
print(f'{Path(__file__).stem}:app')
uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
pass
Acceso: http://127.0.0.1:5555/douban/movie_top250?page_num=5
Ejemplo: Obtención de subtítulos de video en la estación B
import uvicorn
from pathlib import Path
from fastapi import FastAPI
import requests
import json
from scrapy.http import HtmlResponse
app = FastAPI()
headers = {
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cookie': 'buvid3=31D606A5-C08F-CF7F-6345-DFE18CDF1FCA25535infoc; b_nut=1681180325; CURRENT_FNVAL=4048; _uuid=FA2D2910A-1F4C-9ED10-24FB-710C228FE726630982infoc; buvid_fp=dd9cac90362a92030f254a522e274486; buvid4=F13E840C-6245-40B7-9B63-5FDD1992821229542-023041110-0bxmZkxc4Ip6QXGeEfs0Og%3D%3D; CURRENT_PID=130742c0-d811-11ed-ac38-37fb01852f74; rpdid=|(JYYkY~RRYu0J\'uY)uk~lJY|; i-wanna-go-back=-1; header_theme_version=CLOSE; home_feed_column=5; is-2022-channel=1; nostalgia_conf=-1; DedeUserID=384760568; DedeUserID__ckMd5=8fd50449771672ee; b_ut=5; FEED_LIVE_VERSION=V_NO_BANNER_1; bsource=search_bing; browser_resolution=1863-969; bp_video_offset_384760568=787947892852654100; b_lsid=C105138DE_187B25E90A6; SESSDATA=d3f7b6a0%2C1697876752%2C413d0%2A42; bili_jct=e41d9dfdbd372b0cb95222cfa0d33199; sid=59e50ddx; innersign=1; PVID=1; innersign=1'
}
def get_subtitle(video_id=None):
if video_id:
url = f'https://www.bilibili.com/video/{video_id}/'
resp = requests.get(url, headers=headers)
if 200 == resp.status_code:
scrapy_resp = HtmlResponse(url, body=resp.content, encoding='utf-8')
try:
temp = scrapy_resp.css('html').re('"subtitle_url":"(.*?)"')[0]
except BaseException as be:
return {'请求失败': str(be)}
subtitle_url = temp.replace(r'\u002F', '/')
print(subtitle_url)
r = requests.get(subtitle_url)
if 200 == r.status_code:
return r.json()
else:
return {'请求失败, 失败状态码': resp.status_code}
else:
return {'请求失败, 失败状态码': resp.status_code}
else:
return {"请求失败": '视频 id 错误'}
@app.get("/bilibili/{video_id}")
async def get_item(video_id):
"""和 Flask 不同,Flask 是使用 <>,而 FastAPI 使用 {}"""
data = get_subtitle(video_id)
return {"data": data}
if __name__ == '__main__':
'''
http://127.0.0.1:5555/bilibili/BV1bW411n7fY
'''
print(f'{Path(__file__).stem}:app')
uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
pass
4. Ejemplo: fastapi desarrolla un sitio web de renderizado web
FastAPI, un marco web de Python, no viene con un " motor de plantillas para renderizar páginas web ", pero debido a esto, puede usar cualquier plantilla web. El ejemplo oficial es jinjia2.
Las plantillas son una parte importante del desarrollo web full-stack. Con Jinja, puede crear plantillas enriquecidas que potencian el front-end de sus aplicaciones web de Python.
Jinja es un motor de plantillas escrito en Python diseñado para ayudar con el proceso de representación de las respuestas de la API. En todos los lenguajes de plantillas, hay variables que se reemplazan con los valores que realmente se les pasan, y hay etiquetas que controlan la lógica de la plantilla cuando se representa la plantilla.
instalar jinja2
Instalación: pip install jinja2 aiofiles
Una plantilla Jinja es solo un archivo de texto. Jinja puede generar cualquier formato basado en texto (HTML, XML, CSV, LaTeX, etc.). Las plantillas de Jinja no necesitan tener una extensión específica: .html, .xml o cualquier otra extensión servirá.
Respecto a las extensiones para plantillas: Cualquier archivo se puede cargar como plantilla, independientemente de la extensión del archivo. Agregar una extensión .jinja como user.html.jinja puede facilitar algunos complementos del IDE o del editor, pero no es obligatorio. El escape automático se puede aplicar en función de la extensión del archivo, por lo que debe tener en cuenta el sufijo adicional en este caso.
Otra buena heurística para identificar plantillas es que se encuentran en la carpeta de plantillas, independientemente de la extensión. Este es un diseño común para los proyectos.
El motor de plantillas de Jinja utiliza llaves {} para distinguir sus expresiones y sintaxis del HTML normal, el texto y cualquier otra variable en los archivos de plantilla. La sintaxis { {}} se denomina bloque variable. La sintaxis {% %} incluye estructuras de control como if/else, bucles y macros. Tres bloques de sintaxis comunes utilizados en el lenguaje de plantillas Jinja incluyen los siguientes:
- {% ... %}: esta sintaxis se usa para declaraciones como estructuras de control.
- { { todo.item }}: esta sintaxis se utiliza para imprimir el valor de la expresión que se le pasa.
- {# Prueba #}: esta sintaxis se usa al escribir un comentario, no se muestra en la página.
Jinja2 es un lenguaje de plantillas popular utilizado por Flask, Bottle, Pelican y también por Django.
Renderiza la primera plantilla de Jinja
código:
import jinja2
environment = jinja2.Environment()
template = environment.from_string("Hello, {
{ name }}!")
result = template.render(name="渲染第一个jinja2模板")
print(result)
El componente central de Jinja es la clase Environment(). En este ejemplo, se crea un entorno Jinja sin ningún parámetro. Luego use environment.from_string para personalizar el entorno. Aquí hay que crear un entorno común y cargar la cadena Hello, { { name }}! como plantilla en él.
Este ejemplo muestra dos pasos importantes que generalmente se realizan cuando se usa Jinja:
- Cargar plantilla: carga una fuente que contiene variables de marcador de posición. De forma predeterminada, están encerrados entre un par de llaves { { }}.
- Renderizar plantilla: llenar marcadores de posición con contenido. Puede proporcionar un diccionario o argumentos de palabras clave como contexto.
Los resultados de la ejecución son los siguientes:
Usar archivos externos como plantillas
De la misma manera que arriba, podemos usar un archivo externo como nuestra fuente de plantilla y crear una nueva carpeta en nuestro proyecto. En su directorio de trabajo, cree una carpeta llamada templates/.
Luego puede crear archivos de plantilla index.html en el directorio de plantillas y representarlos usando la sintaxis de Jinja2. Por ejemplo, escriba el siguiente contenido en template/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Bienvenido</title>
<link href="{ {
url_for('static', path='/styles.css') }}" rel="stylesheet">
</head>
<body>
<h1>Hola, {
{ nombre }}</h1>
</body>
</html>
De vuelta en main.py:
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
@app.get("/{name}")
async def home(request: Request, name: str):
return templates.TemplateResponse("index.html", {
"request": request,
"name": name
})
if __name__ == '__main__':
pass
La estructura de directorios de todo el archivo es la siguiente:
Inicie el servicio FastAPI: uvicorn main:app --reload --port 8888
Luego abra otra terminal y ejecute el comando curl 127.0.0.1:8888/Yuzhou1su, y podrá ver el resultado del nombre representado de la siguiente manera:
Acceda a este http://127.0.0.1:8888/Yuzhou1su a través de un navegador para ver el color representado por css:
Las variables de plantilla de Jinja pueden ser cualquier tipo u objeto de Python siempre que se puedan convertir en cadenas. Se puede pasar un tipo de modelo, lista o diccionario a la plantilla y mostrar sus propiedades colocando esas propiedades en el segundo bloque enumerado anteriormente. En la siguiente sección, echaremos un vistazo a los filtros. Los filtros son una parte importante de todo motor de plantillas, y en Jinja los filtros nos permiten realizar ciertas funciones como concatenar valores de una lista y recuperar la longitud de un objeto, entre otras. Funciones de uso común en Jinja: variables, filtros, sentencias if, bucles, macros y herencia de plantillas.
variable
Las variables de plantilla están definidas por el diccionario de contexto pasado a la plantilla.
En las plantillas, puede manipular las variables siempre que la aplicación las pase. Las variables también pueden tener atributos o elementos a los que puede acceder. Las propiedades que tiene una variable dependen de la aplicación que proporciona la variable.
Además de la sintaxis de "subíndice" estándar de Python __getitem__ ( [] ), también puede usar un punto ( . ) para acceder a los atributos de una variable.
Las siguientes líneas hacen lo mismo:
{
{ foo.bar }}
{
{ foo['bar'] }}
filtros
Aunque la sintaxis de Python y Jinja es muy similar, las operaciones de modificación como concatenar cadenas, establecer el primer carácter de una cadena en mayúsculas, etc. no se pueden realizar en Jinja utilizando la sintaxis de Python. Por lo tanto, para realizar tales operaciones de modificación, usamos filtros en Jinja.
Las variables pueden ser modificadas por filtros. Los filtros se separan de las variables con un símbolo de barra vertical (|) y los parámetros opcionales se pueden incluir entre paréntesis. Se pueden encadenar varios filtros. La salida de un filtro se aplica al siguiente. El formato de definición del filtro es el siguiente:
{
{ variable | filter_name(*args) }}
Filtrar sin parámetros:
{
{ variable | filter_name }}
{
{ name|striptags|title }}
filtro predeterminado : si el valor no está definido, devolverá el valor predeterminado pasado; de lo contrario, devolverá el valor de la variable:
{
{ my_variable | default('my_variable is not defined') }}
filtro de escape : este filtro se usa para representar la salida HTML sin procesar: convierta los caracteres & < > ' " en la cadena s a secuencias seguras de HTML. Use esta opción si necesita mostrar texto en HTML que puede contener dichos caracteres. Marca el retorno valor como una cadena tokenizada.
{
{ "<title>Todo Application</title>" | escape }}
<title>Todo Application</title>
Filtros de conversión de tipo : estos filtros incluyen filtros int y float para convertir de un tipo de datos a otro:
{
{ 3.142 | int }}
3
{
{ 20 | float }}
20.0
filtro de unión : join(*value*, *d=u''* , *attribute=None*) devuelve una cadena que es la concatenación de las cadenas en la secuencia. El separador entre elementos por defecto es una cadena vacía, puede definirlo con un parámetro opcional:
{
{ [1, 2, 3] | join('|') }}
-> 1|2|3
{
{ [1, 2, 3] | join }}
-> 123
También es posible concatenar ciertas propiedades de los objetos:
{
{ users|join(', ', attribute='username') }}
Filtro de longitud : este filtro devuelve la longitud de una secuencia o colección, que funciona igual que la función len() en Python:
Todo count: {
{ todos | length }}
Todo count: 4
si condición
El uso de la sentencia if en Jinja es similar al uso en Python. Se usa dentro de {% %} bloques de control. Veamos un ejemplo:
{% if todos %}
<ul>
{% for todo in todos %}
<li>{
{ todo.name|e }}</li>
{% endfor %}
</ul>
{% endif %}
Condición de bucle
También podemos iterar sobre variables en Jinja. Puede ser una lista o una función general, digamos esta, por ejemplo
{% for todo in todos %}
<li>{
{ todo.name|e }}</li>
{% endfor %}
Puede acceder a variables especiales dentro de un bucle for, como loop.index, que proporciona el índice de la iteración actual.
macro
Las macros son comparables a las funciones en los lenguajes de programación regulares. Ayudan a poner modismos comunes en funciones reutilizables para que no se repita (el principio "SECO").
{% macro input(name, value='', type='text', size=20) %}
<div class="form">
<input type="{
{ type }}" name="{
{ name }}"
value="{
{ value|escape }}" size="{
{ size }}">
</div>
{% endmacro %}
Ahora, para crear rápidamente una entrada en su formulario, llame a esta macro:
{
{ input('item') }}
Una vez completada la representación, devolverá:
<div class="form">
<input type="text" name="item" value="" size="20" />
</div>
Jinja en Fast API
FastAPI en realidad está diseñado para crear API y microservicios. Se puede usar para crear aplicaciones web que sirvan HTML usando Jinja, pero eso no es realmente para lo que está optimizado.
Si desea crear un sitio web grande que represente una gran cantidad de HTML en el servidor, Django podría ser una mejor opción.
Sin embargo, si está creando un sitio web moderno con un marco de front-end como React, Angular o Vue, obtener datos de FastAPI es una excelente opción.
5. FastAPI, sitio web de desarrollo vue
5.1 Comunicación entre FastAPI y Vue3
:https://zhuanlan.zhihu.com/p/632387477
Opere la base de datos basada en Vue3 y FastAPI: https://zhuanlan.zhihu.com/p/632393099
5.2 Conecte Vue.js como front-end y Fastapi como back-end
:https://www.cnblogs.com/jajaja111122222/p/15904405.html
Estructura de directorios
├── main.py
└── plantillas
└── home.html
back-end fastapi
pip instalar fastapi[todos]
pip instalar jinja2
principal.py
- Servimos nuestra interfaz en / y representamos nuestro home.html en esa ruta.
- Usamos la carpeta de plantillas para guardar nuestro HTML y pasarlo a Jinja.
- Además, enviaremos una solicitud a /add desde nuestro front-end.
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
templates = Jinja2Templates(directory="templates")
app = FastAPI()
class TextArea(BaseModel):
content: str
@app.post("/add")
async def post_textarea(data: TextArea):
print(data.dict())
return {**data.dict()}
@app.get("/")
async def serve_home(request: Request):
return templates.TemplateResponse("home.html", {"request": request})
Interfaz - inicio.html
- Vamos a crear una aplicación ficticia con un área de texto y un botón.
- Estamos utilizando Axios para enviar solicitudes al backend.
- Dado que se ejecutan en el mismo puerto, podemos pasar / agregar directamente a Axios.
<html>
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<body>
<div id="app">
<textarea name="" id="content" cols="30" rows="10" v-model="content"></textarea>
<button @click="addText" id="add-textarea">click me</button>
</div>
<script>
new Vue({
el: "#app",
data: {
title: '',
content: ''
},
methods: {
addText() {
return axios.post("/add", {
content: this.content
}, {
headers: {
'Content-type': 'application/json',
}
}).then((response) => {
console.log("content: " + this.content);
});
}
}
});
</script>
</body>
</html>
ejecutar, prueba de acceso
Comando: uvicorn principal: aplicación --recargar
Al final, tendrás un área de texto horrible y un botón. Pero te ayudará a entender mejor las cosas.
5.3 FastApi+Vue+LayUI implementa una demostración simple de separación de front-end y back-end
En el uso real, generalmente se recomienda separar los proyectos de front-end y back-end. A continuación, use FastApi+Vue+LayUI para hacer una demostración con separación de front-end y back-end.
extremo posterior
El backend adopta FastApi, código test.py
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import JSONResponse
from pathlib import Path
import uvicorn
import subprocess
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get('/info')
async def user_list():
# vue的响应数据
ret_list = [
{'id': '1', 'value': 'one'},
{'id': '2', 'value': 'two'},
{'id': '3', 'value': 'three'},
]
return JSONResponse(content=ret_list)
@app.get("/check")
async def home(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
if __name__ == '__main__':
'''
http://127.0.0.1:5555/check
'''
# print(f'{Path(__file__).stem}:app')
uvicorn.run(f'{Path(__file__).stem}:app', host="0.0.0.0", port=5555)
pass
Interfaz
El front-end importa directamente los recursos CDN de Vue, LayUI y Axios JS y CSS. En la fase de montaje de la instancia de Vue, use axios para llamar a la interfaz de back-end para obtener los datos y use el estilo de LayUI para embellecer el elemento de la mesa.
el código
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!-- 引入 layui.css -->
<link rel="stylesheet" href="https://www.layuicdn.com/layui/css/layui.css"/>
<!-- 引入 layui.js -->
<script src="https://www.layuicdn.com/layui/layui.js" type="text/javascript" charset="utf-8"></script>
<title>Home</title>
</head>
<body>
<div id="app">
<table class="layui-table">
<tr v-for="fo in info_list">
<td> [[ fo.id ]] </td>
<td> [[ fo.value ]] </td>
</tr>
</table>
</div>
<table id="test" class="layui-table"></table>
<script>
const {createApp, ref} = Vue
const vue_app = createApp({
data() {
return {
info_list: [{id: 1, name: '默认值'}],
// info: "hello vue..."
}
},
mounted() {
this.showinfo();
},
methods: {
showinfo() {
axios.get('/info').then(response => {
this.info_list = response.data;
// console.log(response);
console.log(`this.info_list ---> ${this.info_list.toString()}`);
}, err => {
console.log(err);
})
}
}
});
// vue_app.config.delimiters = ['[[', ']]'];
vue_app.config.compilerOptions.delimiters = ['[[', ']]']
vue_app.mount('#app');
</script>
</body>
</html>
Tanto vue como jinja2 usan "{ {content}}" para mostrar el valor de las variables en la parte delantera de forma predeterminada, por lo que causará conflictos.
Puede modificar la forma en que vue muestra los valores, es decir, modificar el "interpolador":
vue 2 vías:
<script>
const vue = new Vue({ el:"#app", delimitadores: ["[[", "]]"], data:{ selects:['enu','cha'], userData:[] } </script>
La vía vue 3:
En Vue 3, la notación de diferencia predeterminada son llaves dobles ( { { }} ) para representar datos dinámicos en plantillas. Sin embargo, si desea modificar la notación predeterminada para la interpolación, Vue 3 proporciona una forma sencilla de hacerlo.
Antes de crear una instancia de la aplicación Vue, use el método de configuración de la función createApp para configurar el símbolo delta global:
import { createApp } from 'vue';
const app = createApp({});
//app.config.delimiters = [ ' psapp.config.compilerOptions.delimiters = ['${', '}'];
app.mount('#app');
En el código anterior, usamos app.config.delimiters para modificar el símbolo de diferencia a ${ }.
Después de la modificación, puede usar el nuevo símbolo delta en la plantilla para mostrar datos dinámicos:
<template>
<div>
<p>${ message }</p>
</div>
</template><script>
export default { data() { return { mensaje: "¡Hola, mundo!" }; } }; </script> En el ejemplo anterior, usamos la notación ${ } delta para mostrar los datos del mensaje. Cabe señalar que después de modificar el símbolo de diferencia, debe asegurarse de que el nuevo símbolo de diferencia no entre en conflicto con el nombre de la variable en la plantilla. Al mismo tiempo, la modificación del signo de diferencia solo es válida dentro del alcance de la instancia de la aplicación actual y no afectará a otras instancias de la aplicación.
ejecutar proyecto
Inicie el servidor backend de FastApi y acceda a la interfaz /test/check.
Explicación detallada del uso de axios para enviar solicitudes de obtención y publicación
:https://blog.csdn.net/grand_brol/article/details/108167088
Preguntas y respuestas
P: ¿Por qué /info
siempre hay una redirección en la interfaz de solicitud Temporary Redirect
?
R: La razón es que cuando definimos FastApi
la interfaz, el formato uri
no estaba estandarizado y el uri
final del .Redirigir la solicitud que el navegador no tiene a la función de vista que definimos ./
/
uri
/
FastApi
/
/
5.4 Use python fastapi+vue para construir rápidamente un sitio web
:https://www.elprup.com/2020/09/19/fastapi_vue/
Un sitio web tradicional se lleva a cabo por completo con un marco web, como express, koa basado en nodejs, django y tornado basado en python. El nuevo tipo de sitio web se ha convertido en un modo de desarrollar el sistema front-end con Vue y usar el marco API para desarrollar la interfaz de solicitud de API back-end.
5.5 Desarrollar una aplicación de una sola página con FastAPI y Vue.js
Dirección original: https://testdriven.io/blog/developing-a-single-page-app-with-fastapi-and-vuejs/
Dirección de origen: https://github.com/testdrivenio/fastapi-vue
Traducción 1: https://juejin.cn/post/7113790977848360967
Traducción 2: https://www.cnblogs.com/leimu/p/16992966.html