Я пишу сценарий для публикации данных из внутренней системы в сторонний веб-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
, похоже, не использует его при сериализации.
Здесь:
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"}
Для аудитории: спецификация @field_serializer
в Pydantic запускается, когда вы используете Object.model_dump()
или Object.model_dump_json()
, что означает, что мои фильтры сериализатора теперь обрабатывают указанные поля должным образом, и данные могут быть успешно сериализованы с помощью requests.post
в JSON для отправки. часть model_dump()
— это тот бит, которого мне не хватало, который преобразовывал объект в Dict, у которого не было несериализуемых полей.
Объект 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 Я обновил свой ответ, включив в него полный пример того, как его можно использовать. Попробуете и убедитесь, работает это или нет? Параметр json
не должен создавать проблем с пользовательскими правилами кодирования. Обязательно включите правила для всех встречающихся типов (видите, я добавил правило Decimal
, вы можете аналогичным образом добавить для значений Integer
и Boolean
). Надеюсь, это поможет, дайте мне знать, как все пойдет.
@MarkBenson Полный рабочий пример с сервером Flask
найдите: replit.com/join/wdqfgnjfmy-dimitarvel
@Димитар, я не думаю, что так следует поступать. Марк использует Pydantic, и вам не понадобится столько пользовательского кода, поскольку в Pydantic уже встроена довольно хорошая сериализация. Кроме того, использование product_item.dict()
устарело и вместо него следует использовать model_dump()
или model_dump_json()
. И эти методы уже создают сериализованные данные, готовые к отправке с запросами.
Спасибо. Это было именно то, что мне нужно. Ты жжешь.