Source code for lembas.param
"""Custom parameter types that can be defined on `lembas.Case` instances."""
from __future__ import annotations
from functools import cached_property
from typing import TYPE_CHECKING
from typing import Any
if TYPE_CHECKING:
from lembas.case import Case
# Create alias for built-in type function so that we can re-assign a variable with that name
_builtin_type = type
class _NoDefault:
"""Used as a sentinel to indicate lack of a default value for an `InputAttribute`."""
[docs]
class InputParameter:
"""An input parameter which can be defined for a ``Case``, which determines case-specific values.
The parameter is of a defined type and type conversion will be performed when setting the value,
if appropriate.
Args:
type: The parameter type, e.g. ``float``, ``str``, etc.
default: The default value. If the type is not set, the type of the default will be used.
min: The minimum value for the parameter (if it is a float).
max: The maximum value for the parameter (if it is a float).
control: Set as True to indicate a runtime control parameter.
These parameters will be excluded from the `case.inputs` dictionary.
"""
_name: str
def __init__(
self,
*,
type: type | None = None,
default: Any = _NoDefault,
min: float | None = None,
max: float | None = None,
control: bool = False,
):
if type is not None:
self._type = type
elif default != _NoDefault:
self._type = _builtin_type(default)
else:
raise TypeError(
"An 'InputParameter' must either explicitly set the type, or have a default value to infer it from."
)
self._default = default
self._min_value = min
self._max_value = max
self._control = control
def __set_name__(self, owner: type[Case], name: str) -> None:
self._name = name
def __set__(self, instance: object, value: Any) -> None:
if self._type is not None:
value = self._type(value)
if self._min_value is not None and value < self._min_value:
raise ValueError(
"Specified value less than minimum for attribute "
f"'{self._name}': ({value} < {self._min_value})"
)
if self._max_value is not None and value > self._max_value:
raise ValueError(
"Specified value exceeds maximum for attribute "
f"'{self._name}': ({value} > {self._max_value})"
)
instance.__dict__[self._name] = value
def __get__(self, instance: Case, owner: type[Case]) -> Any:
"""Retrieve the attribute from the instance dictionary.
If the value hasn't been set, we attempt to return a default, unless there is no default,
in which case we raise an AttributeError.
"""
try:
return instance.__dict__[self._name]
except KeyError:
if self._default == _NoDefault:
raise AttributeError(
f"'{self._name}' attribute of '{owner.__name__}' class "
"has no default value and must be specified explicitly"
)
return self._default
@cached_property
def include_in_inputs_dict(self) -> bool:
"""If True, include the parameter in the `case.inputs` dictionary."""
return not self._control