fastapi icon indicating copy to clipboard operation
fastapi copied to clipboard

`Body` related parameters don't take `alias` in consideration

Open Kludex opened this issue 1 year ago • 8 comments

Privileged issue

  • [X] I'm @tiangolo or he asked me directly to create an issue here.

Issue Content

The Body, File, and Form parameters don't support alias.

The following snippet demonstrates the issue:

from typing import Annotated

from fastapi import Body, Cookie, FastAPI, Form, UploadFile, File, Query, Header, Path

app = FastAPI()


@app.post("/{path}")
def endpoint(
    path: Annotated[int, Path(alias="PathAlias")],
    cookie: Annotated[int, Cookie(alias="CookieAlias")],
    header: Annotated[int, Header(alias="HeaderAlias")],
    query: Annotated[int, Query(alias="QueryAlias")],
    body: Annotated[int, Body(alias="BodyAlias")],
    form: Annotated[int, Form(alias="FormAlias")],
    file: Annotated[UploadFile, File(alias="FileAlias")],
):
    ...

If you look at the generated Swagger you see that alias is only being used for params.Param related fields i.e. Path, Cookie, etc.

Screenshot 2023-09-20 at 10 04 21

People can overcome this right now using validation_alias as follows:

from typing import Annotated

from fastapi import Body, Cookie, FastAPI, Form, UploadFile, File, Query, Header, Path

app = FastAPI()


@app.post("/{path}")
def endpoint(
    path: Annotated[int, Path(alias="PathAlias")],
    cookie: Annotated[int, Cookie(alias="CookieAlias")],
    header: Annotated[int, Header(alias="HeaderAlias")],
    query: Annotated[int, Query(alias="QueryAlias")],
    body: Annotated[int, Body(validation_alias="BodyAlias")],
    form: Annotated[int, Form(validation_alias="FormAlias")],
    file: Annotated[UploadFile, File(validation_alias="FileAlias")],
):
    ...

In any case, this should be fixed in FastAPI.

Kludex avatar Sep 20 '23 08:09 Kludex

When I use only validation_alias in Form I can see alias in Swagger, but the validation is not working well. For the moment I'm using both alias and validation_alias, It works.

I think issue is as FastAPI pass validation_alias to FieldInfo of Pydantic v2 in super().init () inside class Body

harol97 avatar Sep 20 '23 12:09 harol97

Here is one way to fix this issue in FastAPI:

  1. Add support for alias in Body, Form, and File parameters similar to how it is already supported in Path, Cookie, etc.

This would involve:

  • Updating the Body, Form, and File classes to accept an alias argument.

  • Plumbing the alias through to OpenAPI/Swagger schema generation.

  • Updating the documentation to mention that alias is now supported.

  1. Handle conflicts if alias and validation_alias are both provided.

For example, raise an exception if they don't match or take precedence of one over the other.

  1. Add tests to validate the new alias functionality.

  2. Submit a PR with the changes to the main FastAPI repo.

This would fix the issue so alias can be used directly instead of having to use validation_alias as a workaround. It keeps the API consistent across all the different parameter types.

Hamish-Leahy avatar Sep 22 '23 00:09 Hamish-Leahy

I request a PR #10319 to fix it. Now I can use alias in Form and File

harol97 avatar Sep 25 '23 04:09 harol97

It looks like this behavior might extend to Param types during validation. Here's an example that includes Query, Path, Header, and Cookie, compared to Pydantic's Field:

from fastapi import Query, Path, Header, Cookie
from pydantic import BaseModel, ConfigDict, Field

class ForbidExtra(BaseModel):
    model_config = ConfigDict(extra="forbid")

class FieldModel(ForbidExtra):
    type_: str | None = Field(default=None, alias="type_alias")

class QueryModel(ForbidExtra):
    type_: str | None = Query(default=None, alias="type_alias")

class CookieModel(ForbidExtra):
    type_: str | None = Cookie(default=None, alias="type_alias")

class HeaderModel(ForbidExtra):
    type_: str | None = Header(default=None, alias="type_alias")

class PathModel(ForbidExtra):
    type_: str | None = Path(alias="type_alias")

field = FieldModel(type_alias="alias")  # This behaves as expected.
# But these raise ValidationError:
query = QueryModel(type_alias="alias")
cookie = CookieModel(type_alias="alias")
header = HeaderModel(type_alias="alias")
path = PathModel(type_alias="alias")

HosseinMarvi avatar Nov 03 '23 12:11 HosseinMarvi

@harol97, the workaround didn't work for me, am I doing something wrong?

    for route in app.routes:
        if isinstance(route, APIRoute):
            # TODO: body params aliases won't work in Swagger - https://github.com/tiangolo/fastapi/issues/10286
            _transform_snake_case(route.dependant.body_params)
            _transform_snake_case(route.dependant.query_params)
            _transform_snake_case(route.dependant.header_params)```

def _transform_snake_case(params: list[ModelField]) -> None:
    for param in params:
        camelized = humps.camelize(param.name)
        param.field_info.alias = camelized
        param.field_info.validation_alias = camelized

image

hruzeda avatar Nov 14 '23 21:11 hruzeda

@hruzeda you could first do: camelized = humps.camelize(param.field_info.alias)

harol97 avatar Nov 20 '23 01:11 harol97

Hi, as @Kludex told me to watch this issue I wanted to make sure if I apply the workaround correctly as it is not (maybe no longer, not 100% sure on that) working. The context you can find in this discussion: https://github.com/tiangolo/fastapi/discussions/11004

I tried:

async def upload_docs(
    files: list[UploadFile] = File(..., validation_alias="file")
) -> dict:

and this

async def upload_docs(
    files: list[UploadFile] = File(..., alias="file", validation_alias="file")
) -> dict:

without success.

I want my multipart form data file upload to allow for either file or files in the post body to be compatible with some legacy systems.

nylocx avatar Jan 23 '24 07:01 nylocx