Coverage for torxtools/argtools.py: 53%

75 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-20 22:02 +0000

1""" 

2Parser types for command-line options, arguments and sub-commands 

3 

4""" 

5 

6# Class names are not CamelCase since they act as functions 

7# pylint: disable=invalid-name 

8 

9import os 

10from argparse import Action, ArgumentTypeError 

11 

12__all__ = [ 

13 "is_dir", 

14 "is_file", 

15 "is_file_and_not_dir", 

16 "is_int_between", 

17 "is_int_negative", 

18 "is_int_negative_or_zero", 

19 "is_int_positive", 

20 "is_int_positive_or_zero", 

21 "is_not_dir", 

22] 

23 

24 

25def _get_int_number(value: int, message: str) -> int: 

26 try: 

27 return int(value) 

28 except (ValueError, TypeError): 

29 raise ArgumentTypeError(message) from None 

30 

31 

32class is_int_positive(Action): 

33 """ 

34 Verify that argument passed is a positive integer. 

35 

36 Example 

37 ------- 

38 

39 .. code-block:: 

40 

41 parser.add_argument( 

42 "--size", "-s", 

43 dest="size", 

44 help="[MB] Minimal size of attachment", 

45 action=argtools.is_int_positive, 

46 default=100, 

47 ) 

48 """ 

49 

50 def __call__(self, _parser, namespace, value, *args, **kwargs): 

51 message = f"value '{value}' must be positive" 

52 number = _get_int_number(value, message) 

53 if number <= 0: 

54 raise ArgumentTypeError(message) from None 

55 setattr(namespace, self.dest, number) 

56 

57 

58class is_int_positive_or_zero(Action): 

59 """ 

60 Verify that argument passed is a positive integer or zero. 

61 

62 Example 

63 ------- 

64 

65 .. code-block:: 

66 

67 parser.add_argument( 

68 "--size", "-s", 

69 dest="size", 

70 help="[MB] Minimal size of attachment", 

71 action=argtools.is_int_positive_or_zero, 

72 default=100, 

73 ) 

74 """ 

75 

76 def __call__(self, _parser, namespace, value, *args, **kwargs): 

77 message = f"value '{value}' must be positive or zero" 

78 number = _get_int_number(value, message) 

79 if number < 0: 

80 raise ArgumentTypeError(message) from None 

81 setattr(namespace, self.dest, number) 

82 

83 

84class is_int_negative(Action): 

85 """ 

86 Verify that argument passed is a negative integer. 

87 

88 Example 

89 ------- 

90 

91 .. code-block:: 

92 

93 parser.add_argument( 

94 "--temperature", "-t", 

95 dest="temperature", 

96 help="[C] Temperature colder than freezing point", 

97 action=argtools.is_int_negative, 

98 default=-50, 

99 ) 

100 """ 

101 

102 def __call__(self, _parser, namespace, value, *args, **kwargs): 

103 message = f"value '{value}' must be negative" 

104 number = _get_int_number(value, message) 

105 if number >= 0: 

106 raise ArgumentTypeError(message) from None 

107 setattr(namespace, self.dest, number) 

108 

109 

110class is_int_negative_or_zero(Action): 

111 """ 

112 Verify that argument passed is a negative integer or zero. 

113 

114 Example 

115 ------- 

116 

117 .. code-block:: 

118 

119 parser.add_argument( 

120 "--temperature", "-t", 

121 dest="temperature", 

122 help="[C] Temperature colder than freezing point", 

123 action=argtools.is_int_negative_or_zero, 

124 default=-50, 

125 ) 

126 """ 

127 

128 def __call__(self, _parser, namespace, value, *args, **kwargs): 

129 message = f"value '{value}' must be negative or zero" 

130 number = _get_int_number(value, message) 

131 if number > 0: 

132 raise ArgumentTypeError(message) from None 

133 setattr(namespace, self.dest, number) 

134 

135 

136class is_file(Action): 

137 """ 

138 Returns path if path is an existing regular file. 

139 This follows symbolic links 

140 

141 Example 

142 ------- 

143 

144 .. code-block:: 

145 

146 parser.add_argument( 

147 "-f", "--file" 

148 action=argtools.is_file 

149 ) 

150 """ 

151 

152 def __call__(self, _parser, namespace, value, *args, **kwargs): 

153 message = f"value '{value}' must be an existing file" 

154 if not os.path.isfile(str(value)): 

155 raise ArgumentTypeError(message) from None 

156 setattr(namespace, self.dest, value) 

157 

158 

159class is_not_dir(Action): 

160 """ 

161 :deprecated: Since 1.1.3 

162 

163 Use is_file_and_not_dir instead. 

164 In the next minor version, this function will only check that it's not a directory, a no longer check if it's a file or not 

165 

166 Example 

167 ------- 

168 

169 .. code-block:: 

170 

171 parser.add_argument( 

172 "-f", "--file" 

173 action=argtools.is_not_dir 

174 ) 

175 """ 

176 

177 def __call__(self, _parser, namespace, value, *args, **kwargs): 

178 message = f"value '{value}' must be an existing file" 

179 value = str(value) 

180 if not os.path.exists(value): 

181 raise ArgumentTypeError(message) from None 

182 if os.path.isdir(value): 

183 raise ArgumentTypeError(message) from None 

184 setattr(namespace, self.dest, value) 

185 

186 

187class is_file_and_not_dir(Action): 

188 """ 

189 Path must exist and be a file and not a directory. 

190 

191 Example 

192 ------- 

193 

194 .. code-block:: 

195 

196 parser.add_argument( 

197 "-f", "--file" 

198 action=argtools.is_file_and_not_dir 

199 ) 

200 """ 

201 

202 def __call__(self, _parser, namespace, value, *args, **kwargs): 

203 message = f"value '{value}' must be an existing file" 

204 value = str(value) 

205 if not os.path.exists(value): 

206 raise ArgumentTypeError(message) from None 

207 if os.path.isdir(value): 

208 raise ArgumentTypeError(message) from None 

209 setattr(namespace, self.dest, value) 

210 

211 

212class is_dir(Action): 

213 """ 

214 Returns path if path is an existing regular directory. 

215 This follows symbolic links 

216 

217 Example 

218 ------- 

219 

220 .. code-block:: 

221 

222 parser.add_argument( 

223 "-d", "--dir" 

224 action=argtools.is_dir 

225 ) 

226 """ 

227 

228 def __call__(self, _parser, namespace, value, *args, **kwargs): 

229 message = f"value '{value}' must be an existing directory" 

230 if not os.path.isdir(str(value)): 

231 raise ArgumentTypeError(message) from None 

232 setattr(namespace, self.dest, value) 

233 

234 

235def is_int_between(minv, maxv): 

236 """ 

237 Verify that argument passed is between minv and maxv (inclusive) 

238 

239 Parameters 

240 ---------- 

241 minv: int 

242 minimum value 

243 

244 maxv: int 

245 maximum value 

246 

247 Example 

248 ------- 

249 

250 .. code-block:: 

251 

252 parser.add_argument( 

253 "--percent", 

254 help="Percentage (0 to 100)", 

255 action=argtools.is_int_between(0, 100), 

256 default=50, 

257 ) 

258 """ 

259 

260 class _is_int_between(Action): 

261 def __call__(self, _parser, namespace, value, *args, **kwargs): 

262 message = f"value '{value}' must be between {minv} and {maxv}" 

263 number = _get_int_number(value, message) 

264 if number < minv or number > maxv: 

265 raise ArgumentTypeError(message) from None 

266 setattr(namespace, self.dest, number) 

267 

268 return _is_int_between