Я хочу отправить запрос курлом с телом 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)
Подскажите, где может быть ошибка и куда смотреть?
1 ответ 1
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>\"hi\"</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>"hi"</word></response>%
simplexml.loads("<word>\"hi\"</word>")возвращает{'word': {'word': '"hi"'}}. Я не работал с simplexml, не знаю почему так, но это точно не маппится на ваш класс XmlData (вложенный словарь не является строкой, поэтому и выдает ошибку валидации).