Skip to content

Commit 53798f6

Browse files
assert_int, resolve_multiple_ways_to_calculate_value functions
1 parent a02c7d0 commit 53798f6

File tree

1 file changed

+134
-1
lines changed

1 file changed

+134
-1
lines changed

hwcomponents/model.py

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import inspect
33
from numbers import Number
44
from functools import wraps
5-
from typing import Callable, List, Type, Union, TypeVar
5+
from typing import Any, Callable, List, Type, Union, TypeVar
66
from hwcomponents._logging import ListLoggable
77
from hwcomponents._util import parse_float
88

@@ -412,3 +412,136 @@ def try_call_arbitrary_action(
412412
if _return_estimation_object:
413413
return value
414414
return value.value
415+
416+
def assert_int(self, name: str, value: int | float | Any) -> int:
417+
"""
418+
Checks that the value is an integer, and if so, returns it as an integer.
419+
Otherwise, raises a ValueError.
420+
421+
Parameters
422+
----------
423+
name: str
424+
The name of the attribute to check. Used for error messages.
425+
value: int | float | Any
426+
The value to check.
427+
Returns
428+
-------
429+
int
430+
The value as an integer.
431+
"""
432+
433+
if isinstance(value, int):
434+
return value
435+
if isinstance(value, float) and value.is_integer():
436+
return int(value)
437+
if isinstance(value, str):
438+
try:
439+
value = int(value)
440+
except:
441+
pass
442+
raise ValueError(f"{name} must be an integer. Got {value}.")
443+
444+
def assert_match(
445+
self,
446+
value_a: int | float | Any | None,
447+
value_b: int | float | Any | None,
448+
name_a: str,
449+
name_b: str,
450+
) -> int:
451+
"""
452+
Checks that the two values are equal, and if so, returns the matched value. If
453+
one value is None, returns the other value. Raise an error if the two values are
454+
not equal, or if both are None.
455+
456+
Parameters
457+
----------
458+
value_a: int | float | Any
459+
The first value to check.
460+
value_b: int | float | Any
461+
The second value to check.
462+
name_a: str
463+
The name of the first value. Used for error messages.
464+
name_b: str
465+
The name of the second value. Used for error messages.
466+
467+
Returns
468+
-------
469+
int
470+
The matched value.
471+
"""
472+
if value_a is None and value_b is None:
473+
raise ValueError(
474+
f"Both {name_a} and {name_b} are None. At least one must be provided."
475+
)
476+
if value_a is None:
477+
return value_b
478+
if value_b is None:
479+
return value_a
480+
481+
if value_a != value_b:
482+
raise ValueError(
483+
f"Mismatch between {name_a} and {name_b}. Got {value_a} and {value_b}."
484+
)
485+
486+
return value_a
487+
488+
def resolve_multiple_ways_to_calculate_value(
489+
self, name: str, *args: tuple[str, Callable[[Any], Any], dict[str, Any]]
490+
) -> Any:
491+
"""
492+
Parses multiple possible ways to set an attribute, raising errors if the values
493+
are not consistent.
494+
495+
Each possible argument is a tuple containing a function and a dictionary of
496+
keyword arguments. A function fails if any keyword arguments are None, if the
497+
function raises an error, or if the function returns None.
498+
499+
The outputs of all non-failing functions are compared, and an error is raised if
500+
they are not equal.
501+
502+
Parameters
503+
----------
504+
name: str
505+
The name of the attribute to set.
506+
*args: tuple[str, Callable[[Any], Any], dict[str, Any]]
507+
The possible ways to set the attribute. Each tuple contains a name, a
508+
function that takes the current value and returns the new value, and a
509+
dictionary of keyword arguments to pass to the function.
510+
Returns
511+
-------
512+
The value of the attribute.
513+
"""
514+
515+
error_messages = []
516+
517+
success_values = []
518+
519+
for fname, func, kwargs in args:
520+
for key, value in kwargs.items():
521+
fname = f"{fname}({', '.join(f'{k}={v}' for k, v in kwargs.items())})"
522+
if value is None:
523+
error_messages.append(f"{fname}: {key} is None.")
524+
try:
525+
new_value = func(**kwargs)
526+
except Exception as e:
527+
error_messages.append(f"{fname} raised {e}")
528+
if new_value is None:
529+
error_messages.append(f"{fname} returned None.")
530+
else:
531+
success_values.append((fname, new_value))
532+
533+
values = set(v[-1] for v in success_values)
534+
535+
if len(values) == 0:
536+
raise ValueError(
537+
f"Could not set {name} with any of the following options:\n\t"
538+
+ "\n\t".join(error_messages)
539+
)
540+
541+
if len(values) > 1:
542+
raise ValueError(
543+
f"Different ways to set {name} returned conflicting values:\n\t"
544+
+ "\n\t".join(f"{fname}: {value}" for fname, value in success_values)
545+
)
546+
547+
return next(iter(values))

0 commit comments

Comments
 (0)