Skip to content

Commit

Permalink
Support for user plugins with table containing references
Browse files Browse the repository at this point in the history
  • Loading branch information
prusse-martin committed Dec 16, 2024
1 parent 6f86fc6 commit 71dccb6
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 157 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CHANGELOG
* We have decided internally to revert the versioning schema back to SemVer, starting at ``1.0.0``, in order to improve better backward and forward compatibility support for plugins.
* Fix bug related to Probabilistic History Matching result reader. The shape of result was wrong.
* Add the heat transfer mechanism for fluid materials composed by a parcel of radiation and convection.
* Add support for references inside tables defined by plugins.


2024.2 (2024-09-10)
Expand Down
8 changes: 8 additions & 0 deletions src/alfasim_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ def get_alfasim_sdk_api_path():
from alfasim_sdk._internal.alfacase.case_description import (
PressureContainerDescription,
)
from alfasim_sdk._internal.alfacase.case_description import (
PluginTableColumnForInternalReference,
)
from alfasim_sdk._internal.alfacase.case_description import (
PluginTableColumnForTracerReference,
)
from alfasim_sdk._internal.alfacase.case_description import (
PressureNodePropertiesDescription,
)
Expand Down Expand Up @@ -470,6 +476,8 @@ def get_alfasim_sdk_api_path():
"PipeThermalModelType",
"PipeThermalPositionInput",
"PluginDescription",
"PluginTableColumnForInternalReference",
"PluginTableColumnForTracerReference",
"PositionalPipeTrendDescription",
"PressureContainerDescription",
"PressureNodePropertiesDescription",
Expand Down
14 changes: 14 additions & 0 deletions src/alfasim_sdk/_internal/alfacase/case_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from barril.units import Array
from barril.units import Scalar

from ..validators import non_empty_str
from .case_description_attributes import attrib_array
from .case_description_attributes import attrib_curve
from .case_description_attributes import attrib_dict_of
Expand All @@ -32,6 +33,7 @@
from .case_description_attributes import dict_of_array
from .case_description_attributes import dict_with_scalar
from .case_description_attributes import InvalidReferenceError
from .case_description_attributes import list_of_optional_integers
from .case_description_attributes import list_of_strings
from .case_description_attributes import Numpy1DArray
from .case_description_attributes import numpy_array_validator
Expand Down Expand Up @@ -86,6 +88,7 @@ class PluginTracerReference:
@attr.s(frozen=True, slots=True)
class PluginInternalReference:
plugin_item_id = attr.ib(default=None)
container_key = attr.ib(default=None)


@attr.s(frozen=True, slots=True)
Expand All @@ -94,6 +97,17 @@ class PluginMultipleReference:
item_id_list = attr.ib(default=attr.Factory(list))


@attr.s(frozen=True, slots=True)
class PluginTableColumnForTracerReference:
tracer_ids = attr.ib(validator=list_of_optional_integers)


@attr.s(frozen=True, slots=True)
class PluginTableColumnForInternalReference:
plugin_item_ids = attr.ib(validator=list_of_optional_integers)
container_key = attr.ib(validator=non_empty_str)


