Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Body related parameters don't take alias in consideration #10286

Open
1 task done
Kludex opened this issue Sep 20, 2023 · 7 comments
Open
1 task done

Body related parameters don't take alias in consideration #10286

Kludex opened this issue Sep 20, 2023 · 7 comments

Comments

@Kludex
Copy link
Sponsor Collaborator

Kludex commented Sep 20, 2023

Privileged issue

  • 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.

@harol97
Copy link

harol97 commented Sep 20, 2023

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

@Hamish-Leahy
Copy link

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.

@harol97
Copy link

harol97 commented Sep 25, 2023

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

@HosseinMarvi
Copy link

HosseinMarvi commented Nov 3, 2023

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")

@hruzeda
Copy link

hruzeda commented Nov 14, 2023

@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

@harol97
Copy link

harol97 commented Nov 20, 2023

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

@nylocx
Copy link

nylocx commented Jan 23, 2024

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: #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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants