Process body as plain text in FastAPI and show in SwaggerUI - python

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.

Related

Send pathlib.Path data to FastAPI: PosixPath is not JSON serializable

I have built an API using FastAPI and am trying to send data to it from a client.
Both the API and the client use a similar Pydantic model for the data that I want to submit. This includes a field that contains a file path, which I store in a field of type pathlib.path.
However, FastAPI does not accept the submission because it apparently cannot handle the path object:
TypeError: Object of type PosixPath is not JSON serializable
Here's a minimal test file that shows the problem:
import pathlib
from pydantic import BaseModel
from fastapi import FastAPI
from fastapi.testclient import TestClient
api = FastAPI()
client = TestClient(api)
class Submission(BaseModel):
file_path: pathlib.Path
#api.post("/", response_model=Submission)
async def add_submission(subm: Submission):
print(subm)
# add submission to database
return subm
def test_add_submission():
data = {"file_path": "/my/path/to/file.csv"}
print("original data:", data)
# create a Submission object, which casts filePath to pathlib.Path:
submission = Submission(**data)
print("submission object:", submission)
payload = submission.dict()
print("payload:", payload)
response = client.post("/", json=payload) # this throws the error
assert response.ok
test_add_submission()
When I change the model on the client side to use a string instead of a Path for file_path, things go through. But then I lose the pydantic power of casting the input to a Path when a Submission object is created, and then having a Path attribute with all its possibilities. Surely, there must be better way?
What is the correct way to send a pathlib.PosixPath object to a FastAPI API as part of the payload?
(This is Python 3.8.9, fastapi 0.68.1, pydantic 1.8.2 on Ubuntu)
The problem with you code is, that you first transform the pydantic model into a dict, which you then pass to the client, which uses its own json serializer and not the one provided by pydantic.
submission.dict() converts any pydantic model into a dict but keeps any other datatype.
With client.post("/", json=payload) requests json serializer is used, which cannot handle pathlib.Path.
The solution is, not to convert the pydantic model into dict first, but use the json() method of the pydantic model itself and pass it to the client.
response = client.post("/", data=submission.json())
Notice that you have to change the parameter json to data.

Best way to update objects with nullable properties with fastapi

I'm building a simple REST API.
If the object you want to update contains properties that can contain null, what is the best way to define it in fastapi?
When using pydantic.BaseModel, it is not possible to support the usage of not updating if the property does not exist.
example:
from fastapi import Body, FastAPI
from typing import Optional, Literal
import dataclasses
app = FastAPI()
#dataclasses.dataclass
class User:
name: str
type: Optional[Literal['admin', 'guest']]
user = User('test_user', 'admin')
class UpdateUser(BaseModel):
name: str
type: Optional[Literal['admin', 'guest']]
#app.put('/')
def put(update_user: UpdateUser):
# In the case of BaseModel, I don't know the difference between the property
# that I don't update and the property that I want to update with None,
# so I always update with None.
user.name = update_user.name
user.type = update_user.type
I think the simplest way is to use dict.
example:
from fastapi import Body, FastAPI
from typing import Optional, Literal
import dataclasses
app = FastAPI()
#dataclasses.dataclass
class User:
id: int
name: str
type: Optional[Literal['admin', 'guest']]
user = User(1, 'test_user', 'admin')
#app.put('/')
def put(body = Body(...)):
if 'name' in body:
user.name = body.name
if 'type' in body:
user.type = body.type
However, in this case, it is not possible to specify the JSON type used for the request like BaseModel.
How can I implement the update process with dict-like flexibility while preserving type information?
It turns out that this is a typical patch request.
At the time of update, UpdateUser can receive it, and update_user.dict(exclude_unset=True) can receive a dictionary containing only the parts that need to be updated.
example:
from fastapi import Body, FastAPI
from typing import Optional, Literal
import dataclasses
app = FastAPI()
#dataclasses.dataclass
class User:
name: str
type: Optional[Literal['admin', 'guest']]
user = User('test_user', 'admin')
class UpdateUser(BaseModel):
name: str
type: Optional[Literal['admin', 'guest']]
#app.patch('/')
def patch(update_user: UpdateUser):
update_user_dict = update_user.dict(exclude_unset=True)
if 'name' in update_user_dict:
user.name = update_user.name
if 'type' in update_user_dict:
user.type = update_user.type
https://fastapi.tiangolo.com/tutorial/body-updates/#using-pydantics-exclude_unset-parameter

FastAPI dynamic multiple path parameters

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.

Is it possible to get query params with Pydantic if param's name use special symbols?

I'm handling this request in my code (Python3.9, FastAPI, Pydantic):
https://myapi.com/api?params[A]=1&params[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()

How to generate response decriptions in FastAPI

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

Categories

Resources