3

Я хочу отправить запрос курлом с телом xml и получить этот же xml как ответ. Мой запрос:

curl -X post http://host:port/api/v1/ -H "Content-Type: application/xml" -H "Accept: application/xml" -d "<word>\"hi\"</word>"

Мой код:

from simplexml import dumps, loads
from pydantic import BaseModel
from typing import TypeVar, Generic, Type, Any
from starlette.requests import Request
from starlette.responses import Response
from fastapi import FastAPI, Depends
T = TypeVar("T", bound=BaseModel)
class XmlData(BaseModel):
 word: str
class XmlBody(Generic[T]):
 def __init__(self, model_class: Type[T]):
 self.model_class = model_class
 async def __call__(self, request: Request) -> T:
 body = await request.body()
 dict_data = loads(body)
 return self.model_class.parse_obj(dict_data)
class XmlResponse(Response):
 media_type = "application/xml"
 def render(self, content: Any) -> bytes:
 return dumps({'response': content}).encode("utf-8")
app = FastAPI()
@app.post("api/v1/")
def post_data(data: XmlData = Depends(XmlBody(XmlData))):
 return XmlResponse(data)

Получаю ошибку:

pydantic.error_wrappers.ValidationError: 1 validation error for XmlData
name
 str type expected (type=type_error.str)

Подскажите, где может быть ошибка и куда смотреть?

insolor
51.7k18 золотых знаков61 серебряный знак105 бронзовых знаков
задан 12 янв. 2022 в 8:24
7
  • 1
    Вопрос будет закрыт, так как на Stack Overflow на русском вопросы принято задавать только на русском языке. Пожалуйста, переведите ваш вопрос на русский язык или воспользуйтесь Stack Overflow на английском. Commented 12 янв. 2022 в 8:25
  • 1
    @strawdog Спасибо, вопрос переведен. Commented 12 янв. 2022 в 8:31
  • @insolor готово Commented 12 янв. 2022 в 9:01
  • simplexml.loads("<word>\"hi\"</word>") возвращает {'word': {'word': '"hi"'}}. Я не работал с simplexml, не знаю почему так, но это точно не маппится на ваш класс XmlData (вложенный словарь не является строкой, поэтому и выдает ошибку валидации). Commented 12 янв. 2022 в 9:05
  • @insolor Да, Вы правы. при таком теле: "<Request><word>\"hi\"</word></Request>" получается другая ошибка. Буду смотреть дальше. Спасибо Commented 12 янв. 2022 в 9:08

1 ответ 1

2

ValidationError возникает из-за того, что simplexml.loads("<word>\"hi\"</word>") возвращает {'word': {'word': '"hi"'}}, и это не соответствует модели XmlData (в словаре данные по ключу "word" - не строка, а вложенный словарь), поэтому и падает на вызове self.model_class.parse_obj(dict_data) с ошибкой валидации.

Корректным словарем для этой модели был бы словарь вида {'word': '"hi"'}.

Со входными данными <Request><word>\"hi\"</word></Request> (из комментариев к вопросу) получится словарь {'Request': {'word': '"hi"'}}. При попытке конвертировать в XmlData упадет с ошибкой валидации, на этот раз из-за того, что во внешнем словаре отсутствует ключ "word":

>>> from pydantic import BaseModel 
>>> class XmlData(BaseModel): 
... word: str 
>>> XmlData.parse_obj({'Request': {'word': '"hi"'}}) 
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "pydantic/main.py", line 578, in pydantic.main.BaseModel.parse_obj
 File "pydantic/main.py", line 406, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for XmlData
word
 field required (type=value_error.missing)
1 validation error for XmlData
word
 field required (type=value_error.missing)

Тут по сути нужно сначала вытащить данные по ключу 'Request', и уже их конвертировать в XmlData:

>>> dict_data = {'Request': {'word': '"hi"'}} 
>>> XmlData.parse_obj(dict_data["Request"]) 
XmlData(word='"hi"')

В вашем коде нужно изменить строку return self.model_class.parse_obj(dict_data) на return self.model_class.parse_obj(dict_data["Request"]).

Дальше уже будет падать при возврате результата с ошибкой AttributeError: 'XmlData' object has no attribute 'items'. Тут просто нужно данные перед передачей в XmlResponse преобразовать в словарь (return XmlResponse(data.dict())).

Ну и еще одна ошибка, путь в @app.post() нужно указывать с / в начале, иначе при запросе будет возвращаться ошибка HTTP 404 Not found.

Итого, получается такой код (измененные строки помечены комментарием # <--):

from simplexml import dumps, loads
from pydantic import BaseModel
from typing import TypeVar, Generic, Type, Any
from starlette.requests import Request
from starlette.responses import Response
from fastapi import FastAPI, Depends
T = TypeVar("T", bound=BaseModel)
class XmlData(BaseModel):
 word: str
class XmlBody(Generic[T]):
 def __init__(self, model_class: Type[T]):
 self.model_class = model_class
 async def __call__(self, request: Request) -> T:
 body = await request.body()
 dict_data = loads(body)
 return self.model_class.parse_obj(dict_data["Request"]) # <--
class XmlResponse(Response):
 media_type = "application/xml"
 def render(self, content: Any) -> bytes:
 return dumps({'response': content}).encode("utf-8")
app = FastAPI()
@app.post("/api/v1/") # <--
def post_data(data: XmlData = Depends(XmlBody(XmlData))):
 return XmlResponse(data.dict()) # <--

Отправляем POST запрос, получаем ответ:

<?xml version="1.0" ?>
<response>
 <word>\&quot;hi\&quot;</word>
</response>

Скриншот

Через curl (имя метода POST должно быть прописано капсом, иначе будет возвращать HTTP 405 с телом {"detail":"Method Not Allowed"}):

❯ curl -X POST http://127.0.0.1:8000/api/v1/ -H "Content-Type: application/xml" -H "Accept: application/xml" -d "<Request><word>\"hi\"</word></Request>"
<?xml version="1.0" ?><response><word>&quot;hi&quot;</word></response>% 
ответ дан 13 янв. 2022 в 5:44

Ваш ответ

Черновик сохранён
Черновик удалён

Зарегистрируйтесь или войдите

Регистрация через Google
Регистрация через почту

Отправить без регистрации

Необходима, но никому не показывается

Отправить без регистрации

Необходима, но никому не показывается

Нажимая «Отправить ответ», вы соглашаетесь с условиями пользования и подтверждаете, что прочитали политику конфиденциальности.

Начните задавать вопросы и получать на них ответы

Найдите ответ на свой вопрос, задав его.

Задать вопрос

Изучите связанные вопросы

Посмотрите похожие вопросы с этими метками.