Дата, десятичное число не подлежит сериализации в формате JSON при кодировании модели Pydantic для отправки POST через запросы

Я пишу сценарий для публикации данных из внутренней системы в сторонний веб-API. Наша внутренняя система использует модели данных, определенные в Pydantic. Я пытаюсь отправить данные в одной из этих моделей в API, используя requests.post, но он не может сериализовать некоторые типы, и это сводит меня с ума.

Возьмем эту модель данных в качестве примера:

class Product(BaseModel):
    id: int
    group_id: int
    sku: str
    price: Decimal
    special_offer_price: Decimal
    last_update: datetime.date

Если я попытаюсь отправить данные с использованием этой модели, используя requests.post, таким образом:

product_item = website_api.Product(**product.__dict__)

requests.post(url, headers=jwt_auth.auth_header, json=product_item)

Поля str и int в порядке, но поля Decimal и datetime.date выдают исключение, поскольку они не сериализуемы, что приводит к ошибке примерно такого типа:

TypeError: Object of type date is not JSON serializable

Я попытался добавить @field_serializer для полей в определение класса для pydantic;

    @field_serializer("price", "special_offer_price")
    def serialize_decimal(self, value):
        return str(value)
    
    @field_serializer("last_update")
    def serialize_date(self, value):
        return str(value)

но requests, похоже, не использует его при сериализации.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Здесь:

product_item = website_api.Product(**product.__dict__)

если ваш product_item является экземпляром упомянутой вами модели Product, вам следует сделать:

requests.post(url, headers=jwt_auth.auth_header, json=product_item.model_dump())

Вам не нужны сериализаторы для datetime.date и Decimal. Когда я протестировал это в этом классе Product, я смог без проблем сериализовать его с помощью product_item.model_dump().

Как здесь:

import datetime
from decimal import Decimal
from pydantic import BaseModel


class Product(BaseModel):
    id: int
    group_id: int
    sku: str
    price: Decimal
    special_offer_price: Decimal
    last_update: datetime.date


product = Product(
    id=1,
    group_id=1,
    sku = "123456",
    price=Decimal("100.00"),
    special_offer_price=Decimal("90.00"),
    last_update=datetime.date(2021, 1, 1),
)

print(product.model_dump())

производит:

{'id': 1, 'group_id': 1, 'sku': '123456', 'price': Decimal('100.00'), 'special_offer_price': Decimal('90.00'), 'last_update': datetime.date(2021, 1, 1)}

или если вам это нравится как строка json, то это:

print(product.model_dump_json())

производит:

{"id":1,"group_id":1,"sku":"123456","price":"100.00","special_offer_price":"90.00","last_update":"2021-01-01"}

Спасибо. Это было именно то, что мне нужно. Ты жжешь.

MarkBenson 05.07.2024 13:32

Для аудитории: спецификация @field_serializer в Pydantic запускается, когда вы используете Object.model_dump() или Object.model_dump_json(), что означает, что мои фильтры сериализатора теперь обрабатывают указанные поля должным образом, и данные могут быть успешно сериализованы с помощью requests.post в JSON для отправки. часть model_dump() — это тот бит, которого мне не хватало, который преобразовывал объект в Dict, у которого не было несериализуемых полей.

MarkBenson 05.07.2024 13:51

Объект Date, как следует из ошибки, по умолчанию не подлежит сериализации. Для этой цели вы можете создать кодировщик:

import json
from decimal import Decimal
from datetime import date
from pydantic import BaseModel

class CustomJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return str(obj)
        if isinstance(obj, date):
            return obj.isoformat()
        return super().default(obj)

class Product(BaseModel):
    id: int
    group_id: int
    sku: str
    price: Decimal
    special_offer_price: Decimal
    last_update: date

product_item = Product(
    id=1,
    group_id=10,
    sku = "ABC123",
    price=Decimal('19.99'),
    special_offer_price=Decimal('15.99'),
    last_update=date.today()
)

product_dict = product_item.dict()
data = json.dumps(product_dict, cls=CustomJSONEncoder)

headers = {
    'Content-Type': 'application/json',
    **jwt_auth.auth_header
}

response = requests.post(url, headers=headers, data=data)

С этого момента вы сможете делать с этими сериализованными данными JSON все, что захотите.

Привет, Дитмар. Я знал об этом, но, к сожалению, встроенный параметр json=requests.post не позволяет мне использовать собственный кодировщик при запуске процесса. Мне удалось заставить json.dumps сериализовать объект в текстовую строку JSON, но передача его как data= в requests.post привела к его экранированию и, таким образом, исказила JSON.

MarkBenson 05.07.2024 13:36

@MarkBenson Я обновил свой ответ, включив в него полный пример того, как его можно использовать. Попробуете и убедитесь, работает это или нет? Параметр json не должен создавать проблем с пользовательскими правилами кодирования. Обязательно включите правила для всех встречающихся типов (видите, я добавил правило Decimal, вы можете аналогичным образом добавить для значений Integer и Boolean). Надеюсь, это поможет, дайте мне знать, как все пойдет.

Dimitar 05.07.2024 14:20

@MarkBenson Полный рабочий пример с сервером Flask найдите: replit.com/join/wdqfgnjfmy-dimitarvel

Dimitar 05.07.2024 14:48

@Димитар, я не думаю, что так следует поступать. Марк использует Pydantic, и вам не понадобится столько пользовательского кода, поскольку в Pydantic уже встроена довольно хорошая сериализация. Кроме того, использование product_item.dict() устарело и вместо него следует использовать model_dump() или model_dump_json(). И эти методы уже создают сериализованные данные, готовые к отправке с запросами.

Mateusz Anikiej 05.07.2024 19:00

Другие вопросы по теме