В прошлой теме рассматривалось получение данных из тела запроса с помощью класса fastapi.Body в виде словаря или отдельных его значений. Однако FastAPI также
позволяет получать данные в виде объектов своих классов. Такие классы должны быть унаследованы от класса pydantic.BaseModel. Такие классы определяются специально
под запрос, данные которого необходимо получить.
Например, определим в файле приложения следующий код:
from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
app = FastAPI()
@app.get("/")
def root():
return FileResponse("public/index.html")
@app.post("/hello")
def hello(person: Person):
return {"message": f"Привет, {person.name}, твой возраст - {person.age}"}
Здесь в функции hello получаем данные из тела запроса в объект класса Person. Данный класс унаследован от BaseModel. Класс Person определяет два атрибута,
которые соответствуют данным из тела запроса, которые мы собираемся получить. В данном случае это атрибут name, который представляет строку, и атрибут age,
который представляет целое число.
Получив данные, мы сможем работать с ними как в данными объекта Person, например, обратиться к его атрибутам name и age. В частности, в данном случае, используя эти атрибуты, формируем и отправляем клиенту некоторое сообщение.
В проекте определим папку public, в которой определим для тестирования веб-страницу index.html со следующим кодом:
<!DOCTYPE html>
<html>
<head>
<title>METANIT.COM</title>
<meta charset="utf-8" />
</head>
<body>
<div id="message"></div>
<p>
Введите имя: <br />
<input name="username" id="username" />
</p>
<p>
Введите возраст: <br />
<input name="userage" id="userage" type="number" />
</p>
<button onclick="send()">Отправить</button>
<script>
async function send(){
// получаем введеные в поля имя и возраст
const username = document.getElementById("username").value;
const userage = document.getElementById("userage").value;
// отправляем запрос
const response = await fetch("/hello", {
method: "POST",
headers: { "Accept": "application/json", "Content-Type": "application/json" },
body: JSON.stringify({
name: username,
age: userage
})
});
if (response.ok) {
const data = await response.json();
document.getElementById("message").textContent = data.message;
}
else
console.log(response);
}
</script>
</body>
</html>
Здесь на сервер по адресу "/hello" в запросе типа POST отправляются введеные в поля значения. Эти значения в теле запроса имеют ключи "name" и "age" - как и атрибуты модели Person: между ключами отправляемых данных и атрибутами класса должно быть соответствие по имени. Полученное от сервера сообщение отображается на веб-странице в блоке сверху:
В примере выше оба атрибута: и name, и age являются обязательными. А это значит, что если в запросе не будет значения хотя бы для одного из этих атрибутов, то приложение пришлет клиенту ошибку. Однако мы также можем сделать некоторые атрибуты необязательными, присвоив им значение None:
from fastapi import FastAPI, Body
from fastapi.responses import FileResponse
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int | None = None
app = FastAPI()
@app.get("/")
def root():
return FileResponse("public/index.html")
@app.post("/hello")
def hello(person: Person):
if person.age == None:
return {"message": f"Привет, {person.name}"}
else:
return {"message": f"Привет, {person.name}, твой возраст - {person.age}"}
В данном случае атрибут name остается обязательным, а атрибут age - необязательным, поэтому в запросе необязательно для него передавать значение.
Для более детальной настройки атрибутов модели применяется класс pydantic.Field. Например, он позволяет задать значение по умолчанию и правила валдации значений с помощью следующих параметров конструктора:
default: устанавливает значение по умолчанию
min_length: устанавливает минимальное количество символов в значении параметра
max_length: устанавливает максимальное количество символов в значении параметра
pattern: устанавливает регулярное выражение, которому должно соответствовать значение параметра
lt: значение параметра должно быть меньше определенного значения
le: значение параметра должно быть меньше или равно определенному значению
gt: значение параметра должно быть больше определенного значения
ge: значение параметра должно быть больше или равно определенному значению
Применим некоторые параметры:
from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str = Field(default="Undefined", min_length=3, max_length=20)
age: int= Field(default=18, ge=18, lt=111)
app = FastAPI()
@app.get("/")
def root():
return FileResponse("public/index.html")
@app.post("/hello")
def hello(person: Person):
return {"message": f"Привет, {person.name}, твой возраст - {person.age}"}
В данном случае значение параметра name должно иметь не меньше 3 и не больше 20 символов, а параметр "age" должен представлять число в диапазоне от 18 (включительно) до 111 (не включая). Если в запросе не переданы значения для атрибутов класса, то атрибуты name и age получаются значения по умолчанию: строку "Undefined" и число 18 соответственно.
Подобным образом можно получать список объектов модели:
from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
app = FastAPI()
@app.get("/")
def root():
return FileResponse("public/index.html")
@app.post("/hello")
def hello(people:list[Person]):
return {"message": people}
В этом случае для теста мы могли бы отправить данные из кода javascript следующим образом:
const response = await fetch("/hello", {
method: "POST",
headers: { "Accept": "application/json", "Content-Type": "application/json" },
body: JSON.stringify([
{ name: "Tom", age: 38 },
{ name: "Bob", age: 41 },
{ name: "Sam", age: 25 }
])
});
const data = await response.json();
console.log(data);
Модель может содержать список. Например, класс Person содержит список изучаемых языков:
from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel
class Person(BaseModel):
name: str
languages: list = []
app = FastAPI()
@app.get("/")
def root():
return FileResponse("public/index.html")
@app.post("/hello")
def hello(person: Person):
return {"message": f"Name: {person.name}. Languages: {person.languages}"}
В данном случае для хранения языков в классе Person определен атрибут languages. В этом случае отправка данных из javascript выглядела бы следующим образом:
const response = await fetch("/hello", {
method: "POST",
headers: { "Accept": "application/json", "Content-Type": "application/json" },
body: JSON.stringify({
name: "Tom",
languages: ["Python", "JavaScript"]
})
});
const data = await response.json();
console.log(data); // {message: "Name: Tom. Languages: ['Python', 'JavaScript']"}
Также у атрибута можно установить значение по умолчанию на случай, если в запросе не содержится соответствующих данных:
class Person(BaseModel):
name: str
languages: list = ["Java", "Python", "JavaScript"]
Одна модель может содержать другую модель. Например, пользователь работает в какой-нибудь компании. И для хранения данных компании можно создать отдельную модель - Company:
from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel
class Company(BaseModel):
name: str
class Person(BaseModel):
name: str
company: Company
app = FastAPI()
@app.get("/")
def root():
return FileResponse("public/index.html")
@app.post("/hello")
def hello(person: Person):
return {"message": f"{person.name} ({person.company.name})"}
Для простоты здесь класс компании имеет только один атрибут - название компании. Отправка запроса в коде javascript в этом случае могла бы выглядеть так:
const response = await fetch("/hello", {
method: "POST",
headers: { "Accept": "application/json", "Content-Type": "application/json" },
body: JSON.stringify({
name: "Tom",
company: {name: "Google"}
})
});
const data = await response.json();
console.log(data);