Coverage for pesummary/core/cli/actions.py: 79.8%

188 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-05-02 08:42 +0000

1# Licensed under an MIT style license -- see LICENSE.md 

2 

3import re 

4import copy 

5import os 

6import ast 

7import argparse 

8import configparser 

9import numpy as np 

10 

11__author__ = ["Charlie Hoy <charlie.hoy@ligo.org>"] 

12 

13 

14class CheckFilesExistAction(argparse.Action): 

15 """Class to extend the argparse.Action to identify if files exist 

16 """ 

17 def __call__(self, parser, namespace, values, option_string=None): 

18 setattr(namespace, self.dest, values) 

19 self.check_input(values) 

20 

21 def check_input(self, value): 

22 """Check that all files provided exist 

23 

24 Parameters 

25 ---------- 

26 value: str, list, dict 

27 data structure that you wish to check 

28 """ 

29 if isinstance(value, list): 

30 for ff in value: 

31 _ = self.check_input(ff) 

32 elif isinstance(value, str): 

33 _ = self._is_file(value) 

34 elif isinstance(value, dict): 

35 for _value in value.values(): 

36 _ = self.check_input(_value) 

37 else: 

38 _ = self._is_file(value) 

39 

40 def _is_file(self, ff): 

41 """Return True if the file exists else raise a FileNotFoundError 

42 exception 

43 

44 Parameters 

45 ---------- 

46 ff: str 

47 path to file you wish to check 

48 """ 

49 cond = any(_str in ff for _str in ["*", "@", "https://"]) 

50 cond2 = isinstance(ff, str) and ff.lower() == "none" 

51 if not os.path.isfile(ff) and not cond and not cond2: 

52 raise FileNotFoundError( 

53 "The file '{}' provided for '{}' does not exist".format( 

54 ff, self.dest 

55 ) 

56 ) 

57 return True 

58 

59 

60class BaseDeprecatedAction(object): 

61 """Class to handle deprecated argparse options 

62 """ 

63 class _BaseDeprecatedAction(object): 

64 def __call__(self, *args, **kwargs): 

65 import warnings 

66 msg = ( 

67 "The option '{}' is out-of-date and may not be supported in " 

68 "future releases.".format(self.option_strings[0]) 

69 ) 

70 if _new_option is not None: 

71 msg += " Please use '{}'".format(_new_option) 

72 warnings.warn(msg) 

73 return super().__call__(*args, **kwargs) 

74 

75 def __new__(cls, *args, new_option=None, **kwargs): 

76 global _new_option 

77 _new_option = new_option 

78 

79 

80class DeprecatedStoreAction(BaseDeprecatedAction): 

81 """Class to handle deprecated argparse._StoreAction options 

82 """ 

83 class _DeprecatedStoreAction( 

84 BaseDeprecatedAction._BaseDeprecatedAction, argparse._StoreAction 

85 ): 

86 pass 

87 

88 def __new__(cls, *args, **kwargs): 

89 super().__new__(cls, *args, **kwargs) 

90 return cls._DeprecatedStoreAction 

91 

92 

93class DeprecatedStoreTrueAction(BaseDeprecatedAction): 

94 """Class to handle deprecated argparse._StoreTrueAction options 

95 """ 

96 class _DeprecatedStoreTrueAction( 

97 BaseDeprecatedAction._BaseDeprecatedAction, argparse._StoreTrueAction 

98 ): 

99 pass 

100 

101 def __new__(cls, *args, **kwargs): 

102 super().__new__(cls, *args, **kwargs) 

103 return cls._DeprecatedStoreTrueAction 

104 

105 

106class DeprecatedStoreFalseAction(BaseDeprecatedAction): 

107 """Class to handle deprecated argparse._StoreFalseAction options 

108 """ 

109 class _DeprecatedStoreFalseAction( 

110 BaseDeprecatedAction._BaseDeprecatedAction, argparse._StoreFalseAction 

111 ): 

112 pass 

113 

114 def __new__(cls, *args, **kwargs): 

115 super().__new__(cls, *args, **kwargs) 

116 return cls._DeprecatedStoreFalseAction 

117 

118 

119class ConfigAction(argparse.Action): 

120 """Class to extend the argparse.Action to handle dictionaries as input 

121 """ 

122 def __call__(self, parser, namespace, values, option_string=None): 

123 setattr(namespace, self.dest, values) 

124 

125 items = {} 

126 config = configparser.ConfigParser() 

127 config.optionxform = str 

128 try: 

129 config.read(values) 

130 sections = config.sections() 

131 for section in sections: 

132 for key, value in config.items(section): 

133 if value.lower() == "true": 

134 items[key] = True 

135 elif value.lower() == "false": 

136 items[key] = False 

137 elif value.lower() == "none": 

138 items[key] = None 

139 else: 

140 try: 

141 _type = getattr( 

142 parser, "_option_string_actions" 

143 )["--{}".format(key)].type 

144 except Exception: 

145 _type = None 

146 if ":" in value or "{" in value: 

147 try: 

148 items[key] = self.dict_from_str(value, dtype=_type) 

149 except Exception: 

150 items[key] = value 

151 elif "," in value or "[" in value: 

