Coverage for /home/benjarobin/Bootlin/projects/Schneider-Electric-Senux/sbom-cve-check/src/sbom_cve_check/utils/parsing.py: 53%

62 statements  

« prev     ^ index     » next       coverage.py v7.11.1, created at 2025-11-28 15:37 +0100

1# -*- coding: utf-8 -*- 

2# SPDX-License-Identifier: GPL-2.0-only 

3 

4from collections.abc import Callable 

5from configparser import ConfigParser 

6from datetime import UTC, datetime, timedelta 

7from typing import Any, TypeVar 

8 

9__DURATION_MULTIPLIER = { 

10 "seconds": 1.0, 

11 "second": 1.0, 

12 "sec": 1.0, 

13 "s": 1.0, 

14 "minutes": 60.0, 

15 "minute": 60.0, 

16 "min": 60.0, 

17 "months": 2629800.0, 

18 "month": 2629800.0, 

19 "M": 2629800.0, 

20 "msec": 1.0e-3, 

21 "ms": 1.0e-3, 

22 "m": 60.0, 

23 "hours": 3600.0, 

24 "hour": 3600.0, 

25 "hr": 3600.0, 

26 "h": 3600.0, 

27 "days": 86400.0, 

28 "day": 86400.0, 

29 "d": 86400.0, 

30 "weeks": 604800.0, 

31 "week": 604800.0, 

32 "w": 604800.0, 

33} 

34 

35_ParamT = TypeVar("_ParamT") 

36 

37 

38def parse_duration(v: object) -> timedelta: 

39 unit = "s" 

40 if isinstance(v, (int, float)): 

41 val = float(v) 

42 elif isinstance(v, str): 

43 idx_unit = next( 

44 (i for i, ch in enumerate(v) if not (ch.isdigit() or ch == ".")), -1 

45 ) 

46 if idx_unit == 0: 

47 raise ValueError(f"Invalid duration: {v}") 

48 

49 if idx_unit < 0: 

50 val = float(v.strip()) 

51 else: 

52 val = float(v[:idx_unit].strip()) 

53 unit = v[idx_unit:].strip() 

54 else: 

55 raise TypeError(f"Invalid duration type: {v}") 

56 

57 multiplier = __DURATION_MULTIPLIER.get(unit) 

58 if not multiplier: 

59 raise ValueError(f"Invalid duration suffix: {v}") 

60 

61 return timedelta(seconds=val * multiplier) 

62 

63 

64def parse_boolean(v: object) -> bool: 

65 """Parse the given value as a boolean. Currently, supports integers or strings.""" 

66 b = None 

67 if isinstance(v, str): 

68 b = ConfigParser.BOOLEAN_STATES.get(v.lower()) 

69 elif isinstance(v, int) and (v in {0, 1}): 

70 b = bool(v) 

71 

72 if b is None: 

73 raise ValueError(f"Invalid boolean value: {v}") 

74 return b 

75 

76 

77def parse_list(v: object) -> list[str]: 

78 if not isinstance(v, str): 

79 raise TypeError(f"Cannot parse list, not a string: {v}") 

80 return [v.strip() for v in v.split(",")] 

81 

82 

83def _update_param_value( 

84 args: dict[str, Any], 

85 cfg_name: str, 

86 new_name: str | None, 

87 required: bool, 

88 param_type: type[_ParamT], 

89 parse_fn: Callable[[Any], _ParamT | None], 

90) -> None: 

91 """ 

92 Low-level helper to update the parameter `cfg_name` in `args`, 

93 optionally under `new_name`. 

94 If the value in `args`'s `cfg_name` is not of type `param_type`, use 

95 `parse_fn` to convert it. 

96 """ 

97 v = args.pop(cfg_name, None) 

98 if v is None: 

99 if required: 

100 raise ValueError(f"Missing {cfg_name} {param_type.__name__} parameter") 

101 return 

102 

103 args[new_name or cfg_name] = v if isinstance(v, param_type) else parse_fn(v) 

104 

105 

106def update_boolean_param( 

107 args: dict[str, Any], 

108 cfg_name: str, 

109 new_name: str | None = None, 

110 required: bool = False, 

111) -> None: 

112 _update_param_value(args, cfg_name, new_name, required, bool, parse_boolean) 

113 

114 

115def update_integer_param( 

116 args: dict[str, Any], 

117 cfg_name: str, 

118 new_name: str | None = None, 

119 required: bool = False, 

120) -> None: 

121 _update_param_value(args, cfg_name, new_name, required, int, int) 

122 

123 

124def update_timedelta_param( 

125 args: dict[str, Any], 

126 cfg_name: str, 

127 special_vals: dict[str, timedelta | None], 

128 new_name: str | None = None, 

129 required: bool = False, 

130) -> None: 

131 def _parse_duration(s: str) -> timedelta | None: 

132 if s in special_vals: 

133 return special_vals[s] 

134 return parse_duration(s) 

135 

136 _update_param_value(args, cfg_name, new_name, required, timedelta, _parse_duration) 

137 

138 

139def update_list_param( 

140 args: dict[str, Any], 

141 cfg_name: str, 

142 new_name: str | None = None, 

143 required: bool = False, 

144) -> None: 

145 _update_param_value(args, cfg_name, new_name, required, list, parse_list) 

146 

147 

148def datetime_from_iso_format(s: str | None) -> datetime | None: 

149 if not s: 

150 return None 

151 

152 d = datetime.fromisoformat(s) 

153 if not d.tzinfo: 

154 return d.replace(tzinfo=UTC, microsecond=0) 

155 return d.replace(microsecond=0)