I'm using FastAPI to create backend for my project. I have a method that allows to upload a file. I implemented it as follows:
from fastapi import APIRouter, UploadFile, File
from app.models.schemas.files import FileInResponse
router = APIRouter()
#router.post("", name="files:create-file", response_model=FileInResponse)
async def create(file: UploadFile = File(...)) -> FileInResponse:
pass
As you can see, I use a dedicated pydantic model for a method result—FileInResponse:
from pathlib import Path
from pydantic import BaseModel
class FileInResponse(BaseModel):
path: Path
And I follow this naming pattern for models (naming models as <Entity>InCreate, <Entity>InResponse, and so on) throughout the API. However, I couldn't create a pydantic model with a field of the type File, so I had to declare it directly in the route definition (i.e. without a model containing it). As a result, I have this long auto generated name Body_files_create_file_api_files_post in the OpenAPI docs:
Is there a way to change the schema name?
If you take a look at response model you see this:
It receives the same type you would declare for a Pydantic model atribute, so it can be a Pydantic model, but it can also be, e.g. a list of Pydantic models, like List[Item].
FastAPI will use this response_model to:
Convert the output data to its type declaration.
Validate the data.
Add a JSON Schema for the response, in the OpenAPI path operation.
Will be used by the automatic documentation systems.
That mean that response model is use to return json not a file like you want to do.
I couldn't create a pydantic model with a field of the type File
This is totaly normal
so I had to declare it directly in the route definition (i.e. without a model containing it)
And that's normal too, remember that File is not a python type nor a pydantic specific type so it can't be part of a model inheritating from pydantic BaseModel.
To do a file response you can follow the official doc:
from fastapi import FastAPI
from fastapi.responses import FileResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
#app.get("/")
async def main():
return FileResponse(some_file_path)
if you want to return a file like object (i.e. file from an instance of UploadFile) follow this part of the doc:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
#app.get("/")
def main():
def iterfile():
with open(some_file_path, mode="rb") as file_like:
yield from file_like
return StreamingResponse(iterfile(), media_type="video/mp4")
Related
How do i hide a field in the FastAPI UI, yet i am able to pass in information if needed?
I tried adding '_' to the field name and added alias to map it, however the alias input is not registered.
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Subject(BaseModel):
score: int
_difficulty: str = Field('low', alias='difficulty')
Input data = {'score'= 70, 'difficulty'='high'}
When I post the above data, Subject._difficulty still show as 'low'
I'm building a FastAPI server to receive requests sent by slack slash command. Using the code below, I could see that the following:
token=BLAHBLAH&team_id=BLAHBLAH&team_domain=myteam&channel_id=BLAHBLAH&channel_name=testme&user_id=BLAH&user_name=myname&command=%2Fwhatever&text=test&api_app_id=BLAHBLAH&is_enterprise_install=false&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%BLAHBLAH&trigger_id=BLAHBLAHBLAH
was printed, which is exactly the payload I saw in the official docs. I'm trying to use the payload information to do something, and I'm curious whether there's a great way of parsing this payload info. I can definitely parse this payload using split function or any other beautiful functions, but I'm curious whether there is a 'de facto' way of dealing with slack payload. Thanks in advance!
from fastapi import FastAPI, Request
app = FastAPI()
#app.post("/")
async def root(request: Request):
request_body = await request.body()
print(request_body)
Receive JSON data
You would normally use Pydantic models to declare a request body - if you were about to receive data in JSON form - thus benefiting from the validation that Pydantic has to offer (for more options on how to post JSON data, have a look at this answer). So, you would define a model like this:
from pydantic import BaseModel
class Item(BaseModel):
token: str
team_id: str
team_domain: str
# etc.
#app.post("/")
def root(item: Item):
print(item.dict()) # convert to dictionary (if required)
return item
The payload would look like this:
{
"token": "gIkuvaNzQIHg97ATvDxqgjtO"
"team_id": "Foo",
"team_domain": "bar",
# etc.
}
Receive Form data
If, however, you were about to receive the payload as Form data, just like what slack API does (as shown in the link you provided), you could use Form fileds. With Form fields, your payload will still be validated against those fields and the type you define them with. You would need, however, to define all the parameters in the endpoint, as described in the above link and as shown below:
from fastapi import Form
#app.post("/")
def root(token: str = Form(...), team_id: str = Form(...), team_domain: str = Form(...)):
return {"token": token, "team_id": team_id, "team_domain": team_domain}
or to avoid that, you may want to have a look at this post, which describes how to use Pydantic models with Form fields. As suggested in one of the answers, you can do that even without using Pydantic models, but instead with creating a custom dependency class using the #dataclass decorator, which allows you to define classes with less code. Example:
from dataclasses import dataclass
from fastapi import FastAPI, Form, Depends
#dataclass
class Item:
token: str = Form(...)
team_id: str = Form(...)
team_domain: str = Form(...)
#...
#app.post("/")
def root(data: Item = Depends()):
return data
As FastAPI is actually Starlette underneath, even if you still had to access the request body in the way you do in the question, you should rather use methods such as request.json() or request.form(), as described in Starlette documentation, which allow you to get the request body parsed as JSON or form-data, respectively.
In symplified case we've got a projects and files. Files belong to projects. Every of them has their own router to perfom CRUD actions via API.
So, in code it should look like this:
from fastapi import FastAPI, APIRouter
app = FastAPI()
projects_router = APIRouter()
files_router = APIRouter()
app.include_router(projects_router, prefix="/projects")
projects_router.include_router(files_router, prefix="/{project_id}/files")
#files_router.get("/")
def list_files(project_id: int):
# Some code, that list all project's files by project_id
But the list_files function can't get project_id. How can it?
You need to add project_id as a path parameter. By default it's a query parameter.
Three dots ... in Path parameter make it required:
from fastapi import Path
#files_router.get("/")
def list_files(project_id: int = Path(..., gt=0)):
# Some code, that list all project's files by project_id
I'm new to FastAPI (migrating from Flask) and I'm trying to create a Pydantic model for my GET route:
from fastapi import APIRouter,Depends
from pydantic import BaseModel
from typing import Optional,List
router = APIRouter()
class SortModel(BaseModel):
field: Optional[str]
directions: List[str]
#router.get("/pydanticmodel")
def get_sort(criteria: SortModel = Depends(SortModel)):
pass #my code for handling this route.....
When I'm running curl -X GET http://localhost:XXXX/pydanticmodel?directions=up&directions=asc&field=id I'm getting 422 Unprocessable Entity: {"detail":[{"loc":["body"],"msg":"field required","type":"value_error.missing"}]}
But if I'm changing directions:List[str] -> directions: str I'm getting 200 OK with directions="asc".
What is the reason that str works for query param and List[str] does not? What am I doing wrong?
Thanks.
It is not, as yet, possible to use a GET request with Pydantic List field as query parameter. When you declare a List field in the Pydantic model, it is interpreted as a request body parameter, instead of a query one (regardless of using Depends()—you can check that through Swagger UI docs at http://127.0.0.1:8000/docs, for instance). Additionally, as you are using a GET request, even if you added the List of directions in the body and attempted sending the request, it wouldn't work, as a POST request would be required for that operation.
The way to do this is to either define the List of directions explicitly with Query as a separate parameter in your endpoint, or implement your query parameter-parsing in a separate dependency class, as described here. Remember again to define the List field explicitly with Query, so that directions can be interpreted as a query parameter and appear multiple times in the URL (in others words, to receive multiple values). Example:
from typing import List, Optional
from fastapi import APIRouter, Depends, Query
class SortModel:
def __init__(
self,
field: Optional[str],
directions: List[str] = Query(...)
):
self.field = field
self.directions = directions
router = APIRouter()
#router.get("/")
def send_user(criteria: SortModel = Depends()):
return criteria
The above can be re-written using the #dataclass decorator, as shown below:
from typing import List, Optional
from fastapi import APIRouter, Depends, Query
from dataclasses import dataclass
#dataclass
class SortModel:
field: Optional[str]
directions: List[str] = Query(...)
router = APIRouter()
#router.get("/")
def send_user(criteria: SortModel = Depends()):
return criteria
I'm running into the same issue. The following solution will work, but it isn't really what I want however maybe it's good enough for you:
from fastapi import APIRouter,Depends, Query
from pydantic import BaseModel
from typing import Optional,List
router = APIRouter()
class SortModel(BaseModel):
field: Optional[str]
#router.get("/pydanticmodel")
def get_sort(criteria: SortModel = Depends(SortModel), directions: List[str] = Query(...)):
pass #my code for handling this route.....
It's not a Pydantic or FastAPI problem.
If you want to send an array with curl you should use -d flag.
In: curl -X GET "http://127.0.0.1:8000/pydanticmodel?field=123" -d "[\"string\"]"
Out: {"field":"123","directions":["string"]}
Now your code should work perfectly.
I have a list of endpoints like below.
endpoints ["/endpoint1", "/endpoint2", "/endpoint3"]
I would like to create dynamic endpoints in my app and create swagger API docs for all the endpoints, how can I do this.
#app.route(<endpoint>):
def process():
Use Enum--(FastAPI doc) classes
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
endpoint3 = "endpoint3"
app = FastAPI()
#app.get("/model/{model_name}")
async def process(model_name: ModelName):
return {"model_name": model_name, "message": "Some message"}
and thus you will get the result as follows,