Coverage for src/polars_eval_metrics/metric_helpers.py: 96%

28 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-09-29 15:04 +0000

1from __future__ import annotations 

2 

3""" 

4Helper functions for creating metrics from various sources. 

5 

6Simple, functional approach to metric creation without factory classes. 

7""" 

8 

9# pyre-strict 

10 

11from typing import TYPE_CHECKING, Any 

12 

13from .utils import parse_enum_value 

14 

15 

16if TYPE_CHECKING: 

17 from .metric_define import MetricDefine, MetricScope, MetricType 

18 

19 

20def create_metric_from_dict(config: dict[str, Any]) -> MetricDefine: 

21 """Create a MetricDefine from dictionary configuration.""" 

22 

23 _validate_metric_config(config) 

24 

25 from .metric_define import MetricDefine 

26 

27 return MetricDefine( 

28 name=config["name"], 

29 label=config.get("label", config["name"]), 

30 type=config.get("type", "across_sample"), 

31 scope=config.get("scope"), 

32 within_expr=config.get("within_expr"), 

33 across_expr=config.get("across_expr"), 

34 ) 

35 

36 

37def create_metrics(configs: list[dict[str, Any]] | list[str]) -> list[MetricDefine]: 

38 """Create metrics from configurations or simple names.""" 

39 if not configs: 

40 return [] 

41 

42 from .metric_define import MetricDefine 

43 

44 if isinstance(configs[0], str): 

45 str_configs: list[str] = configs # pyre-ignore[9] 

46 return [MetricDefine(name=name) for name in str_configs] 

47 

48 dict_configs: list[dict[str, Any]] = configs # pyre-ignore[9] 

49 return [create_metric_from_dict(config) for config in dict_configs] 

50 

51 

52# ======================================== 

53# VALIDATION FUNCTIONS - Centralized Logic 

54# ======================================== 

55 

56 

57def _validate_metric_config(config: dict[str, Any]) -> None: 

58 """Validate metric configuration dictionary.""" 

59 if "name" not in config: 

60 raise ValueError("Metric configuration must include 'name'") 

61 

62 if not isinstance(config["name"], str) or not config["name"].strip(): 

63 raise ValueError("Metric name must be a non-empty string") 

64 

65 if "type" in config and config["type"] is not None: 

66 from .metric_define import MetricType 

67 

68 parse_enum_value( 

69 config["type"], 

70 MetricType, 

71 field="metric type", 

72 ) 

73 

74 if "scope" in config and config["scope"] is not None: 

75 from .metric_define import MetricScope 

76 

77 parse_enum_value( 

78 config["scope"], 

79 MetricScope, 

80 field="metric scope", 

81 allow_none=True, 

82 )