feat: Support dynamic parameters

This commit is contained in:
Fangyin Cheng
2024-08-30 07:24:22 +08:00
parent 0219f5733b
commit e9155f0c31
2 changed files with 228 additions and 34 deletions

View File

@@ -36,6 +36,8 @@ _ALLOWED_TYPES: Dict[str, Type] = {
}
_BASIC_TYPES = [str, int, float, bool, dict, list, set]
_DYNAMIC_PARAMETER_TYPES = [str, int, float, bool]
DefaultParameterType = Union[str, int, float, bool, None]
T = TypeVar("T", bound="ViewMixin")
TM = TypeVar("TM", bound="TypeMetadata")
@@ -292,9 +294,6 @@ class ParameterCategory(str, Enum):
return cls.RESOURCER
DefaultParameterType = Union[str, int, float, bool, None]
class TypeMetadata(BaseModel):
"""The metadata of the type."""
@@ -313,7 +312,23 @@ class TypeMetadata(BaseModel):
return self.__class__(**self.model_dump(exclude_defaults=True))
class Parameter(TypeMetadata, Serializable):
class BaseDynamic(BaseModel):
"""The base dynamic field."""
dynamic: bool = Field(
default=False,
description="Whether current field is dynamic",
examples=[True, False],
)
dynamic_minimum: int = Field(
default=0,
description="The minimum count of the dynamic field, only valid when dynamic is"
" True",
examples=[0, 1, 2],
)
class Parameter(BaseDynamic, TypeMetadata, Serializable):
"""Parameter for build operator."""
label: str = Field(
@@ -332,11 +347,6 @@ class Parameter(TypeMetadata, Serializable):
description="The category of the parameter",
examples=["common", "resource"],
)
# resource_category: Optional[str] = Field(
# default=None,
# description="The category of the resource, just for resource type",
# examples=["llm_client", "common"],
# )
resource_type: ResourceType = Field(
default=ResourceType.INSTANCE,
description="The type of the resource, just for resource type",
@@ -389,6 +399,17 @@ class Parameter(TypeMetadata, Serializable):
values[k] = handled_v
return values
@model_validator(mode="after")
def check_parameters(self) -> "Parameter":
"""Check the parameters."""
if self.dynamic and not self.is_list:
raise FlowMetadataException("Dynamic parameter must be list.")
if self.dynamic and self.dynamic_minimum < 0:
raise FlowMetadataException(
"Dynamic minimum must be greater then or equal to 0."
)
return self
@classmethod
def _covert_to_real_type(cls, type_cls: str, v: Any, is_list: bool) -> Any:
def _parse_single_value(vv: Any) -> Any:
@@ -450,6 +471,8 @@ class Parameter(TypeMetadata, Serializable):
description: Optional[str] = None,
options: Optional[Union[BaseDynamicOptions, List[OptionValue]]] = None,
resource_type: ResourceType = ResourceType.INSTANCE,
dynamic: bool = False,
dynamic_minimum: int = 0,
alias: Optional[List[str]] = None,
ui: Optional[UIComponent] = None,
):
@@ -461,6 +484,8 @@ class Parameter(TypeMetadata, Serializable):
raise ValueError(f"Default value is missing for optional parameter {name}.")
if not optional:
default = None
if dynamic and type not in _DYNAMIC_PARAMETER_TYPES:
raise ValueError("Dynamic parameter must be str, int, float or bool.")
return cls(
label=label,
name=name,
@@ -474,6 +499,8 @@ class Parameter(TypeMetadata, Serializable):
placeholder=placeholder,
description=description or label,
options=options,
dynamic=dynamic,
dynamic_minimum=dynamic_minimum,
alias=alias,
ui=ui,
)
@@ -635,6 +662,11 @@ class BaseResource(Serializable, BaseModel):
description="The label to display in UI",
examples=["LLM Operator", "OpenAI LLM Client"],
)
custom_label: Optional[str] = Field(
None,
description="The custom label to display in UI",
examples=["LLM Operator", "OpenAI LLM Client"],
)
name: str = Field(
...,
description="The name of the operator",
@@ -668,7 +700,7 @@ class IOFiledType(str, Enum):
LIST = "list"
class IOField(Resource):
class IOField(BaseDynamic, Resource):
"""The input or output field of the operator."""
is_list: bool = Field(
@@ -676,17 +708,6 @@ class IOField(Resource):
description="Whether current field is list",
examples=[True, False],
)
dynamic: bool = Field(
default=False,
description="Whether current field is dynamic",
examples=[True, False],
)
dynamic_minimum: int = Field(
default=0,
description="The minimum count of the dynamic field, only valid when dynamic is"
" True",
examples=[0, 1, 2],
)
mappers: Optional[List[str]] = Field(
default=None,
description="The mappers of the field, transform the field to the target type",
@@ -724,18 +745,6 @@ class IOField(Resource):
mappers=mappers_cls,
)
@model_validator(mode="before")
@classmethod
def base_pre_fill(cls, values: Dict[str, Any]) -> Dict[str, Any]:
"""Pre fill the metadata."""
if not isinstance(values, dict):
return values
if "dynamic" not in values:
values["dynamic"] = False
if "dynamic_minimum" not in values:
values["dynamic_minimum"] = 0
return values
class BaseMetadata(BaseResource):
"""The base metadata."""
@@ -1137,6 +1146,38 @@ class ViewMetadata(BaseMetadata):
values["outputs"] = new_outputs
return values
@model_validator(mode="after")
def check_metadata(self) -> "ViewMetadata":
"""Check the metadata."""
if self.inputs:
for field in self.inputs:
if field.mappers:
raise ValueError("Input field can't have mappers.")
dyn_cnt, is_last_field_dynamic = 0, False
for field in self.inputs:
if field.dynamic:
dyn_cnt += 1
is_last_field_dynamic = True
else:
if is_last_field_dynamic:
raise ValueError("Dynamic field input must be the last field.")
is_last_field_dynamic = False
if dyn_cnt > 1:
raise ValueError("Only one dynamic input field is allowed.")
if self.outputs:
dyn_cnt, is_last_field_dynamic = 0, False
for field in self.outputs:
if field.dynamic:
dyn_cnt += 1
is_last_field_dynamic = True
else:
if is_last_field_dynamic:
raise ValueError("Dynamic field output must be the last field.")
is_last_field_dynamic = False
if dyn_cnt > 1:
raise ValueError("Only one dynamic output field is allowed.")
return self
def get_operator_key(self) -> str:
"""Get the operator key."""
if not self.flow_type: