fmeval.transforms.util

  1from typing import Any, Callable, Dict, List, Tuple
  2from fmeval.util import assert_condition
  3
  4
  5def validate_key_uniqueness(keys: List[str]) -> None:
  6    """Validate that a list of keys contains unique values.
  7
  8    This function exists to capture the full list of duplicate keys
  9    in the error message that is raised, for a better debugging experience.
 10
 11    :param keys: The keys to be validated.
 12    :raises: EvalAlgorithmInternalError if the values in `keys` are not unique.
 13    """
 14    seen = set()
 15    duplicates = []
 16    for key in keys:
 17        if key in seen:
 18            duplicates.append(key)
 19        else:
 20            seen.add(key)
 21    assert_condition(len(duplicates) == 0, f"Duplicate keys found: {duplicates}.")
 22
 23
 24def validate_existing_keys(record: Dict[str, Any], keys: List[str]) -> None:
 25    """Validate that all expected keys are present in a record.
 26
 27    :param record: The record to be validated.
 28    :param keys: The keys that are expected to be present in the record.
 29    :raises: EvalAlgorithmInternalError if any validation fails.
 30    """
 31    missing_keys = []
 32    for key in keys:
 33        if key not in record:
 34            missing_keys.append(key)
 35    assert_condition(
 36        len(missing_keys) == 0,
 37        f"Record {record} is expected to contain the following keys, " f"but they are missing: {missing_keys}.",
 38    )
 39
 40
 41def validate_call(call_method: Callable) -> Callable:
 42    """Decorator for the __call__ method of Transforms used for validating input and output.
 43
 44    This decorator validates that all keys in a Transform's `input_keys` attribute are
 45    present in the input record that is passed to `__call__` and that the keys that
 46    are added to the record by the Transform's internal `__call__` logic are limited
 47    to the keys specified by the Transform's `output_keys` attribute.
 48
 49    Note that this decorator should only be used by Transforms that mutate their input record,
 50    as the output key validation may not make sense in the case where a new record object
 51    (which may not keep all the same keys as the original record) is returned as the output.
 52
 53    Additionally, this decorator should be used in conjunction with the
 54    `register_input_output_keys` method, as the `input_keys` and `output_keys` are initialized
 55    to None in `Transform.__init__`.
 56
 57    :param call_method: The `__call__` method of a Transform.
 58    :returns: A wrapper function that performs pre- and post-validation on top of `__call__`.
 59    """
 60
 61    def wrapper(self, record: Dict[str, Any]) -> Dict[str, Any]:
 62        assert_condition(
 63            self.input_keys is not None,
 64            "self.input_keys has not been set. You should set this attribute using "
 65            "the register_input_output_keys method.",
 66        )
 67        assert_condition(
 68            self.output_keys is not None,
 69            "self.output_keys has not been set. You should set this attribute using "
 70            "the register_input_output_keys method.",
 71        )
 72        validate_existing_keys(record, self.input_keys)
 73        call_output = call_method(self, record)
 74        validate_existing_keys(call_output, self.output_keys)
 75        return call_output
 76
 77    return wrapper
 78
 79
 80def create_output_key(transform_name: str, *args, **kwargs) -> str:
 81    """Create an output key to be used by a Transform instance.
 82
 83    This method is used to create unique, easily-identifiable output keys
 84    for Transform instances. *args and **kwargs are used purely for
 85    ensuring key uniqueness, and need not be arguments to the Transform's
 86    initializer, though they generally will be, for ease of interpretability.
 87
 88    :param transform_name: The name of the Transform class.
 89        This argument is generally passed via the __name__ attribute of
 90        a class. Note that we do not simply pass the class itself (which
 91        would be the more intuitive approach), as Ray wraps actor classes
 92        in its own wrapper class, which will cause the __name__ attribute
 93        to return an unexpected value.
 94    :param *args: Variable length argument list.
 95    :param **kwargs: Arbitrary keyword arguments.
 96    """
 97
 98    def args_to_str(positional_args: Tuple[str]) -> str:
 99        return ", ".join(str(arg) for arg in positional_args)
100
101    def kwargs_to_str(keyword_args: Dict[str, Any]) -> str:
102        return ", ".join(f"{k}={str(v)}" for k, v in keyword_args.items())
103
104    args_string = args_to_str(args)
105    kwargs_string = kwargs_to_str(kwargs)
106    output_key = (
107        f"{transform_name}"
108        f"({args_string if args_string else ''}"
109        f"{', ' if args_string and kwargs_string else ''}"
110        f"{kwargs_string if kwargs_string else ''})"
111    )
112    return output_key
def validate_key_uniqueness(keys: List[str]) -> None:
 6def validate_key_uniqueness(keys: List[str]) -> None:
 7    """Validate that a list of keys contains unique values.
 8
 9    This function exists to capture the full list of duplicate keys
10    in the error message that is raised, for a better debugging experience.
11
12    :param keys: The keys to be validated.
13    :raises: EvalAlgorithmInternalError if the values in `keys` are not unique.
14    """
15    seen = set()
16    duplicates = []
17    for key in keys:
18        if key in seen:
19            duplicates.append(key)
20        else:
21            seen.add(key)
22    assert_condition(len(duplicates) == 0, f"Duplicate keys found: {duplicates}.")

Validate that a list of keys contains unique values.

This function exists to capture the full list of duplicate keys in the error message that is raised, for a better debugging experience.

Parameters
  • keys: The keys to be validated.
