How to document default None/null in OpenAPI/Swagger using FastAPI?
Asked Answered
N

1

5

Using a ORM, I want to do a POST request letting some fields with a null value, which will be translated in the database for the default value specified there.

The problem is that OpenAPI (Swagger) docs, ignores the default None and still prompts a UUID by default.

from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
from uuid import UUID
import uvicorn


class Table(BaseModel):
    # ID: Optional[UUID]      # the docs show a example UUID, ok
    ID: Optional[UUID] = None # the docs still shows a uuid, when it should show a null or valid None value.

app = FastAPI()  
    
@app.post("/table/", response_model=Table)
def create_table(table: Table):
    # here we call to sqlalchey orm etc.
    return 'nothing important, the important thing is in the docs'
    
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

In the OpenAPI schema example (request body) which is at the docs we find:

{
 "ID": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

This is not ok, because I specified that the default value is None,so I expected this instead:

{
 "ID": null, # null is the equivalent of None here
}

Which will pass a null to the ID and finally will be parsed in the db to the default value (that is a new generated UUID).

Normy answered 12/5, 2022 at 10:58 Comment(7)
Why do you have ID: UUID defaulted to the string 'null'? Shouldn't it be None?Burnsides
@npk, yes you are right, and UUID should be Optional[UUID] but that will produce in the docs a UUID not a null, basically is exactly that what I need to change to get a null in the docs.Normy
You want your users to explicitly post null for ID in the request body? Or you just want the example in the generated docs to be different?Crabbing
@Crabbing The second, with Optional[UUID] they can already specify the uuid if they want or putting null, but I want null to be the default value and the one documented as in most of cases you won't generate the uuid by yourself.Normy
Basically for the documentation ID: Optional[UUID] = None is the same as ID: Optional[UUID]. I think in one case it should write null and in the other a example UUIDNormy
I thought I had a pretty good handle on FastAPI, but this got me stumped. I tried everything I could think of, but none of them resulted in an example with a value of 'null' without quotations. I am sorry I wasn't able to help, but wanted to let you know either way because chances are; it can't be done. Might be a good idea to open an issue on Github? Please tag me there, I am very curious if this can be done. :)Crabbing
@JarroVGIT, I just posted it there and tag you: github.com/tiangolo/fastapi/issues/…Normy
H
8

When you declare Optional parameters, users shouldn't include those parameters in the request specified with null or None (in Python), in order to be None. By default, the value of such parameters will be None, unless the user specifies some other value when sending the request.

Hence, all you have to do is to declare a custom example for the Pydantic model using Config and schema_extra, as described in the documentation and as shown below. The below example will create an empty (i.e., {}) request body in OpenAPI (Swagger UI), which can be successfully submitted (as ID is the only attribute of the model and is optional).

class Table(BaseModel):
    ID: Optional[UUID] = None
    
    class Config:
        schema_extra = {
            "example": {
            }
        }

@app.post("/table/", response_model=Table)
def create_table(table: Table):
    return table

If the Table model included some other required attributes, you could add example values for those, as demonstrated below:

class Table(BaseModel):
    ID: Optional[UUID] = None
    some_attr: str
    
    class Config:
        schema_extra = {
            "example": {
                "some_attr": "Foo"
            }
        }

If you would like to keep the auto-generated examples for the rest of the attributes except the one for the ID attribute, you could use the below to remove ID from the model's properties in the generated schema (inspired by Schema customization):

class Table(BaseModel):
    ID: Optional[UUID] = None
    some_attr: str
    some_attr2: float
    some_attr3: bool
    
    class Config:
        @staticmethod
        def schema_extra(schema: Dict[str, Any], model: Type['Table']) -> None:
            del schema.get('properties')['ID']

Also, if you would like to add custom example to some of the attributes, you could use Field() (as described here); for example, some_attr: str = Field(example="Foo").

Another possible solution would be to modify the generated OpenAPI schema, as described in Solution 3 of this answer. Though, the above solution is likely more suited to this case.

Note

ID: Optional[UUID] = None is the same as ID: UUID = None. As previously documented in FastAPI website (see this answer):

The Optional in Optional[str] is not used by FastAPI, but will allow your editor to give you better support and detect errors.

Since then, FastAPI has revised their documentation with the following:

The Union in Union[str, None] will allow your editor to give you better support and detect errors.

Hence, ID: Union[UUID, None] = None is the same as ID: Optional[UUID] = None and ID: UUID = None. In Python 3.10+, one could also use ID: UUID| None = None (see here).

As per FastAPI documentation (see Info section in the link provided):

Have in mind that the most important part to make a parameter optional is the part:

= None

or the:

= Query(default=None)

as it will use that None as the default value, and that way make the parameter not required.

The Union[str, None] part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required.

Humectant answered 18/5, 2022 at 13:18 Comment(4)
I just want to add to this answer, that it is possible to make the null type required via Pydantic, in which case it would be a willingness to put null or UID in a concrete way. Of course, this does not affect the display of the example value, since swagger does not handle the null type. To make the null required : Optional[UUID] = Field(...) or Optional[UUID] = ...Endorsement
This is indeed useful to know, thanks! but it doesn't quite solve the problem. If i use this solution I would need to write all the additional fields by hand in all the classes excluding the ID field.Normy
My question explicitly asked how to document using the non quoted "null" because it still shows that you can use that field, even if it is an optional one. But if you can just skip documenting the ID would be OK for me, as long as I don't need to hardcode all other fields in the schema_extra.Normy
@Chris, thanks! that works, it is pity that is not possible to document the null thought, but your answer is an OK alternative solution.Normy

© 2022 - 2024 — McMap. All rights reserved.