Тіло запиту - Вкладені моделі¶
З FastAPI Ви можете визначати, перевіряти, документувати та використовувати моделі, які можуть бути вкладені на будь-яку глибину (завдяки Pydantic).
Поля списку¶
Ви можете визначити атрибут як підтип. Наприклад, Python-список (list
):
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: list = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Це зробить tags
списком, хоча не визначається тип елементів списку.
Поля списку з параметром типу¶
Але Python має специфічний спосіб оголошення списків з внутрішніми типами або "параметрами типу":
Імпортуємо List
з модуля typing¶
У Python 3.9 і вище можна використовувати стандартний list
для оголошення таких типів, як ми побачимо нижче. 💡
Але в Python версії до 3.9 (від 3.6 і вище) спочатку потрібно імпортувати List
з модуля стандартної бібліотеки Python typing
:
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: list[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Оголошення list
з параметром типу¶
Щоб оголосити типи з параметрами типу (внутрішніми типами), такими як list
, dict
, tuple
:
- Якщо Ви використовуєте версію Python до 3.9, імпортуйте їх відповідну версію з модуля
typing
. - Передайте внутрішні типи як "параметри типу", використовуючи квадратні дужки:
[
and]
.
У Python 3.9 це буде виглядати так:
my_list: list[str]
У версіях Python до 3.9 це виглядає так:
from typing import List
my_list: List[str]
Це стандартний синтаксис Python для оголошення типів.
Використовуйте той самий стандартний синтаксис для атрибутів моделей з внутрішніми типами.
Отже, у нашому прикладі, ми можемо зробити tags
саме "списком рядків":
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: list[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Типи множин¶
Але потім ми подумали, що теги не повинні повторюватися, вони, ймовірно, повинні бути унікальними рядками.
І Python має спеціальний тип даних для множин унікальних елементів — це set
.
Тому ми можемо оголосити tags
як множину рядків:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Навіть якщо Ви отримаєте запит з дубльованими даними, він буде перетворений у множину унікальних елементів.
І коли Ви будете виводити ці дані, навіть якщо джерело містить дублікати, вони будуть виведені як множина унікальних елементів.
І це буде анотовано/документовано відповідно.
Вкладені моделі¶
Кожен атрибут моделі Pydantic має тип.
Але цей тип сам може бути іншою моделлю Pydantic.
Отже, Ви можете оголосити глибоко вкладені JSON "об'єкти" з конкретними іменами атрибутів, типами та перевірками.
Усе це, вкладене без обмежень.
Визначення підмоделі¶
Наприклад, ми можемо визначити модель Image
:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Використання підмоделі як типу¶
А потім ми можемо використовувати її як тип атрибута:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Це означатиме, що FastAPI очікуватиме тіло запиту такого вигляду:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
Завдяки такій декларації у FastAPI Ви отримуєте:
- Підтримку в редакторі (автозавершення тощо), навіть для вкладених моделей
- Конвертацію даних
- Валідацію даних
- Автоматичну документацію
Спеціальні типи та валідація¶
Окрім звичайних типів, таких як str
, int
, float
, та ін. Ви можете використовувати складніші типи, які наслідують str
.
Щоб побачити всі доступні варіанти, ознайомтеся з оглядом типів у Pydantic. Деякі приклади будуть у наступних розділах.
Наприклад, у моделі Image
є поле url
, тому ми можемо оголосити його як HttpUrl
від Pydantic замість str
:
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Рядок буде перевірено як дійсну URL-адресу і задокументовано в JSON Schema / OpenAPI як URL.
Атрибути зі списками підмоделей¶
У Pydantic Ви можете використовувати моделі як підтипи для list
, set
тощо:
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
images: Union[list[Image], None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import List, Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
images: Union[List[Image], None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
Це означає, що FastAPI буде очікувати (конвертувати, валідувати, документувати тощо) JSON тіло запиту у вигляді:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": [
"rock",
"metal",
"bar"
],
"images": [
{
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
},
{
"url": "http://example.com/dave.jpg",
"name": "The Baz"
}
]
}
Інформація
Зверніть увагу, що тепер ключ images
містить список об'єктів зображень.
Глибоко вкладені моделі¶
Ви можете визначати вкладені моделі довільної глибини:
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None
class Offer(BaseModel):
name: str
description: str | None = None
price: float
items: list[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
🤓 Other versions and variants
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
images: Union[list[Image], None] = None
class Offer(BaseModel):
name: str
description: Union[str, None] = None
price: float
items: list[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
from typing import List, Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
images: Union[List[Image], None] = None
class Offer(BaseModel):
name: str
description: Union[str, None] = None
price: float
items: List[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
Інформація
Зверніть увагу, що в моделі Offer
є список Item
ів, які, своєю чергою, можуть мати необов'язковий список Image
ів.
Тіла запитів, що складаються зі списків¶
Якщо верхній рівень JSON тіла, яке Ви очікуєте, є JSON масивом
(у Python — list
), Ви можете оголосити тип у параметрі функції, як і в моделях Pydantic:
images: List[Image]
images: list[Image]
наприклад:
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
return images
🤓 Other versions and variants
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
@app.post("/images/multiple/")
async def create_multiple_images(images: List[Image]):
return images
Підтримка в редакторі всюди¶
Ви отримаєте підтримку в редакторі всюди.
Навіть для елементів у списках:
Ви не змогли б отримати таку підтримку в редакторі, якби працювали напряму зі dict
, а не з моделями Pydantic.
Але Вам не потрібно турбуватися про це: вхідні dict'и автоматично конвертуються, а вихідні дані автоматично перетворюються в JSON.
Тіла з довільними dict
¶
Ви також можете оголосити тіло як dict
з ключами одного типу та значеннями іншого типу.
Це корисно, якщо Ви не знаєте наперед, які імена полів будуть дійсними (як у випадку з моделями Pydantic).
Це буде корисно, якщо Ви хочете приймати ключі, які заздалегідь невідомі.
Це також зручно, якщо Ви хочете мати ключі іншого типу (наприклад, int
).
Ось що ми розглянемо далі.
У цьому випадку Ви можете приймати будь-який dict
, якщо його ключі — це int
, а значення — float
:
from fastapi import FastAPI
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
return weights
🤓 Other versions and variants
from typing import Dict
from fastapi import FastAPI
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: Dict[int, float]):
return weights
Порада
Майте на увазі, що в JSON тілі ключі можуть бути лише рядками (str
).
Але Pydantic автоматично конвертує дані.
Це означає, що навіть якщо клієнти вашого API надсилатимуть ключі у вигляді рядків, якщо вони містять цілі числа, Pydantic конвертує їх і проведе валідацію.
Тобто dict
, який Ви отримаєте як weights
, матиме ключі типу int
та значення типу float
.
Підсумок¶
З FastAPI Ви маєте максимальну гнучкість завдяки моделям Pydantic, зберігаючи при цьому код простим, коротким та елегантним.
А також отримуєте всі переваги:
- Підтримка в редакторі (автодоповнення всюди!)
- Конвертація даних (парсинг/сериалізація)
- Валідація даних
- Документація схем
- Автоматичне створення документації