22import inspect
33from numbers import Number
44from functools import wraps
5- from typing import Callable , List , Type , Union , TypeVar
5+ from typing import Any , Callable , List , Type , Union , TypeVar
66from hwcomponents ._logging import ListLoggable
77from 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