@attr.s(frozen=True, slots=True)
class PluginTableContainer:
columns = attr.ib(default=attr.Factory(dict))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
list_of_strings = deep_iterable(
member_validator=optional(instance_of(str)), iterable_validator=instance_of(list)
)
list_of_optional_integers = deep_iterable(
member_validator=optional(instance_of(int)), iterable_validator=instance_of(list)
)
AttrNothingType = type(attr.NOTHING)
ScalarLike = Union[Tuple[Number, str], Scalar]
ArrayLike = Union[Tuple[Sequence[Number], str], Array]
Expand Down
58 changes: 45 additions & 13 deletions src/alfasim_sdk/_internal/alfacase/plugin_alfacase_to_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
from alfasim_sdk._internal.alfacase.case_description import PluginFileContent
from alfasim_sdk._internal.alfacase.case_description import PluginInternalReference
from alfasim_sdk._internal.alfacase.case_description import PluginMultipleReference
from alfasim_sdk._internal.alfacase.case_description import (
PluginTableColumnForInternalReference,
)
from alfasim_sdk._internal.alfacase.case_description import (
PluginTableColumnForTracerReference,
)
from alfasim_sdk._internal.alfacase.case_description import PluginTableContainer
from alfasim_sdk._internal.alfacase.case_description import PluginTracerReference
from alfasim_sdk._internal.alfacase.case_description_attributes import (
Expand Down Expand Up @@ -311,8 +317,11 @@ def _convert_reference(
if is_dict_with_keys(value, "tracer_id"):
return PluginTracerReference(tracer_id=int(value["tracer_id"]))
else:
if is_dict_with_keys(value, "plugin_item_id"):
return PluginInternalReference(plugin_item_id=int(value["plugin_item_id"]))
if is_dict_with_keys(value, "plugin_item_id", strict=False):
return PluginInternalReference(
container_key=value.get("container_key"),
plugin_item_id=int(value["plugin_item_id"]),
)
raise InvalidPluginDataError(f"Can not convert to a reference: {value!r}")


Expand Down Expand Up @@ -341,6 +350,37 @@ def _convert_string(
raise InvalidPluginDataError(f"Can not convert to a string: {value!r}")


def _convert_table_column_contents(
raw_col: object,
) -> Union[
Array, PluginTableColumnForInternalReference, PluginTableColumnForTracerReference
]:
"""
Try to convert an object into a valid table column content.
This is a helper function to `_convert_table` and is not registered
in `_PLUGIN_FIELD_TO_CASEDESCRIPTION`.
"""
if is_dict_with_keys(raw_col, "values", "unit"):
# Quantity.
return Array([float(v) for v in raw_col["values"]], raw_col["unit"])
elif is_dict_with_keys(raw_col, "tracer_ids"):
# Reference to tracer.
return PluginTableColumnForTracerReference(
[(int(v) if v != "None" else None) for v in raw_col["tracer_ids"]]
)
elif is_dict_with_keys(raw_col, "plugin_item_ids", "container_key"):
# Reference to plugin model.
return PluginTableColumnForInternalReference(
container_key=raw_col["container_key"],
plugin_item_ids=[
(int(v) if v != "None" else None) for v in raw_col["plugin_item_ids"]
],
)
else:
raise InvalidPluginDataError(f"Can not convert table column: {raw_col!r}")


def _convert_table(
value: object, type_from_plugin: BaseField, alfacase_path: Path
) -> PluginTableContainer:
Expand All @@ -352,17 +392,9 @@ def _convert_table(
raw_columns = value["columns"]
col_ids = [col.id for col in type_from_plugin.rows]
if is_dict_with_keys(raw_columns, *col_ids):
columns = {}
for col in col_ids:
raw_col = raw_columns[col]
if is_dict_with_keys(raw_col, "values", "unit"):
columns[col] = Array(
[float(v) for v in raw_col["values"]], raw_col["unit"]
)
else:
raise InvalidPluginDataError(
f"Can not convert table column: {raw_col!r}"
)
columns = {
col: _convert_table_column_contents(raw_columns[col]) for col in col_ids
}
return PluginTableContainer(columns=columns)
raise InvalidPluginDataError(f"Can not convert to a table: {value!r}")

Expand Down
73 changes: 52 additions & 21 deletions src/alfasim_sdk/_internal/types.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import numbers
from typing import Callable
from typing import FrozenSet
from typing import List
from typing import Optional
from typing import Sequence
from typing import Union

import attr
Expand All @@ -16,38 +16,38 @@
from alfasim_sdk._internal.validators import valid_unit


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class ALFAsimType:
name: str = attrib(default="ALFAsim")


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class TracerType(ALFAsimType):
_CONTAINER_TYPE = "TracerModelContainer"


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class Tab:
"""
Base class for tab attributes available at ALFAsim.
"""


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class Tabs:
"""
Base class for tabs attributes available at ALFAsim.
"""


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class Group:
"""
Base class for Group attribute available at ALFAsim.
"""


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class BaseField:
"""
A base field for all types available at ALFAsim.
Expand Down Expand Up @@ -205,7 +205,7 @@ def alfasim_get_data_model_type():
)


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class String(BaseField):
"""
The String field represents an input that allows the user to enter and edit a single line of plain text.
Expand Down Expand Up @@ -249,7 +249,7 @@ class MyModel:
value: str = attrib(validator=non_empty_str)


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class Enum(BaseField):
"""
The Enum field provides list of options to the user, showing only the selected item but providing a way to display
Expand Down Expand Up @@ -331,7 +331,7 @@ def check( # pylint: disable=arguments-differ
)


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class BaseReference(BaseField):
ref_type: type = attrib()
container_type: Optional[str] = attrib(
Expand All @@ -340,7 +340,11 @@ class BaseReference(BaseField):

def __attrs_post_init__(self):
if issubclass(self.ref_type, ALFAsimType):
self.container_type = self.ref_type._CONTAINER_TYPE
if self.container_type is not None:
raise TypeError(
"When using a ALFAsimType the container_type field must be None"
)
object.__setattr__(self, "container_type", self.ref_type._CONTAINER_TYPE)
else:
if self.container_type is None:
raise TypeError(
Expand All @@ -364,7 +368,7 @@ def check(self, attr: Attribute, value) -> None:
)


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class Reference(BaseReference):
"""
The Reference field provides a list of options to the user and displays the current item selected.
Expand Down Expand Up @@ -464,7 +468,7 @@ class MyContainer:
"""


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class MultipleReference(BaseReference):
"""
The MultipleReference field works similar to :class:`Reference`, providing a list of options
Expand Down Expand Up @@ -638,24 +642,33 @@ class MyModel:
class TableColumn(BaseField):
"""
The TableColumn component provides columns for a :class:`Table` field.
Currently only columns with a :class:`Quantity` fields are available.
Currently only columns with :class:`Quantity` and :class:`Reference` fields are available.
Check out the documentation from :class:`Table` to see more details about the usage and how to retrieve values.
"""

id: str = attrib(validator=non_empty_str)
value: Quantity = attrib()
value: Union[Quantity, Reference] = attrib()
caption: str = attrib(init=False, default="")

def __attrs_post_init__(self) -> None:
object.__setattr__(self, "caption", self.value.caption)

@value.validator
def check( # pylint: disable=arguments-differ
self, attr: Attribute, values: Quantity
self, attr: Attribute, values: Union[Quantity, Reference]
) -> None:
if not isinstance(values, Quantity):
raise TypeError(f"{attr.name} must be a Quantity, got a {type(values)}.")
if isinstance(values, Quantity):
return # Always OK.
elif isinstance(values, Reference):
if values.container_type is None:
raise ValueError(
"When placed on table columns a Reference requires a non None container_type."
)
else:
raise TypeError(
f"{attr.name} must be a Quantity or a Reference, got a {type(values)}."
)


@attr.s(kw_only=True, frozen=True)
Expand All @@ -667,6 +680,14 @@ class Table(BaseField):
.. code-block:: python
@data_model(caption="Wolf")
class Wolf:
name = String(value="Wolf", caption="Name")
@container_model(caption="The Pack", model=Wolf, icon="")
class Pack:
pass
@data_model(icon="", caption="My Model")
class MyModel:
table_field=Table(
Expand All @@ -687,10 +708,20 @@ class MyModel:
caption="Pressure Column Caption",
),
),
TableColumn(
id="tracer",
value=Refence(
ref_type=Wolf,
container_type="Pack",
caption="Wolf Column Caption",
),
),
],
caption="Table Field",
)
TODO: ASIM-5862: update docs table_field_example_1.png image!
The image above illustrates the output from the example above.
.. image:: /_static/images/api/table_field_example_1.png
Expand Down Expand Up @@ -750,7 +781,7 @@ class MyModel:
"""

rows: FrozenSet[TableColumn] = attrib(converter=tuple)
rows: Sequence[TableColumn] = attrib(converter=tuple)

@rows.validator
def check( # pylint: disable=arguments-differ
Expand All @@ -763,7 +794,7 @@ def check( # pylint: disable=arguments-differ
raise TypeError(f"{attr.name} must be a list of TableColumn.")


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class Boolean(BaseField):
"""
The Boolean field provides a checkbox to select/deselect a property.
Expand Down Expand Up @@ -809,7 +840,7 @@ class MyModel:
value: bool = attrib(validator=instance_of(bool))


@attr.s(kw_only=True)
@attr.s(kw_only=True, frozen=True)
class FileContent(BaseField):
"""
The FileContent component provides a platform-native file dialog to the user to be able to select a file.
Expand Down
Loading

0 comments on commit 71dccb6

Please sign in to comment.