Получение данных запроса в виде объекта класса

Последнее обновление: 06.09.2022

В прошлой теме рассматривалось получение данных из тела запроса с помощью класса 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: между ключами отправляемых данных и атрибутами класса должно быть соответствие по имени. Полученное от сервера сообщение отображается на веб-странице в блоке сверху:

Получение данных запроса в объекты классов python и класс pydantic.BaseModel в FastAPI

Необязательные атрибуты

В примере выше оба атрибута: и 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 - необязательным, поэтому в запросе необязательно для него передавать значение.

Детальная настройка атрибутов и класс Field

Для более детальной настройки атрибутов модели применяется класс 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);
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850