I want to generate a description of all available responses (along with code 200 example), which are represented in the code, like here.
from typing import Any
import uvicorn
from fastapi import FastAPI, HTTPException
router = FastAPI()
from pydantic import BaseModel
class FileItemBase(BaseModel):
current_project: str = "Test project"
class FileItemInDBBase(FileItemBase):
id: int
folder_path: str
class Config:
orm_mode = True
class FileResponse(FileItemInDBBase):
pass
#router.get("/", response_model=FileResponse)
def example_code() -> Any:
"""
# beautiful description
to demonstrate functionality
"""
demo=True
if demo:
raise HTTPException(418, "That is a teapot.")
if __name__ =="__main__":
uvicorn.run(router)
What I got with this is such a description.
When I try this out - I got an error response (as expected).
What I want - is the description of an error included in the example responses, like here. A Frontend-developer can look at this description and process such cases in the right way without testing the API.
I know how it can be made within OpenAPI specs.
Is there a way to generate this description with FastAPI?
You can add a responses parameter to your path operation.
Then you can pass your model there. It will create a schema for that model.
class FileItemBase(BaseModel):
current_project: str = "Test project"
#app.get("/", response_model=FileItemBase, responses={418: {"model": FileItemBase}})
def example_code():
"""
# beautiful description
to demonstrate functionality
"""
demo = True
if demo:
raise HTTPException(418, "That is a teapot.")
Related
In ASP.NET Core you have the concept of adding a service/object as "scoped" which means that it is the same object within a request. We used this concept e.g. to get the user out of the Bearer token header and provide the user data project-wide over a "CurrentUserService".
I'm searching for a way to do this using Pythons FastAPI and Beanie framework. Here is the use-case:
I want to use the event-based actions of beanie to automatically set the user data before a database update. Here is the current state of the code:
class DataEntity(Document):
name: str
last_modified_by: str
#before_event(Replace)
def set_version_data(self):
#here the current user should be set into last_modified_by
So my question is: How can I set the last_modified_by field on this event level?
I already tried to use the FastAPI dependencies, because they look like a scoped wrap-around I searched for:
async def get_current_user(x_user: str = Header(None)) -> str:
return x_user
class DataEntity(Document):
name: str
last_modified_by: str
#before_event(Replace)
def set_version_data(self, current_user: str = Depends(get_current_user)):
self.last_modified_by = current_user
But this is not handled right and I get the error "pydantic.error_wrappers.ValidationError: 1 validation error for DataEntity: last_modified_by - str type expected (type=type_error.str)".
I am trying to have my FastAPI accept a string as body. Note that this string is not a JSON. For me it is important that the body argument can also be specified in the SwaggerUI that FastAPI provides.
At the moment, I am doing the following:
#app.put("items/{item_name}")
async def put(item_name: str, body: str = Body(..., media_type="text/plain")):
print(body)
But it gives me an error: Error: Unprocessable Entity
I hope it fixes:
Create your pydantic model:
from pydantic import BaseModel
class Item(BaseModel):
item_name: str
body: str
(if you create it in same page):
#app.put("items/{item_name}")
async def put(item_name: Item.item_name, body: Item.body):
return body
Also it is better to use "id"s to update something.
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).
When you declare Optional parameters, users shouldn't have to include those parameters in their request (specified with null value) in order to be null. The default value of the parameters will be null, 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.
I would like to achieve the following :
My application contains some "sub-domains" which correspond to different parts of the app.
Each domain has its own entities
I would like to write a single controler like so :
#app.get("/{domain}/entity/{entity}/{id}")
async def read_users(domain: Domain, entity: Entity, id: Int):
pass
considering Entity would be an Enum that could change following the selected domain.
For instance, if the domain is "architecture", Entity could be defined like :
class Entity(str, Enum):
building = "building"
floor = "floor"
but if the selected domain is "vehicle", the matching Entity would be :
class Entity(str, Enum):
engine = "engine"
wheels = "wheels"
More generally, I guess what I'm looking for is a way to make a path parameter validation dependent on another path parameter.
This way :
GET /architecture/entity/floor/1 is valid since, floor is a valid entity for domain architecture
GET /vehicle/entity/wheels/5 is valid since, wheels is a valid entity for domain vehicle
GET /architecture/entity/engine/1 is invalid since, engine is not a valid entity for domain architecture
Is there any way to achieve this ?
You can use closures. The following code does not use Enums for brevity :
from fastapi import FastAPI
app = FastAPI()
domains = {"architecture": ["building","floor"], "vehicle": ["engine","wheels"]}
def set_route(domain,entity):
url = "/{}/entity/{}/{{id}}".format(domain,entity)
#app.get(url)
async def read_users(id: int):
return(f"Users of the {domain} {entity} #{id}")
for domain, entities in domains.items():
for entity in entities:
set_route(domain,entity)
And it yields the desired API schema :
I'm not sure if exactly what you want is possible, for sure you can write controller where you add pydantic validation, and handle exception if it throws validation error:
from pydantic import BaseModel, ValidationError
from enum import Enumfrom fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from typing import Union, Literal
class ArchitectureEntity(BaseModel):
entity: Union[Literal['building'], Literal['floor']]
class VehicleEntity(BaseModel):
entity: Union[Literal['wheels'], Literal['engine']]
#app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder({"detail": exc.errors(), "Error": "Entity not permitted"}),
)
#app.get("/{domain}/entity/{entity}/{id}")
async def read_users(domain: Domain, entity: Entity, id: int):
if domain == 'architecture':
entity = ArchitectureEntity(entity=entity)
elif domain == 'vehicle':
entity = VehicleEntity(entity=entity)
return {'architecture': entity}
However openapi docs will not show, that e.g architecture and engine are not allowed together.
I'm handling this request in my code (Python3.9, FastAPI, Pydantic):
https://myapi.com/api?params[A]=1¶ms[B]=2
I tried to make following model:
BaseModel for handling special get request
(for fastapi.Query and pydantic.Field is same)
I also set up aliases for it, but in swagger docs I see next field:
Snap of the swagger docs
There are fields that are specified as extra_data
So, if I specify query params in parameters of my endpoint like this:
#app.get('/')
def my_handler(a: str = Query(None, alias="params[A]")):
return None
Everything works fine. How can I fix it? I want to initialize my pydantic.BaseModel with speacial aliases using this way and avoid usage of query-params in
class MyModel(BaseModel):
a = Field(alias="params[A]")
b = Field(alias="params[B]")
def my_handler(model: MyModel = Depends()):
return model.dict()