How would I create a Parameterized class programmatically?

Figured it out. I can use .param.add_parameter.

Which means I’m able to do basic conversions between Param models and Pydantic Models via the functions param_to_pydantic_class and pydantic_to_param_class.

import datetime
from typing import Container, Dict, List, Optional, Type, Union

import param
import pydantic
import pytest
from pydantic import BaseConfig, BaseModel, create_model

try:
    import numpy as np

    DATE_TYPE = Union[datetime.datetime, datetime.date, np.datetime64]
except:
    DATE_TYPE = Union[datetime.datetime, datetime.date]

PARAM_TO_PYTHON_TYPE: Dict[param.Parameter, Type] = {
    param.String: str,
    param.Integer: int,
    param.Number: float,
    param.Date: DATE_TYPE,
    param.List: List,
}
PYTHON_TYPE_TO_PARAM = {value: key for key, value in PARAM_TO_PYTHON_TYPE.items()}


def _get_python_type_from_parameter(parameter: param.Parameter):
    if isinstance(parameter, param.List) and parameter.item_type:
        python_type: Type = List[parameter.item_type]
    else:
        python_type = PARAM_TO_PYTHON_TYPE[parameter.__class__]

    if parameter.allow_None:
        python_type = Union[python_type, None]

    return python_type


def _get_parameter_type_from_python_type(type_: Type) -> Type[param.Parameter]:
    return PYTHON_TYPE_TO_PARAM[type_]


def _get_parameter_type_from_pydantic_field(
    field: pydantic.fields.ModelField,
) -> Type[param.Parameter]:
    return _get_parameter_type_from_python_type(field.type_)


def param_to_pydantic_class(
    parameterized: Type[param.Parameterized],
    *,
    config: Type = BaseConfig,
    exclude: Container[str] = []
) -> Type[BaseModel]:
    fields = {}
    parameters = [
        parameterized.param[key]
        for key in parameterized.param
        if not parameterized.param[key].name in exclude
    ]
    for parameter in parameters:
        python_type = _get_python_type_from_parameter(parameter)
        fields[parameter.name] = (python_type, parameter.default)
    pydantic_model = create_model(parameterized.__name__, __config__=config, **fields)  # type: ignore
    return pydantic_model


def pydantic_to_param_class(value) -> param.Parameterized:
    new_class: param.Parameterized = type(value.__name__, (param.Parameterized,), {})

    for name, field in value.__fields__.items():
        parameter_type = _get_parameter_type_from_pydantic_field(field)
        parameter = parameter_type(default=field.default, allow_None=field.allow_none)
        new_class.param.add_parameter(name, parameter)
    return new_class
4 Likes