Coverage for nexusLIMS/tui/apps/config/validators.py: 100%

61 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2026-03-24 05:23 +0000

1"""Field validators for the NexusLIMS configuration TUI. 

2 

3Provides validation functions specific to configuration editing, extending 

4the common validators with config-domain rules. 

5""" 

6 

7import zoneinfo 

8 

9from nexusLIMS.tui.common.validators import validate_url 

10 

11 

12def validate_nemo_address(value: str | None) -> tuple[bool, str]: 

13 """ 

14 Validate a NEMO API address URL (must be a valid URL with trailing slash). 

15 

16 Parameters 

17 ---------- 

18 value : str | None 

19 NEMO address to validate 

20 

21 Returns 

22 ------- 

23 tuple[bool, str] 

24 (is_valid, error_message) 

25 """ 

26 if not value or not value.strip(): 

27 return False, "NEMO address is required" 

28 

29 is_valid, error = validate_url(value, "NEMO address") 

30 if not is_valid: 

31 return False, error 

32 

33 if not value.rstrip().endswith("/"): 

34 return False, "NEMO address must end with a trailing slash ('/')" 

35 

36 return True, "" 

37 

38 

39def validate_optional_url( 

40 value: str | None, field_name: str = "URL" 

41) -> tuple[bool, str]: 

42 """ 

43 Validate an optional HTTP(S) URL (empty value is accepted). 

44 

45 Parameters 

46 ---------- 

47 value : str | None 

48 URL to validate 

49 field_name : str 

50 Human-readable field name for error messages 

51 

52 Returns 

53 ------- 

54 tuple[bool, str] 

55 (is_valid, error_message) 

56 """ 

57 if not value or not value.strip(): 

58 return True, "" 

59 return validate_url(value, field_name) 

60 

61 

62def validate_optional_iana_timezone(value: str | None) -> tuple[bool, str]: 

63 """ 

64 Validate an optional IANA timezone string (empty value is accepted). 

65 

66 Parameters 

67 ---------- 

68 value : str | None 

69 Timezone string to validate (e.g., "America/New_York") 

70 

71 Returns 

72 ------- 

73 tuple[bool, str] 

74 (is_valid, error_message) 

75 """ 

76 if not value or not value.strip(): 

77 return True, "" 

78 

79 try: 

80 zoneinfo.ZoneInfo(value) 

81 return True, "" 

82 except (zoneinfo.ZoneInfoNotFoundError, KeyError): 

83 return ( 

84 False, 

85 f'Unknown timezone "{value}". Use IANA format (e.g., America/New_York)', 

86 ) 

87 

88 

89def validate_float_positive( 

90 value: str | None, field_name: str = "Value" 

91) -> tuple[bool, str]: 

92 """ 

93 Validate a positive float (> 0). 

94 

95 Parameters 

96 ---------- 

97 value : str | None 

98 String to validate as a positive float 

99 field_name : str 

100 Human-readable field name for error messages 

101 

102 Returns 

103 ------- 

104 tuple[bool, str] 

105 (is_valid, error_message) 

106 """ 

107 if not value or not value.strip(): 

108 return False, f"{field_name} is required" 

109 try: 

110 f = float(value) 

111 if f <= 0: 

112 return False, f"{field_name} must be greater than 0" 

113 return True, "" 

114 except ValueError: 

115 return False, f"{field_name} must be a number" 

116 

117 

118def validate_float_nonneg( 

119 value: str | None, field_name: str = "Value" 

120) -> tuple[bool, str]: 

121 """ 

122 Validate a non-negative float (>= 0). 

123 

124 Parameters 

125 ---------- 

126 value : str | None 

127 String to validate as a non-negative float 

128 field_name : str 

129 Human-readable field name for error messages 

130 

131 Returns 

132 ------- 

133 tuple[bool, str] 

134 (is_valid, error_message) 

135 """ 

136 if not value or not value.strip(): 

137 return False, f"{field_name} is required" 

138 try: 

139 f = float(value) 

140 if f < 0: 

141 return False, f"{field_name} must be 0 or greater" 

142 return True, "" 

143 except ValueError: 

144 return False, f"{field_name} must be a number" 

145 

146 

147def validate_optional_int( 

148 value: str | None, field_name: str = "Value" 

149) -> tuple[bool, str]: 

150 """ 

151 Validate an optional integer (empty value is accepted). 

152 

153 Parameters 

154 ---------- 

155 value : str | None 

156 String to validate as an integer 

157 field_name : str 

158 Human-readable field name for error messages 

159 

160 Returns 

161 ------- 

162 tuple[bool, str] 

163 (is_valid, error_message) 

164 """ 

165 if not value or not value.strip(): 

166 return True, "" 

167 try: 

168 int(value) 

169 return True, "" 

170 except ValueError: 

171 return False, f"{field_name} must be an integer" 

172 

173 

174def validate_smtp_port(value: str | None) -> tuple[bool, str]: 

175 """ 

176 Validate an SMTP port number. 

177 

178 Parameters 

179 ---------- 

180 value : str | None 

181 Port number string to validate 

182 

183 Returns 

184 ------- 

185 tuple[bool, str] 

186 (is_valid, error_message) 

187 """ 

188 if not value or not value.strip(): 

189 return True, "" # Has a sensible default 

190 try: 

191 port = int(value) 

192 if port < 1 or port > 65535: 

193 return False, "SMTP port must be between 1 and 65535" 

194 return True, "" 

195 except ValueError: 

196 return False, "SMTP port must be an integer"