fastapi icon indicating copy to clipboard operation
fastapi copied to clipboard

Make exclude_unset work in pydantic models dependencies in form body

Open drforse opened this issue 2 years ago • 4 comments

First Check

  • [X] I added a very descriptive title to this issue.
  • [X] I used the GitHub search to find a similar issue and didn't find it.
  • [X] I searched the FastAPI documentation, with the integrated search.
  • [X] I already searched in Google "How to X in FastAPI" and didn't find any information.
  • [X] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [X] I already checked if it is not related to FastAPI but to Pydantic.
  • [X] I already checked if it is not related to FastAPI but to Swagger UI.
  • [X] I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • [X] I commit to help with one of those options 👆

Example Code

from fastapi import Depends, Form, FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    x: str = Form()


@app.get("/")
async def foo(item: Item = Depends()):
    print(item.dict(exclude_unset=True))

Description

Open the browser and call endpoint "/" It prints {}

Wanted Solution

I would like it not to fill dependency model with nulls, but leave it unset, with a parameter in Depends for backward compatibilty

Wanted Code

from fastapi import Depends, Form, FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    x: str = Form()


@app.get("/")
async def foo(item: Item = Depends(leave_unset=True)):
    print(item.dict(exclude_unset=True))

Alternatives

Make Form() in fields in models work without Depends() in handler function. Right now it is considered as application/json

Operating System

Windows

Operating System Details

Windows 11

FastAPI Version

0.78.0

Python Version

3.10.6

Additional Context

No response

drforse avatar Sep 12 '22 18:09 drforse

are you want to access http://localhost:8000/ with parameter unset?

csrgxtu avatar Sep 15 '22 10:09 csrgxtu

are you want to access http://localhost:8000/ with parameter unset?

I want to access it with parameter unset and in this case the print be {} instead of {"x": None} as it is now

drforse avatar Sep 21 '22 07:09 drforse

Open the browser and call endpoint "/" It prints {}

And

I want to access it with parameter unset and in this case the print be {} instead of {"x": None} as it is now

Seem to be the opposite of each other..

JarroVGIT avatar Sep 21 '22 11:09 JarroVGIT

Open the browser and call endpoint "/" It prints {}

And

I want to access it with parameter unset and in this case the print be {} instead of {"x": None} as it is now

Seem to be the opposite of each other..

Really? May be I don't construct my sentences correctly. It seems perfectly same to me... What I mean is: now it prints {"x": None}, but I want it to print {} (because exclude_unset is True and "x" wasn't passed in request)

drforse avatar Sep 21 '22 11:09 drforse

I see. You could use exclude_none=True like so:

class Item(BaseModel):
    x: str | None = Form()


@app.get("/")
async def foo(item: Item = Depends()):
    print(item.dict(exclude_unset=True, exclude_none=True))

This would print {} as the default value is None. Not exactly the same you were trying to achieve but maybe might work in your specific use case? It would mean that you would not allow null values as a valid input in certain Form() fields.

JarroVGIT avatar Sep 21 '22 18:09 JarroVGIT

I see. You could use exclude_none=True like so:

class Item(BaseModel):
    x: str | None = Form()


@app.get("/")
async def foo(item: Item = Depends()):
    print(item.dict(exclude_unset=True, exclude_none=True))

This would print {} as the default value is None. Not exactly the same you were trying to achieve but maybe might work in your specific use case? It would mean that you would not allow null values as a valid input in certain Form() fields.

I know about exclude_none, but that's not exactly what I wanted. That's different things

drforse avatar Sep 26 '22 09:09 drforse

It looks like I have similar problem, so...

I have this schema:

class GetStatisticsQueries(BaseModel):
    match_id: int | None = None
    game_id: int | None = None
    statistics_type: StatisticsType | None = None

And I want to use it as query parameters schema this way:

@router.get(
    '',
    summary='Get statistics by the given fields',
    response_model=list[StatisticsSchema],
)
async def get_statistics(
        queries: GetStatisticsQueries = Depends(),
        ...
) -> list[dict]:
    queries_dict = queries.dict(exclude_unset=True)

The problem is that dict(exclude_unset=True) returns all values every time even if value was not set. I guess it happens because of Depends() call.

Gipssik avatar Nov 29 '22 13:11 Gipssik