Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 63 additions & 4 deletions modelon/impact/client/entities/custom_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@
logger = logging.getLogger(__name__)


class _Parameter:
__slots__ = ["_name", "_value", "_value_type", "_valid_values"]
class CustomFunctionParameter:
"""A single analysis parameter of a custom function."""

__slots__ = [
"_name",
"_value",
"_value_type",
"_valid_values",
"_display_name",
"_description",
]

_JSON_2_PY_TYPE = {
"Number": (
Expand All @@ -43,16 +52,46 @@ class _Parameter:
"VariableNames": (list,),
}

def __init__(self, name: str, value: Any, value_type: str, valid_values: List[Any]):
def __init__(
self,
name: str,
value: Any,
value_type: str,
valid_values: List[Any],
display_name: str = "",
description: str = "",
):
self._name = name
self._value = value
self._value_type = value_type
self._valid_values = valid_values
self._display_name = display_name
self._description = description

@property
def name(self) -> str:
return self._name

@property
def display_name(self) -> str:
"""Human-readable name for the parameter, as shown in Modelon Impact.

Differs from 'name' in ways that cannot be derived from it: the 'dynamic' custom
function's stop time is named 'final_time' and displayed as 'Stop Time'. Falls
back to 'name' when the signature declares no display name.

"""
return self._display_name or self._name

@property
def description(self) -> str:
"""Description of what the parameter controls.

Empty when not declared.

"""
return self._description

@property
def type(self) -> str:
return self._value_type
Expand Down Expand Up @@ -113,11 +152,13 @@ def __init__(
self._workspace_id = workspace_id
self._parameter_data = parameter_data
self._param_by_name = {
p["name"]: _Parameter(
p["name"]: CustomFunctionParameter(
p["name"],
p.get("defaultValue", ""),
p["type"],
p.get("values", []),
p.get("displayName", ""),
p.get("description", ""),
)
for p in parameter_data
}
Expand Down Expand Up @@ -177,6 +218,24 @@ def with_parameters(self, **modified: Any) -> CustomFunction:

return new

def get_parameters(self) -> List[CustomFunctionParameter]:
"""Returns the custom function's parameters.

Use this over 'parameter_values' when the parameters are to be shown to
someone: it carries each parameter's display name and description, which
'parameter_values' does not.

Returns:
A list of CustomFunctionParameter class objects.

Example::

for parameter in custom_function.get_parameters():
print(parameter.display_name, parameter.value)

"""
return list(self._param_by_name.values())

@property
def parameter_values(self) -> ParameterDict:
"""Custom_function parameters and value as a dictionary."""
Expand Down
41 changes: 41 additions & 0 deletions tests/impact/client/entities/test_custom_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
IDs,
create_case_entity,
create_custom_artifact_uri_entity,
create_custom_function_entity,
create_experiment_entity,
create_workspace_entity,
)
Expand All @@ -22,6 +23,46 @@ def _get_dynamic_cf(self, client_helper):
dynamic = workspace.get_custom_function("dynamic")
return dynamic

def test_parameters_expose_the_signatures_display_name(self):
"""The display name is what Impact shows, and it is not derivable.

'final_time' is displayed as 'Stop Time', so a caller presenting parameters to
someone has to read it from the signature rather than reformat the name.

"""
custom_function = create_custom_function_entity(
IDs.WORKSPACE_ID_PRIMARY,
IDs.DYNAMIC_CF,
[
{
"name": "final_time",
"type": "Number",
"defaultValue": 1,
"displayName": "Stop Time",
"description": "The stop time of the simulation",
}
],
)

(parameter,) = custom_function.get_parameters()

assert parameter.name == "final_time"
assert parameter.display_name == "Stop Time"
assert parameter.description == "The stop time of the simulation"
assert parameter.value == 1

def test_parameter_display_name_falls_back_to_the_name(self):
custom_function = create_custom_function_entity(
IDs.WORKSPACE_ID_PRIMARY,
IDs.DYNAMIC_CF,
[{"name": "tolerance", "type": "Number", "defaultValue": 1e-6}],
)

(parameter,) = custom_function.get_parameters()

assert parameter.display_name == "tolerance"
assert parameter.description == ""

@pytest.mark.experimental
def test_custom_function_with_parameters_ok(self, custom_function):
workspace = create_workspace_entity(IDs.WORKSPACE_ID_PRIMARY)
Expand Down