Reputation: 4899
I am trying to create a dynamic model using Python's pydantic
library. My input data is a regular dict
. However, the content of the dict
(read: its keys) may vary.
I am wondering how to dynamically create a pydantic
model which is dependent on the dict
's content?
I created a toy example with two different dict
s (inputs1
and inputs2
). Let's assume the nested dict
called strategy
may be different. Based on strategy/name
I know in advance which fields will exist in strategy
. I need to create the pydantic
model based on strategy/name
.
from pydantic import BaseModel
inputs1 = {
"universe": {"name": "test_universe", "ccy": "USD"},
"price_src": "csv",
"strategy": {"name": "test_strat1"},
}
inputs2 = {
"universe": {"name": "test_universe", "ccy": "USD"},
"price_src": "csv",
"strategy": {"name": "test_strat2", "periods": 10},
}
class Universe(BaseModel):
name: str
ccy: str = "EUR"
strategy_name = "test_strat2"
if strategy_name == "test_strat1":
inputs = inputs1
class Strategy(BaseModel):
name: str
elif strategy_name == "test_strat2":
inputs = inputs2
class Strategy(BaseModel):
name: str
periods: int
class StaticModel(BaseModel):
universe: Universe
price_src: str = "csv"
strategy: Strategy
static_model = StaticModel(**inputs)
My expected output if ``strategy_name == "test_strat1":
universe=Universe(name='test_universe', ccy='USD') price_src='csv' strategy=Strategy(name='test_strat1')
My expected output if ``strategy_name == "test_strat2":
universe=Universe(name='test_universe', ccy='USD') price_src='csv' strategy=Strategy(name='test_strat2', periods=10)
I was thinking about using pydantic
's create_model
function. However, I don't understand how to dynamically define the fields
.
Upvotes: 23
Views: 41474
Reputation: 11
I wanted to create a model for my Fast API POST routes allowed format dynamically to include some fields from other models which I used for my database, consider this example:
from pydantic import BaseModel
class DBModel(BaseModel):
id: str
field1: str
field2: int
date: datetime = datetime.utcnow()
I only wanted to have field1
and field2
in my POST route data model. Here is what I did (in Pydantic version 2):
from pydantic import create_model
EXCLUDE_FIELDS = ['date', 'id']
view_model_fields = {
field_name: (field_info.annotation, field_info.default)
for field_name, field_info in DBModel.model_fields.items()
if field_name not in EXCLUDE_FIELDS
}
DBModelView = create_model('DBModelView', **view_model_fields)
And then I used it in my route:
@app.post("/my-post-route")
def post_route(data: DBModelView):
...
return data.model_dump()
Pydantic version 1 Equivalent:
from pydantic import create_model
EXCLUDE_FIELDS = ['date', 'id']
DBModelView = create_model('DBModelView')
DBModelView.__fields__ = {
f: DBModel.__fields__.get(f)
for f in DBModel.__fields__
if f not in EXCLUDE_FIELDS
}
Upvotes: 1
Reputation: 2553
Syntax has changed a bit in Pydantic 2.x:
d = {"name":(str, ...), "periods":(int, 0)}
Strategy = create_model("Strategy", **d)
You now want to pass tuples of (TYPE, DEFAULT) to the named parameters, or to make it fully dynamic, use a dictionary as shown above.
Upvotes: 0
Reputation: 542
You can use create_model
with key=(type, ...)
(3 dots) to declare a field without default value. For example:
from pydantic import BaseModel, create_model
...
if strategy_name == "test_strat1":
inputs = inputs1
Strategy = create_model('Strategy', name=(str, ...))
elif strategy_name == "test_strat2":
inputs = inputs2
Strategy = create_model('Strategy', name=(str, ...), periods=(int, ...))
print(Strategy.schema_json(indent=2))
Output for test_strat1
:
{
...
"properties": {
"name": {
"title": "Name",
"type": "string"
}
},
"required": [
"name"
]
}
And for test_strat2
:
{
...
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"periods": {
"title": "Periods",
"type": "integer"
}
},
"required": [
"name",
"periods"
]
}
Related Pydantic documentation: https://pydantic-docs.helpmanual.io/usage/models/#dynamic-model-creation
You can see in the docs that:
foo=(str, ...)
- str-typed foo
attribute with no defaultsbar=123
- int-typed bar
attribute with a default value of 123
.Upvotes: 5
Reputation: 32283
For the dynamic creation of pydantic models, you can use create_model
. Like so:
from pydantic import create_model
d = {"strategy": {"name": "test_strat2", "periods": 10}}
Strategy = create_model("Strategy", **d["strategy"])
print(Strategy.schema_json(indent=2))
Output:
{
"title": "Strategy",
"type": "object",
"properties": {
"name": {
"title": "Name",
"default": "test_strat2",
"type": "string"
},
"periods": {
"title": "Periods",
"default": 10,
"type": "integer"
}
}
}
Upvotes: 13