В прошлой теме рассматривалось получение данных из тела запроса с помощью класса 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);