Can anyone let me know, how come below pydantic model code works without instantiating UserIn and UserOut class object? Is this something handled internally by pydantic library?
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Optional[str] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Optional[str] = None
class UserInDB(BaseModel):
username: str
hashed_password: str
email: EmailStr
full_name: Optional[str] = None
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
print("User saved! ..not really")
return user_in_db
#app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
print(user_saved.__dict__)
return user_saved
You don't need to instantiate the objects in your code because FastAPI creates them automatically for pydantic schemas in both cases.
For arguments to endpoint methods that are Pydantic models like the user_in: UserIn in your example, it's interpreted as a Request Body, as explained in the docs here. It's worth noting the explanation about how the input data binding works for the endpoint parameters in FastAPI documentation:
If the parameter is also declared in the path, it will be used as a path parameter.
If the parameter is of a singular type (like int, float, str, bool, etc) it will be interpreted as a query parameter.
If the parameter is declared to be of the type of a Pydantic model, it will be interpreted as a request body.
For the endpoint output, when you define the response_model, it will also convert it, as explained in the documentation here:
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.
Related
I have this following class:
class Quiz(BaseModel):
question: str
subject: str
choice: str = Query(choices=('eu', 'us', 'cn', 'ru'))
I can render the form bases on this class like this
#api.post("/postdata")
def post_data(form_data: Quiz = Depends()):
return form_data
How can I display a drop down list for choice field ?
Option 1
Use literal values. Literal type is a new feature of the Python standard library as of Python 3.8 (prior to Python 3.8, it requires the typing-extensions package) and is supported by Pydantic. Example:
from fastapi import FastAPI, Depends
from pydantic import BaseModel
from typing import Literal
app = FastAPI()
class Quiz(BaseModel):
question: str
subject: str
choice: Literal['eu', 'us', 'cn', 'ru'] = 'us'
#app.post('/submit')
def post_data(data: Quiz = Depends()):
return data
Option 2
Use Enums (also, see Python's enum module, as well as FastAPI's documentation on Predefined values). By having your Enum sub-class inheriting from str, the API docs will be able to know that the values must be of type string and will be able to render correctly. Example:
from fastapi import FastAPI, Depends
from pydantic import BaseModel
from enum import Enum
app = FastAPI()
class Country(str, Enum):
eu = 'eu'
us = 'us'
cn = 'cn'
ru = 'ru'
class Quiz(BaseModel):
question: str
subject: str
choice: Country = Country.us
#app.post('/submit')
def post_data(data: Quiz = Depends()):
return data
I have a simple API to insert data inside an object type dictionary, my issue is when I try to save a calculated field. Example code:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
users = {}
class User(BaseModel):
name: str
email: str
calculated: float
#app.post('/user-create/{id_user}')
def creating_an_user(id_user:int,user:User):
calculated = 1+2+3*2
if id_user in users:
return {"Error":"User ID already exists."}
users[id_user] = {
"name":user.name,
"email":user.email,
"calculated":user.calculated #should I need to put just like that ?
}
return users[id_user]
Obviously I receive an error, I think because my method is waiting a manually insert "calculated" field but is not:
TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body.
I am lost in this concern, can anybody help me on this?
I am currently using a root_validator in my FastAPI project using Pydantic like this:
class User(BaseModel):
id: Optional[int]
name: Optional[str]
#root_validator
def validate(cls, values):
if not values.get("id") and not values.get("name"):
raise ValueError("It's an error")
return values
The issue is, when I query the request body in FastAPI, because of return values, instead of the request body being of type class User, it is just a simple python dictionary. How do I get the same object of type class User?
So initially, my request body would like this when I printed it: id=0 name='string' (which is how I want it) (and when I print the type() of it, it shows: <class 'User'>)
Here is what it looks like with return values: {"id":0, "name"="string"}
I have tried making it as just return cls, but this is what it looks like when I print it: <class 'User'> (and when I print the type() of it, it shows: <class 'pydantic.main.ModelMetaclass'>)
How to get my solution?
I had raised this issue in FastAPI and Pydantic discussions. And found the answer here by a community member: https://github.com/tiangolo/fastapi/discussions/4563
The solution is to rename the def validate func to anything else like def validate_all_fields
And the reason for this is validate is base method for BaseModel in Pydantic!!
I want validate a payload schema & I am using Pydantic to do that. The class created by inheriting Pydantic's BaseModel is named as PayloadValidator and it has two attributes, addCustomPages which is list of dictionaries & deleteCustomPages which is a list of strings.
class NestedCustomPages(BaseModel):
"""This is the schema for each custom page."""
html: str
label: str
type: int
class PayloadValidator(BaseModelWithContext):
"""This class defines the payload load schema and also validates it."""
addCustomPages: Optional[List[NestedCustomPages]]
deleteCustomPages: Optional[List[str]]
I want to declare either of the attributes of class PayloadValidator as optional. I have tried looking for the solution for this but couldn't find anything.
There was a question on this a while ago on the pydantic Github page: https://github.com/samuelcolvin/pydantic/issues/506. The conclusion there includes a toy example with a model that requires either a or b to be filled by using a validator:
from typing import Optional
from pydantic import validator
from pydantic.main import BaseModel
class MyModel(BaseModel):
a: Optional[str] = None
b: Optional[str] = None
#validator('b', always=True)
def check_a_or_b(cls, b, values):
if not values.get('a') and not b:
raise ValueError('Either a or b is required')
return b
mm = MyModel()
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.