Here’s another version of param to pydantic:
[import datetime
from typing import Dict, List, Literal, Optional, Tuple, Type, TypeVar, Union
import inspect
import param
from pydantic import BaseConfig, BaseModel, create_model
from pydantic.color import Color
from pydantic.fields import FieldInfo
from typing_extensions import Self
from pydantic.fields import Undefined
DATE_TYPE = Union[datetime.datetime, datetime.date]
PARAM_TYPE_MAPPING: Dict[param.Parameter, Type] = {
param.String: str,
param.Integer: int,
param.Number: float,
param.Boolean: bool,
param.Event: bool,
param.Date: DATE_TYPE,
param.DateRange: Tuple[DATE_TYPE],
param.CalendarDate: DATE_TYPE,
param.CalendarDateRange: Tuple[DATE_TYPE],
param.ListSelector: List,
param.Parameter: object,
param.Color: Color,
}
PandasDataFrame = TypeVar("pandas.core.frame.DataFrame")
class ArbitraryTypesModel(BaseModel):
"""
A Pydantic model that allows arbitrary types.
"""
class Config(BaseConfig):
arbitrary_types_allowed = True
def _create_literal(obj: List[Union[str, Type]]) -> Type:
"""
Create a literal type from a list of objects.
"""
types = []
for obj in obj:
if obj is None:
continue
elif isinstance(obj, str):
types.append(obj)
else:
types.append(obj.__name__)
if types:
return Literal[tuple(types)]
else:
return str
def parameter_to_field(
parameter: param.Parameter, created_models: Dict[str, BaseModel]
) -> (Type, FieldInfo):
"""
Translate a parameter to a pydantic field.
"""
param_type = parameter.__class__
description = " ".join(parameter.doc.split()) if parameter.doc else None
field_info = FieldInfo(description=description)
if param_type in PARAM_TYPE_MAPPING:
type_ = PARAM_TYPE_MAPPING[param_type]
field_info.default = parameter.default
elif param_type is param.ClassSelector:
type_ = parameter.class_
try:
if issubclass(type_, param.Parameterized):
type_ = created_models.get(type_.__name__, type_.__name__)
except TypeError:
pass
if isinstance(type_, tuple):
type_ = _create_literal(type_)
if parameter.default is not None:
default_factory = parameter.default
if not callable(default_factory):
default_factory = type(default_factory)
field_info.default_factory = default_factory
elif param_type is param.List:
type_ = List
if parameter.default == []:
field_info.default_factory = list
elif parameter.default is not None:
field_info.default_factory = parameter.default
elif param_type is param.Dict:
type_ = Dict
if parameter.default == {}:
field_info.default_factory = dict
elif parameter.default is not None:
field_info.default_factory = parameter.default
elif param_type in [param.Selector, param.ObjectSelector]:
if parameter.objects:
type_ = _create_literal(parameter.objects)
else:
type_ = str
elif issubclass(param_type, param.DataFrame):
type_ = PandasDataFrame
elif parameter.name == "align":
type_ = _create_literal(["auto", "start", "center", "end"])
elif parameter.name == "aspect_ratio":
type_ = Union[Literal["auto"], float]
elif parameter.name == "margin":
type_ = Union[float, Tuple[float, float], Tuple[float, float, float, float]]
else:
raise NotImplementedError(
f"Parameter {parameter.name!r} of {param_type.__name__!r} not supported"
)
return type_, field_info
def param_to_pydantic(
parameterized: Type[param.Parameterized],
base_model: Type[BaseModel] = ArbitraryTypesModel,
created_models: Optional[Dict[str, BaseModel]] = None,
) -> Dict[str, BaseModel]:
"""
Translate a param Parameterized to a Pydantic BaseModel.
Parameters
----------
parameterized : Type[param.Parameterized]
The parameterized class to translate.
base_model : Type[BaseModel], optional
The base model to use, by default ArbitraryTypesModel
created_models : Optional[Dict[str, BaseModel]], optional
A dictionary of already created models, by default None
"""
parameterized_name = parameterized.__name__
if created_models is None:
created_models = {}
if parameterized_name in created_models:
return created_models
parameterized_signature = inspect.signature(parameterized.__init__)
required_args = [
arg.name
for arg in parameterized_signature.parameters.values()
if arg.name not in ["self", "params"] and arg.default == inspect._empty
]
fields = {}
for parameter_name in parameterized.param:
parameter = parameterized.param[parameter_name]
if hasattr(parameter, "class_") and hasattr(parameter.class_, "param"):
parameter_class_name = parameter.class_.__name__
if parameterized_name != parameter_class_name:
param_to_pydantic(parameter.class_, created_models=created_models)
type_, field_info = parameter_to_field(parameter, created_models)
if parameter_name == "schema":
field_info.alias = "schema"
parameter_name = "schema_"
elif parameter_name == "copy":
field_info.alias = "copy"
parameter_name = "copy_"
elif parameter_name[0] == "_":
field_info.alias = parameter_name
parameter_name = parameter_name.lstrip("_")
if parameter_name in required_args:
field_info.default = Undefined
elif field_info.default == Undefined and not field_info.default_factory:
field_info.default = None](https://github.com/holoviz-dev/lumen-llm)
fields[parameter_name] = (type_, field_info)
pydantic_model = create_model(parameterized.__name__, __base__=base_model, **fields)
created_models[pydantic_model.__name__] = pydantic_model
return created_models