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
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 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?
Is there a way for a FastAPI application to not display a model in its schema documentation? I have some models which are slight variations of others, and with this duplication occurring for each model, the schema documentation is quite cluttered.
from pydantic import BaseModel
class A(BaseModel):
name: str
class HasID(BaseModel):
id_: int
class AWithID(A, HasID):
pass
Is there some way not to display class AWithID in the documentation?
It's not elegant, but you can manually modify the auto-generated OpenAPI schema.
See the Extending OpenAPI section of the FastAPI docs.
A FastAPI application (instance) has an .openapi() method that is
expected to return the OpenAPI schema.
As part of the application object creation, a path operation for
/openapi.json (or for whatever you set your openapi_url) is
registered.
It just returns a JSON response with the result of the application's
.openapi() method.
By default, what the method .openapi() does is check the property
.openapi_schema to see if it has contents and return them.
If it doesn't, it generates them using the utility function at
fastapi.openapi.utils.get_openapi.
From the output of get_openapi(), all the models are defined under components > schemas, where each model's name is the dictionary key.
{
"openapi": "3.0.2",
"info": {...},
"paths": {...},
"components": {
"schemas": {
"A": {
"title": "A",
"required": [...],
"type": "object",
"properties": {...}
},
"AWithID": {
"title": "AWithID",
"required": [...],
"type": "object",
"properties": {...}
},
"HasID": {
"title": "HasID",
"required": [...],
"type": "object",
"properties": {...}
},
...
}
}
}
So, you can just pop off the models you want to hide:
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
app = FastAPI()
# Define routes before customizing the OpenAPI schema
# ...
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(title="My App", version="1.0.0", routes=app.routes)
openapi_schema["components"]["schemas"].pop("AWithID", None)
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
I am using FastAPI to write a web service. It is good and fast.
FastAPI is using pydantic models to validate input and output data, everything is good but when I want to declare a nested model for array of jsons like below:
[
{
"name": "name1",
"family": "family1"
},
{
"name": "name2",
"family": "family2"
}
]
I get empty response.
I think there is a problem with my model which is:
class Test(BaseModel):
name: str
family: str
class Config:
orm_mode = True
class Tests(BaseModel):
List[Test]
class Config:
orm_mode = True
So, my question is how should I write a model for array of jsons?
Update (26/09/2020)
In Python 3.9 (not yet released), you can do the same as below but with the built-in list generic type (which is always in scope) rather than needing to import the capitalized List type from typing, e.g.
#app.get("/tests", response_model=list[Test])
The issue here is that you are trying to create a pydantic model where it is not needed. If you want to serialize/deserialize a list of objects, just wrap your singular model in a List[] from python's builtin typing module. There is no need to try to create a plural version of your object with a pydantic BaseModel (and as you can see, it does not work anyway).
With that said, the simplest way to do what you want is to just specify a List[Test] at any point where you need a list of Tests, e.g.
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
existing_tests = [
{
"name": "name1",
"family": "family1"
},
{
"name": "name2",
"family": "family2"
}
]
class Test(BaseModel):
name: str
family: str
class Config:
orm_mode = True
app = FastAPI()
#app.get("/tests", response_model=List[Test])
async def fetch_tests():
return existing_tests
#app.post("/tests")
async def submit_tests(new_tests: List[Test]):
print(new_tests)
But of course if you find yourself repeatedly (or only) specifying Test as a list, you can of course just assign this to a variable and then use that variable where needed, like so:
Tests = List[Test]
#app.get("/tests", response_model=Tests)
async def fetch_tests():
return existing_tests
#app.post("/tests")
async def submit_tests(new_tests: Tests):
print(new_tests)
I think the first option is probably slightly clearer in your code though, and unless you are specifying List[Test] many times, using a variable for this purpose is probably not worth the extra layer of indirection.
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)