Convert a python dict to correct python BaseModel pydantic class

Question:

My requirement is to convert python dictionary which can take multiple forms into appropriate pydantic BaseModel class instance. Following are details:

class ConditionType(str, Enum):
    EXPRESSION = 'EXPRESSION'
    CYCLE_DUR_TREND = 'CYCLE_DUR_TREND'

class ConditionalExpressionProps(BaseConditionalProps):
    conditional_expression: str

class CycleDurationTrendProps(BaseConditionalProps):
    direction_up : bool = True
    n : int = Field(1, ge=1, le=1000)

class ConditionalConfig(BaseModel):
    cond_type: ConditionType = ConditionType.EXPRESSION
    condition_prop: BaseConditionalProps

class SendNotificationChannel(BaseModel):
    id: str
    customer_id: str
    conditional_config: Optional[ConditionalConfig]

I want to create a SendNotificationChannel instance with correct conditional_config set such that condition_prop becomes ConditionalExpressionProps or CycleDurationTrendProps based on the structure of dict.

For example, if dict is:

{
    "id" : "1", 
    "customer_id" : "abc", 
    "conditional_config" : {
        "cond_type": "EXPRESSION",
        "conditional_expression" : ".s_num>10"
    }
}

then on doing something like SendNotificationChannel(**dict) should make the conditional_config.condition_prop of type ConditionalExpressionProps, and similarly for CycleDurationTrendProps.

However when I’m doing this, its taking BaseConditionalProps.

>>> channel = {"conditional_config" : {"cond_type": "EXPRESSION", "condition_prop":{"conditional_expression":"ALPHA"}}}
>>> channel_obj = SendNotificationChannel(**channel)
>>> channel_obj
SendNotificationChannel(conditional_config=ConditionalConfig(cond_type=<ConditionType.EXPRESSION: 'EXPRESSION'>, condition_prop=BaseConditionalProps()))
>>> channel_obj.conditional_config.condition_prop
BaseConditionalProps()
>>> 

I’d want to know how would someone solve this problem, or if I’m tackling this the incorrect way.

Asked By: Ouroboros

||

Answers:

If I understand your question correctly you can to use .parse_obj method for your problem. Also you need to update the condition_prop field type. The following code it is example:

from enum import Enum
from typing import Optional, Union

from pydantic import BaseModel, Field


class ConditionType(str, Enum):
    EXPRESSION = 'EXPRESSION'
    CYCLE_DUR_TREND = 'CYCLE_DUR_TREND'

class BaseConditionalProps(BaseModel):
    pass

class ConditionalExpressionProps(BaseConditionalProps):
    conditional_expression: str

class CycleDurationTrendProps(BaseConditionalProps):
    direction_up : bool = True
    n : int = Field(1, ge=1, le=1000)

class ConditionalConfig(BaseModel):
    cond_type: ConditionType = ConditionType.EXPRESSION
    condition_prop: Union[ConditionalExpressionProps, CycleDurationTrendProps]

class SendNotificationChannel(BaseModel):
    id: str
    customer_id: str
    conditional_config: Optional[ConditionalConfig]


channel_1 = {
    "id" : "1", 
    "customer_id" : "abc", 
    "conditional_config" : {
        "cond_type": "EXPRESSION",
        "condition_prop": {
            "conditional_expression" : ".s_num>10"
        }
    }
}

channel_1_obj = SendNotificationChannel.parse_obj(channel_1)
print(channel_1_obj)
# id='1' customer_id='abc' conditional_config=ConditionalConfig(cond_type=<ConditionType.EXPRESSION: 'EXPRESSION'>, condition_prop=ConditionalExpressionProps(conditional_expression='.s_num>10'))

channel_2 = {
    "id" : "2", 
    "customer_id" : "def", 
    "conditional_config" : {
        "cond_type": "CYCLE_DUR_TREND",
        "condition_prop": {
            "n" : 10
        }
    }
}

channel_2_obj = SendNotificationChannel.parse_obj(channel_2)
print(channel_2_obj)
# id='2' customer_id='def' conditional_config=ConditionalConfig(cond_type=<ConditionType.CYCLE_DUR_TREND: 'CYCLE_DUR_TREND'>, condition_prop=CycleDurationTrendProps(direction_up=True, n=10))
Answered By: Denis Artyushin
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.