Raises
  • EvalAlgorithmInternalError if the values in keys are not unique.
def validate_existing_keys(record: Dict[str, Any], keys: List[str]) -> None:
25def validate_existing_keys(record: Dict[str, Any], keys: List[str]) -> None:
26    """Validate that all expected keys are present in a record.
27
28    :param record: The record to be validated.
29    :param keys: The keys that are expected to be present in the record.
30    :raises: EvalAlgorithmInternalError if any validation fails.
31    """
32    missing_keys = []
33    for key in keys:
34        if key not in record:
35            missing_keys.append(key)
36    assert_condition(
37        len(missing_keys) == 0,
38        f"Record {record} is expected to contain the following keys, " f"but they are missing: {missing_keys}.",
39    )

Validate that all expected keys are present in a record.

Parameters
  • record: The record to be validated.
  • keys: The keys that are expected to be present in the record.
Raises
  • EvalAlgorithmInternalError if any validation fails.
def validate_call(call_method: Callable) -> Callable:
42def validate_call(call_method: Callable) -> Callable:
43    """Decorator for the __call__ method of Transforms used for validating input and output.
44
45    This decorator validates that all keys in a Transform's `input_keys` attribute are
46    present in the input record that is passed to `__call__` and that the keys that
47    are added to the record by the Transform's internal `__call__` logic are limited
48    to the keys specified by the Transform's `output_keys` attribute.
49
50    Note that this decorator should only be used by Transforms that mutate their input record,
51    as the output key validation may not make sense in the case where a new record object
52    (which may not keep all the same keys as the original record) is returned as the output.
53
54    Additionally, this decorator should be used in conjunction with the
55    `register_input_output_keys` method, as the `input_keys` and `output_keys` are initialized
56    to None in `Transform.__init__`.
57
58    :param call_method: The `__call__` method of a Transform.
59    :returns: A wrapper function that performs pre- and post-validation on top of `__call__`.
60    """
61
62    def wrapper(self, record: Dict[str, Any]) -> Dict[str, Any]:
63        assert_condition(
64            self.input_keys is not None,
65            "self.input_keys has not been set. You should set this attribute using "
66            "the register_input_output_keys method.",
67        )
68        assert_condition(
69            self.output_keys is not None,
70            "self.output_keys has not been set. You should set this attribute using "
71            "the register_input_output_keys method.",
72        )
73        validate_existing_keys(record, self.input_keys)
74        call_output = call_method(self, record)
75        validate_existing_keys(call_output, self.output_keys)
76        return call_output
77
78    return wrapper

Decorator for the __call__ method of Transforms used for validating input and output.

This decorator validates that all keys in a Transform's input_keys attribute are present in the input record that is passed to __call__ and that the keys that are added to the record by the Transform's internal __call__ logic are limited to the keys specified by the Transform's output_keys attribute.

Note that this decorator should only be used by Transforms that mutate their input record, as the output key validation may not make sense in the case where a new record object (which may not keep all the same keys as the original record) is returned as the output.

Additionally, this decorator should be used in conjunction with the register_input_output_keys method, as the input_keys and output_keys are initialized to None in Transform.__init__.

Parameters
  • call_method: The __call__ method of a Transform. :returns: A wrapper function that performs pre- and post-validation on top of __call__.
def create_output_key(transform_name: str, *args, **kwargs) -> str:
 81def create_output_key(transform_name: str, *args, **kwargs) -> str:
 82    """Create an output key to be used by a Transform instance.
 83
 84    This method is used to create unique, easily-identifiable output keys
 85    for Transform instances. *args and **kwargs are used purely for
 86    ensuring key uniqueness, and need not be arguments to the Transform's
 87    initializer, though they generally will be, for ease of interpretability.
 88
 89    :param transform_name: The name of the Transform class.
 90        This argument is generally passed via the __name__ attribute of
 91        a class. Note that we do not simply pass the class itself (which
 92        would be the more intuitive approach), as Ray wraps actor classes
 93        in its own wrapper class, which will cause the __name__ attribute
 94        to return an unexpected value.
 95    :param *args: Variable length argument list.
 96    :param **kwargs: Arbitrary keyword arguments.
 97    """
 98
 99    def args_to_str(positional_args: Tuple[str]) -> str:
100        return ", ".join(str(arg) for arg in positional_args)
101
102    def kwargs_to_str(keyword_args: Dict[str, Any]) -> str:
103        return ", ".join(f"{k}={str(v)}" for k, v in keyword_args.items())
104
105    args_string = args_to_str(args)
106    kwargs_string = kwargs_to_str(kwargs)
107    output_key = (
108        f"{transform_name}"
109        f"({args_string if args_string else ''}"
110        f"{', ' if args_string and kwargs_string else ''}"
111        f"{kwargs_string if kwargs_string else ''})"
112    )
113    return output_key

Create an output key to be used by a Transform instance.

This method is used to create unique, easily-identifiable output keys for Transform instances. args and *kwargs are used purely for ensuring key uniqueness, and need not be arguments to the Transform's initializer, though they generally will be, for ease of interpretability.

Parameters
  • transform_name: The name of the Transform class. This argument is generally passed via the __name__ attribute of a class. Note that we do not simply pass the class itself (which would be the more intuitive approach), as Ray wraps actor classes in its own wrapper class, which will cause the __name__ attribute to return an unexpected value.
  • *args: Variable length argument list.
  • **kwargs: Arbitrary keyword arguments.