I would like to implement a strategy pattern in pydantic, by taking the strategy string, importing it as a module, retrieving the model for that item for it, and continue model validation with the dynamically imported model.
For example:
cfg = {
"shape": {
"strategy": "mypkg.Cone",
"radius": 5,
"height": 10
}
"transformations": [
{
"strategy": "mypkg.Translate",
"x": 10,
"y": 10,
"z": 10
},
{
"strategy": "quaternions.Rotate",
"quaternion": [0, 0, 0, 0]
}
]
}
I’d like this to be contained within the model class, and arbitrarily recursive so that I can just use Model(**data)
and all strategies are resolved, and so that strategy-loaded models can have their own substrategies.
From this input I’d expect the following validated instance:
<__main__.Model(
shape=<mypkg.cone.Cone(
radius=5,
height=10
)>,
transformations=[
<mypkg.Translate(x, y, z)>,
<quaternions.Rotate(...)>
]
)>
The closest I have gotten is to use try to force rebuild the model during validation and to dynamically add new members to the discriminated type unions, but it only takes effect the NEXT validation cycle of the model:
class Model:
shape: Shape
transformations: list[Transformation]
@model_validator(mode="before")
def adjust_unions(cls, data):
cls.model_fields.update({"shape": import_and_add_to_union(cls, data, "shape")})
cls.model_rebuild(force=True)
return data
import_and_add_to_union
takes the existing FieldInfo
, imports the module, retrieves the
new union member, and produces a new FieldInfo
with an annotation
for a discriminated type union, with both the existing union members, and the newly imported one. This works correctly, but only goes into effect the NEXT validation cycle:
try:
Model(**data) # errors
except:
pass
Model(**data) # now works, but only 1 level deep, because it errored out
# Model(**data) would now work 2 levels deep, etc
on top of that, it’s also not fully self contained in the model, since the parent model needs to assist in building its child discriminated type unions, rather than that the child Shape
class is able to use its own strategy
to return a validated instance of class Cone
.
Is there any way to improve this?