I want to validate JSON object (it is in Telegram Bot API) which contains from field (which is reserved word in Python) by using pydantic validator. So my model should look like the following:
class Message(BaseModel):
message_id: int
from: Optional[str]
date: int
chat: Any
...
But using from keyword is not allowed in this context.
How could I do this?
Note: this is different than "Why we can't use keywords as attributes" because here we get external JSON we don't control and we anyway should handle JSON with from field.
I believe you can replace from with from_.
You can do it like this:
class Message(BaseModel):
message_id: int
from_: Optional[str]
date: int
chat: Any
class Config:
fields = {
'from_': 'from'
}
...
There might be a way to do this using a class statement, but I didn't see anything in a quick skim of the documentation. What you could do is use dynamic model creation instead.
fields = {
'message_id': (int,),
'from': (Optional[str], ...),
'date': (int, ...),
'chat': (Any, ...)
}
Message = create_model("Message", **fields)
Related
I'm new to Python and Fastapi. The problem is Fastapi response body schema shows me this error everytime I attempt to make a post request. Firstly, I intend to convert uuid4 into string using the hex function then display the converted output in the response body.
What I want is that the field named api_key automatically generates dynamic default uuids for each input record (not the same as id but rather a field to store unique values)
**
422 Unprocessable Entity
{
"detail": [
{
"loc": [
"body"
],
"msg": "'str' object is not callable",
"type": "type_error"
}
]
}**
And here is the code I tried:
class Terminal(BaseModel):
api_key: str = Field(title="api key", default_factory=uuid.uuid4().hex)
name: str = Field(..., title="Terminal name", regex="[^\s]+")
mac: MACAddressStr = Field(..., title="MAC address")
mgmt_ip: IPv4Address = Field(..., title="Management IP")
All the fields that require user input works perfectly fine.
It also works when I tried to enter api_key as a string manually.
My desired output:
{
"api_key": "5876753e02f141b5a83f7e9cff6db1ba" // auto-generated
"name": "terminal1",
"mac": "aa:bb:cc:dd:ee:ff",
"mgmt_ip": "1.1.1.1"
}
From docs
default_factory: If provided, it must be a zero-argument callable that will be called when a default value is needed for this field. Among other purposes, this can be used to specify fields with mutable default values, as discussed below. It is an error to specify both default and default_factory.
So, doing this is going to work:
default_factory=lambda: uuid.uuid4().hex
I'm using pydantic 1.9.0 and fastapi 0.71.0 with Python 3.7.8. Here's how I've defined my model:
class PartModel(BaseModel):
_id: str
_key: str
number: str = Field(...)
name: str = Field(...)
price: int = Field(...)
class Config:
schema_extra = {
"example": {
"_key": "12453",
"number": "5F22",
"name": "SHA9-169",
"price": 4,
}
}
I would like to be flexible with the Model because my dictionaries are not consisted when it comes to the length. For example one dictionary might have additional key/value pairs. At the moment when i try to make the request through the FastApi it doesn't allow me to POST in the Database. Is there any way to keep my Model flexible/extendable without defining every key in the class?
In order to work with flexible models, I would recommend to work with Optional fields, as described in https://pydantic-docs.helpmanual.io/usage/models/#required-optional-fields
I wanted to validate the below stated payload -
payload = {
"emailId":['jeet#steinnlabs.com', 'jeet#dope.security'],
"role":"Administrator",
}
Hence, I created a class using Pydantic's BaseModel like this -
class SendInvitePayloadValidator(BaseModel):
emailId: List[str]
role: str
class Config:
extra = Extra.forbid
#validator('emailId')
def validate_email(cls, emailID):
pass
Now, I also wanted to validate each key of payload e.g emailId but in order to do that I need another an extra value lets say tenantId.
If I include tenantId in the model then the payload validation won't work.
So is there a way I could just pass few extra values to the validator method?
I want to define a model like this:
class Item(BaseModel):
class: str
pass: float
Since "class" and "pass" are python keywords this will fail.
Use marshmallow can do this by using data_key:
class Item(Schema):
class_ = fields.String(data_key="class")
pass_ = fields.Float(data_key="pass")
Does pydantic has something like "data_key" or there are other ways to achieve the same goal?Thanks for answering
I can give you another option.
class Item(BaseModel):
class_: str
pass_: float
class Config:
fields = {
'class_': 'class'
'pass_': 'pass'
}
I have a situation where I want to authorize the active user against one of the values (Organization) in a FastAPI route. When an object of a particular type is being submitted, one of the keys (organization_id) should be present and the user should be verified as having access to the organization.
I've solved this as a dependency in the API signature to avoid having to replicate this code across all routes that needs access to this property:
def get_organization_from_body(organization_id: str = Body(None),
user: User = Depends(get_authenticated_user),
organization_service: OrganizationService = Depends(get_organization_service),
) -> Organization:
if not organization_id:
raise HTTPException(status_code=400, detail='Missing organization_id.')
organization = organization_service.get_organization_for_user(organization_id, user)
if not organization:
raise HTTPException(status_code=403, detail='Organization authorization failed.')
return organization
This works fine, and if the API endpoint expects an organization through an organization_id key in the request, I can get the organization directly populated by introducing get_organization_from_body as a dependency in my route:
#router.post('', response_model=Bundle)
async def create_bundle([...]
organization: Organization = Depends(get_organization_from_body),
) -> Model:
.. and if the user doesn't have access to the organization, an 403 exception is raised.
However, I also want to include my actual object on the root level through a schema model. So my first attempt was to make a JSON request as:
{
'name': generated_name,
'organization_id': created_organization['id_key']
}
And then adding my incoming Pydantic model:
#router.post('', response_model=Bundle)
async def create_bundle(bundle: BundleCreate,
organization: Organization = Depends(get_organization_from_body),
[...]
) -> BundleModel:
[...]
return bundle
The result is the same whether the pydantic model / schema contains organization_id as a field or not:
class BundleBase(BaseModel):
name: str
class Config:
orm_mode = True
class BundleCreate(BundleBase):
organization_id: str
client_id: Optional[str]
.. but when I introduce my get_organization_from_body dependency, FastAPI sees that I have another dependency that refers to a Body field, and the description of the bundle object has to be moved inside a bundle key instead (so instead of "validating" the organization_id field, the JSON layout needs to change - and since I feel that organization_id is part of the bundle description, it should live there .. if possible).
The error message tells me that bundle was expected as a separate field:
{'detail': [{'loc': ['body', 'bundle'], 'msg': 'field required', 'type': 'value_error.missing'}]}
And rightly so, when I move name inside a bundle key instead:
{
'bundle': {
'name': generated_name,
},
'organization_id': created_organization['id_key']
}
.. my test passes and the request is successful.
This might be slightly bike shedding, but if there's a quick fix to work around this limitation in any way I'd be interested to find a way to both achieve validation (either through Depends() or in some alternative way without doing it explicitly in each API route function that requires that functionality) and a flat JSON layout that matches my output format closer.
Prior to FastAPI 0.53.2, dependencies for the body were resolved the way you are trying to do. Such code:
class Item(BaseModel):
val_1: int
val_2: int
def get_val_1(val_1: int = Body(..., embed=True)):
return val_1
#app.post("/item", response_model=Item)
def handler(full_body: Item, val_1: int = Depends(get_val_1)):
return full_body
Expected such body:
{
"val_1": 0,
"val_2": 0
}
But starting from version 0.53.2, different body dependencies are automatically embedded (embed=True) and the code above expects the following body:
{
"full_body": {
"val_1": 0,
"val_2": 0
},
"val_1": 0
}
Now, in order to have access to the model for the whole body and to have access to its elements as a separate dependency, you need to use same dependency for the body model everywhere:
def get_val_1(full_body: Item):
return full_body.val_1
#app.post("/item", response_model=Item)
def handler(full_body: Item, val_1: int = Depends(get_val_1)):
return full_body
Update for shared dependency
You can share one body dependency for multiple models, but in this case, two conditions must be met: the names of the dependencies must be the same and their types must be compatible (through inheritance or not). Example below:
class Base(BaseModel):
val_1: int
class NotBase(BaseModel):
val_1: int
class Item1(Base):
val_2: int
class Item2(Base):
val_3: int
def get_val1_base(full_body: Base):
return full_body.val_1
def get_val1_not_base(full_body: NotBase):
return full_body.val_1
#app.post("/item1", response_model=Item1)
def handler(full_body: Item1, val_1: int = Depends(get_val1_base)):
return full_body
#app.post("/item2", response_model=Item2)
def handler(full_body: Item2, val_1: int = Depends(get_val1_not_base)):
return full_body