Tutorial de FastAPI, combinado con vue para lograr la separación de front-end y back-end

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/

Guía de usuario avanzada: https://fastapi.tiangolo.com/zh/advanced/

Concurrencia, implementación, plantillas de proyectos de compilación

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 el
  • ge:mayor o igual a
  • lt: menos que
  • le: 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  . EnumAl  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_pathuna  :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 es 0
  • limit: El valor correspondiente es 10

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 tipo q
  • int Un parámetro de consulta opcional  de tipo  skip, con un valor predeterminado de 0
  • int Un parámetro de consulta opcional  de tipo  limit, con un valor predeterminado de 100

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_modelpará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 establecidos
  • response_model_include Qué atributos se incluyen
  • response_model_exclude omitir ciertos atributos

status_codepará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}

FileEl 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_descriptionla 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 o  PATCH.

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 BaseModel

aplicació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  Pathy Query parámetros del cuerpo de la solicitud

Siéntase libre de mezclar use  Pathy 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 BaseModel

aplicació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 BaseModel

aplicació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 BaseModel

aplicació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 BaseModel

aplicació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_pathpara 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 CORSMiddlewarepara 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. AcceptLos encabezados de solicitud Accept-Language, Content-Language y  Content-Type siempre permiten solicitudes CORS.
  • allow_credentials - Indica que las solicitudes de origen cruzado admiten cookies. El valor predeterminado es  False. 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 es  600.

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:

  1. Cargar plantilla: carga una fuente que contiene variables de marcador de posición. De forma predeterminada, están encerrados entre un par de llaves { { }}.
  2. 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 = [ ' ps

app.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é /infosiempre hay una redirección en la interfaz de solicitud Temporary Redirect?

R: La razón es que cuando definimos FastApila interfaz, el formato urino estaba estandarizado y el urifinal 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

Supongo que te gusta

Origin blog.csdn.net/freeking101/article/details/132039250
Recomendado
Clasificación