Closed elijahsgh closed 6 months ago
@elijahsgh Hi,
Unfortunately FastAPI intentionally focused on json so that right now there is no easy way to integrate it with other content types like xml. The simplest solution I can see to get rid of boilerplate code is to add a validator for the body argument:
import functools as ft
from typing import Annotated, Any, Type, TypeVar
from xml.etree import ElementTree as etree
from fastapi import Body
from pydantic import BeforeValidator
from pydantic_xml import BaseXmlModel
XmlModelT = TypeVar('XmlModelT', bound=BaseXmlModel)
def validate_xml_model(model_type: Type[XmlModelT], value: bytes | dict) -> XmlModelT:
if isinstance(value, bytes):
try:
return model_type.from_xml(value)
except etree.ParseError as e:
raise ValueError(f"invalid xml: {e}")
else:
return model_type.model_validate(value)
def XmlBody(model_type: Type[XmlModelT], **kwargs: Any) -> Type[XmlModelT]:
return Annotated[
model_type,
Body(media_type='application/xml', **kwargs),
BeforeValidator(ft.partial(validate_xml_model, model_type))
]
class MyModel(BaseXmlModel):
...
@app.post('/sub', response_class=PlainTextResponse)
async def sub(
xml_body: XmlBody(MyModel)
):
assert isinstance(xml_body, MyModel)
...
Here's what I ended up with.
async def initxmlfrombody(request: Request) -> AtomFeed:
body = await request.body()
return AtomFeed.from_xml(body)
@app.post('/sub', response_class=PlainTextResponse)
async def sub(
feed: Annotated[AtomFeed, Depends(initxmlfrombody)]
):
...
This leverages the dependency injection of FastAPI with a little factory function.
If AtomFeed (a pydantic xml model) could be instanced with something like AtomFeed(my_xml) instead of AtomFeed.from_xml(my_xml) I think the dependency injection might be able to work directly with it.
In the meantime I think I have it working.
I stumbled onto pydantic xml and it's been fantastic. Thanks!
I was was wanting to use it directly with FastAPI and had some feedback and questions.
In FastAPI, the expectation is you can use a model directly with the endpoints. Here is some example code that doesn't work but I think it should:
This doesn't seem to work because MyModel() can't be initialized with values.
This works perfectly:
My question is why can't I instantiate from XML and why do I have to call Model.from_xml? Did I miss something in the docs? Is there a different class for this?