152 items[key] = self.list_from_str(value, _type) 

153 else: 

154 if _type is not None: 

155 items[key] = _type(value) 

156 else: 

157 items[key] = value 

158 except Exception: 

159 pass 

160 for i in vars(namespace).keys(): 

161 if i in items.keys(): 

162 setattr(namespace, i, items[i]) 

163 

164 @staticmethod 

165 def dict_from_str(string, delimiter=":", dtype=None): 

166 """Reformat the string into a dictionary 

167 

168 Parameters 

169 ---------- 

170 string: str 

171 string that you would like reformatted into a dictionary 

172 """ 

173 string = string.replace("'", "") 

174 string = string.replace('"', '') 

175 string = string.replace("=", ":") 

176 string = string.replace(delimiter, ":") 

177 if "dict(" in string: 

178 string = string.replace("dict(", "{") 

179 string = string.replace(")", "}") 

180 string = string.replace(" ", "") 

181 string = re.sub(r'([A-Za-z/\.0-9][^\[\],:"}]*)', r'"\g<1>"', string) 

182 string = string.replace('""', '"') 

183 try: 

184 mydict = ast.literal_eval(string) 

185 except ValueError as e: 

186 pass 

187 for key in mydict: 

188 if isinstance(mydict[key], str) and mydict[key].lower() == "true": 

189 mydict[key] = True 

190 elif isinstance(mydict[key], str) and mydict[key].lower() == "false": 

191 mydict[key] = False 

192 else: 

193 try: 

194 mydict[key] = int(mydict[key]) 

195 except ValueError: 

196 try: 

197 mydict[key] = float(mydict[key]) 

198 except ValueError: 

199 mydict[key] = mydict[key] 

200 if dtype is not None: 

201 mydict[key] = dtype(mydict[key]) 

202 return mydict 

203 

204 @staticmethod 

205 def list_from_str(string, dtype=None): 

206 """Reformat the string into a list 

207 

208 Parameters 

209 ---------- 

210 string: str 

211 string that you would like reformatted into a list 

212 """ 

213 list = [] 

214 string = string.replace("'", "") 

215 if "[" in string: 

216 string = string.replace("[", "") 

217 if "]" in string: 

218 string = string.replace("]", "") 

219 if ", " in string: 

220 list = string.split(", ") 

221 elif "," in string: 

222 list = string.split(",") 

223 else: 

224 list = [string] 

225 if dtype is not None: 

226 list = [dtype(_) for _ in list] 

227 return list 

228 

229 

230class DictionaryAction(argparse.Action): 

231 """Class to extend the argparse.Action to handle dictionaries as input 

232 """ 

233 def __call__(self, parser, namespace, values, option_string=None): 

234 bool = [True if ':' in value else False for value in values] 

235 if all(i is True for i in bool): 

236 setattr(namespace, self.dest, {}) 

237 elif all(i is False for i in bool): 

238 setattr(namespace, self.dest, []) 

239 else: 

240 raise ValueError("Did not understand input") 

241 

242 items = getattr(namespace, self.dest) 

243 items = copy.copy(items) 

244 for value in values: 

245 value = value.split(':') 

246 if len(value) > 2: 

247 value = [":".join(value[:-1]), value[-1]] 

248 if len(value) == 2: 

249 if value[0] in items.keys(): 

250 if not isinstance(items[value[0]], list): 

251 items[value[0]] = [items[value[0]]] 

252 items[value[0]].append(value[1]) 

253 else: 

254 items[value[0]] = value[1] 

255 elif len(value) == 1: 

256 items.append(value[0]) 

257 else: 

258 raise ValueError("Did not understand input") 

259 setattr(namespace, self.dest, items) 

260 

261 

262class DelimiterSplitAction(argparse.Action): 

263 """Class to extend the argparse.Action to handle inputs which need to be split with 

264 with a provided delimiter 

265 """ 

266 def __call__(self, parser, namespace, values, option_string=None): 

267 import sys 

268 

269 args = np.array(sys.argv[1:]) 

270 cond1 = "--delimiter" in args 

271 cond2 = False 

272 if cond1: 

273 cond2 = ( 

274 float(np.argwhere(args == "--delimiter")) 

275 > float(np.argwhere(args == self.option_strings[0])) 

276 ) 

277 if cond1 and cond2: 

278 raise ValueError( 

279 "Please provide the '--delimiter' command line argument " 

280 "before the '{}' argument".format(self.option_strings[0]) 

281 ) 

282 delimiter = namespace.delimiter 

283 items = {} 

284 for value in values: 

285 value = value.split(delimiter) 

286 if len(value) > 2: 

287 raise ValueError( 

288 "'{}' appears multiple times. Please choose a different " 

289 "delimiter".format(delimiter) 

290 ) 

291 if value[0] in items.keys() and not isinstance(items[value[0]], list): 

292 items[value[0]] = [items[value[0]]] 

293 if value[0] in items.keys(): 

294 items[value[0]].append(value[1]) 

295 elif len(value) == 1 and len(values) == 1: 

296 items = [value[0]] 

297 else: 

298 items[value[0]] = value[1] 

299 setattr(namespace, self.dest, items)