26

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 dicts (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.

asked Feb 12, 2021 at 8:29
1

4 Answers 4

15

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"
 }
 }
}
answered Feb 12, 2021 at 8:43
Sign up to request clarification or add additional context in comments.

4 Comments

Do you mind to expand your solution with respect to the 1) How to define a str and int with no default values? and 2) Match my expected solutions in the way that strategy is nested.
Ad 1) Strategy = create_model("Strategy", **d) should give you the nested structure, shouldn't it?
I don't think it will be possible to create nested models, but you can experiment. As for removing the default values, you can try to use the syntax through the tuple specified in the documentation, like this Strategy = create_model("Strategy", **{k: (type(v), ...) for k, v in d["strategy"].items()})
why does it not support pass in a json schema and get a dynamic model? I see docs.pydantic.dev/latest/integrations/datamodel_code_generator/… can generate a model file by json schema.
5

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 defaults
  • bar=123 - int-typed bar attribute with a default value of 123.
answered Dec 22, 2021 at 17:27

Comments

1

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
}
answered May 20, 2024 at 14:52

Comments

0

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.

answered Feb 6, 2024 at 8:51

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.