Open skewty opened 2 weeks ago
I have been playing around with this and some schema options are not fully / properly supported (expected).
from typing import Annotated
import fastapi, uvicorn
from annotated_types import MinLen
from fastapi_xml import add_openapi_extension
from fastapi_xml import XmlBody, XmlRoute, XmlAppResponse
from pydantic_xml import BaseXmlModel, element
class RequestA(BaseXmlModel):
FieldA: Annotated[str, MinLen(1), element()]
class RequestB(BaseXmlModel):
FieldB: Annotated[str, MinLen(1), element()]
class SampleResponse(BaseXmlModel):
Result: Annotated[str, MinLen(1), element()]
app = fastapi.FastAPI(title="FastAPI::XML", default_response_class=XmlAppResponse)
app.router.route_class = XmlRoute
add_openapi_extension(app)
RequestAorB = RequestA | RequestB
@app.post("/notification")
def post_event(data: RequestAorB = XmlBody()) -> SampleResponse:
return SampleResponse(Result="Success")
uvicorn.run(app, host="0.0.0.0", port=8000)
Notice that using union on the request type causes the base tag to be lost. We get <notagname>
instead.
But the understanding of anyOf is still there.
Is there any interest in this feature? Pydantic is a core part of FastAPI so this seems to be a good match for a project with this name.
It would also be nice to be able to include some ValidationError handling that returns valid XML instead of JSON. This gets pretty close although I am still plagued by not having tag name come through as reported in OP. There are some work arounds but it would be better to solve the issue.
from fastapi_xml import XmlAppResponse, XmlBody, XmlRoute
from pydantic import ConfigDict, ValidationError
from pydantic_xml import BaseXmlModel, element
class ValidationErrorError(BaseXmlModel, tag="Error"):
model_config = ConfigDict(populate_by_name=True)
type: Annotated[str, element(tag="Type", examples=["string_too_short"])]
loc: Annotated[tuple[str, ...], element(tag="Location", examples=["FieldA"])]
msg: Annotated[
str,
element(tag="Message", examples=["String should have at least 5 characters"]),
]
input: Annotated[str, element(tag="Input", examples=["test"])]
url: Annotated[
str,
element(
tag="URL", examples=["https://errors.pydantic.dev/2.9/v/string_too_short"]
),
]
class ValidationErrorDetail(BaseXmlModel, tag="Detail"):
model_config = ConfigDict(populate_by_name=True)
error: Annotated[tuple[ValidationErrorError, ...], element(tag="Error")]
@app.exception_handler(ValidationError)
async def validation_error_handler(
_: fastapi.Request, ex: ValidationError
) -> fastapi.Response:
data = {"error": ex.errors()}
model = ValidationErrorDetail.model_validate(data)
xml = model.to_xml(xml_declaration=True, encoding="UTF-8")
# xml = xmltodict.unparse({"detail": {"error": ex.errors()}})
return XmlAppResponse(
status_code=fastapi.status.HTTP_422_UNPROCESSABLE_ENTITY, content=xml
)
router = fastapi.APIRouter(
route_class=XmlRoute,
default_response_class=XmlAppResponse,
responses={
# https://fastapi.tiangolo.com/advanced/additional-responses/
422: {"description": "Validation Error", "model": ValidationErrorDetail}
},
)
gives
but as above
I would be willing to help with refactoring the existing code to support both code paths. I think 1st step would be to change @staticmethod
[^footnote] to @classmethod
so a subclass can actually change how it's parent does things. I wasn't able to alter your code easily without copy pasting massive portions due to this decision. I basically have to fork.
[^footnote]: Guido van Rossum, the creator of Python, has expressed regret over the introduction of static methods in Python. In a 2005 blog post, he wrote that static methods were an “accident” and that he would have done things differently if he had the chance.
Hey @skewty,
Thank you for your interest in this project! Any help is greatly appreciated. Feel free to fork the project and submit a pull request if you'd like to contribute.
I've been searching for a proper solution that supports Pydantic's BaseModel
for quite some time, but I haven't yet found the perfect fit. On one hand, there's xsdata-pydantic
, which extends |
xsdata
with Pydantic. On the other hand, there's pydantic_xml
, which extends Pydantic’s BaseModel
.
At this point, neither library offers an ideal solution. Therefore, it might be wiser to integrate pydantic_xml
in a more flexible way, so that those who prefer to use xsdata
don’t need pydantic_xml
.
To achieve this, I suggest implementing a dedicated Router and Response class for pydantic_xml in a separate module. This way, users who don’t want to use pydantic_xml won’t need to install it as an optional dependency.
This is a great suggestion, and I'm excited to see it in the project! I’ve mentioned it in previous issues as well. However, this topic is unrelated to the current issue. Please open a separate issue for further discussion, as it's off-topic here.
I got it working with minimal changes to your code:
response.py
route.py
Sample App
WARNING: when tag names are specified in
pydantic_xml.element
they do not seem to get detected / used. I worked around this by not using PEP8 compliant snake_case named fields in my pydantic_xml model definitions. Not sure how hard this would be to implement.