Coverage for biobb_chemistry/babelm/common.py: 66%

150 statements  

« prev     ^ index     » next       coverage.py v7.17.2, created at 2026-11-06 12:40 +0000

1"""Common functions for package biobb_chemistry.babel""" 

2 

3import re 

4from pathlib import Path, PurePath 

5from typing import Optional, Union 

6 

7from biobb_common.tools import file_utils as fu 

8 

9 

10def check_input_path(path, out_log, classname): 

11 """Checks input file""" 

12 if not Path(path).exists(): 

13 fu.log(classname + ": Unexisting input file, exiting", out_log) 

14 raise SystemExit(classname + ": Unexisting input file") 

15 file_extension = PurePath(path).suffix 

16 if not is_valid_input(file_extension[1:]): 

17 fu.log( 

18 classname + ": Format %s in input file is not compatible" % file_extension[1:], 

19 out_log, 

20 ) 

21 raise SystemExit( 

22 classname + ": Format %s in input file is not compatible" % file_extension[1:] 

23 ) 

24 if PurePath(path).name == path or not PurePath(path).is_absolute(): 

25 path = str(PurePath(Path.cwd()).joinpath(path)) 

26 

27 return path 

28 

29 

30def check_output_path(path, out_log, classname): 

31 """Checks output path""" 

32 if PurePath(path).parent and not Path(PurePath(path).parent).exists(): 

33 fu.log( 

34 classname + ": Unexisting output %s output folder, exiting" % type, out_log 

35 ) 

36 raise SystemExit(classname + ": Unexisting %s output folder" % type) 

37 file_extension = PurePath(path).suffix 

38 if not is_valid_input(file_extension[1:]): 

39 fu.log( 

40 classname + ": Format %s in input file is not compatible" % file_extension[1:], 

41 out_log, 

42 ) 

43 raise SystemExit( 

44 classname + ": Format %s in output file is not compatible" % file_extension[1:] 

45 ) 

46 

47 return path 

48 

49 

50def check_input_path_minimize(path, out_log, classname): 

51 """Checks input file""" 

52 if not Path(path).exists(): 

53 fu.log(classname + ": Unexisting input file, exiting", out_log) 

54 raise SystemExit(classname + ": Unexisting input file") 

55 file_extension = PurePath(path).suffix 

56 if not is_valid_input_minimize(file_extension[1:]): 

57 fu.log( 

58 classname + ": Format %s in input file is not compatible" % file_extension[1:], 

59 out_log, 

60 ) 

61 raise SystemExit( 

62 classname + ": Format %s in input file is not compatible" % file_extension[1:] 

63 ) 

64 if PurePath(path).name == path or not PurePath(path).is_absolute(): 

65 path = str(PurePath(Path.cwd()).joinpath(path)) 

66 

67 return path 

68 

69 

70def check_output_path_minimize(path, out_log, classname): 

71 """Checks output path""" 

72 if PurePath(path).parent and not Path(PurePath(path).parent).exists(): 

73 fu.log( 

74 classname + ": Unexisting output %s output folder, exiting" % type, out_log 

75 ) 

76 raise SystemExit(classname + ": Unexisting %s output folder" % type) 

77 file_extension = PurePath(path).suffix 

78 if not is_valid_input_minimize(file_extension[1:]): 

79 fu.log( 

80 classname + ": Format %s in input file is not compatible" % file_extension[1:], 

81 out_log, 

82 ) 

83 raise SystemExit( 

84 classname + ": Format %s in output file is not compatible" % file_extension[1:] 

85 ) 

86 return path 

87 

88 

89def get_binary_path(properties, type): 

90 """Gets binary path""" 

91 return properties.get(type, get_default_value(type)) 

92 

93 

94def get_input_format(input_format, input_path, out_log): 

95 """Checks if provided input format is correct""" 

96 infr = input_format 

97 if not is_valid_input(infr): 

98 file_extension = PurePath(input_path).suffix 

99 fu.log( 

100 "Format %s is not compatible as an input format, assigned input file extension: %s" 

101 % (infr, file_extension[1:]), 

102 out_log, 

103 ) 

104 infr = file_extension[1:] 

105 

106 return infr 

107 

108 

109def check_minimize_property(type, value, out_log): 

110 """Checks all minimize properties""" 

111 value = str(value) 

112 

113 if type == "criteria": 

114 if re.match(r"(\d+(\.\d+)?)", value) or re.match( 

115 r"[+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?", value 

116 ): 

117 return True 

118 else: 

119 fu.log( 

120 "Criteria %s is not correct, assigned default value: %s" 

121 % (value, get_default_value("criteria")), 

122 out_log, 

123 ) 

124 

125 if type == "method": 

126 if value in ["cg", "sd"]: 

127 return True 

128 else: 

129 fu.log( 

130 "Method %s is not correct, assigned default value: %s" 

131 % (value, get_default_value("method")), 

132 out_log, 

133 ) 

134 

135 if type == "force_field": 

136 if value in ["GAFF", "Ghemical", "MMFF94", "MMFF94s", "UFF"]: 

137 return True 

138 else: 

139 fu.log( 

140 "Force field %s is not correct, no force field assigned" % (value), 

141 out_log, 

142 ) 

143 

144 if type == "hydrogens": 

145 if value == "True": 

146 return True 

147 elif value == "False": 

148 pass 

149 else: 

150 fu.log( 

151 "Hydrogens %s is not correct, assigned default value: %s" 

152 % (value, get_default_value("hydrogens")), 

153 out_log, 

154 ) 

155 

156 if type == "steps": 

157 if re.match(r"^\d+$", value): 

158 return True 

159 else: 

160 fu.log( 

161 "Steps %s is not correct, assigned default value: %s" 

162 % (value, get_default_value("steps")), 

163 out_log, 

164 ) 

165 

166 if type == "cutoff": 

167 if value == "True": 

168 return True 

169 elif value == "False": 

170 pass 

171 else: 

172 fu.log( 

173 "Cut-off %s is not correct, assigned default value: %s" 

174 % (value, get_default_value("cutoff")), 

175 out_log, 

176 ) 

177 

178 if type == "rvdw": 

179 if re.match(r"(\d+(\.\d+)?)", value): 

180 return True 

181 else: 

182 fu.log( 

183 "Rvdw %s is not correct, assigned default value: %s" 

184 % (value, get_default_value("rvdw")), 

185 out_log, 

186 ) 

187 

188 if type == "rele": 

189 if re.match(r"(\d+(\.\d+)?)", value): 

190 return True 

191 else: 

192 fu.log( 

193 "Rele %s is not correct, assigned default value: %s" 

194 % (value, get_default_value("rele")), 

195 out_log, 

196 ) 

197 

198 if type == "frequency": 

199 if re.match(r"^\d+$", value): 

200 return True 

201 else: 

202 fu.log( 

203 "Frequency %s is not correct, assigned default value: %s" 

204 % (value, get_default_value("frequency")), 

205 out_log, 

206 ) 

207 

208 return False 

209 

210 

211def get_output_format(output_format, output_path, out_log): 

212 """Checks if provided output format is correct""" 

213 oufr = output_format 

214 if not is_valid_output(oufr): 

215 file_extension = PurePath(output_path).suffix 

216 fu.log( 

217 "Format %s is not compatible as an output format, assigned output file extension: %s" 

218 % (oufr, file_extension[1:]), 

219 out_log, 

220 ) 

221 oufr = file_extension[1:] 

222 

223 return oufr 

224 

225 

226# Records of the PDBQT format (AutoDock / Vina). Open Babel >= 3.2 also copies the 

227# PDB records of the input it does not use (HEADER, COMPND, AUTHOR, SEQRES...) to a 

228# PDBQT output, and Vina rejects them: "Unknown or inappropriate tag found". 

229PDBQT_RECORDS = ("ATOM", "HETATM", "REMARK", "ROOT", "ENDROOT", "BRANCH", "ENDBRANCH", 

230 "TORSDOF", "BEGIN_RES", "END_RES", "MODEL", "ENDMDL", "TER", "END") 

231 

232 

233def remove_non_pdbqt_records(output_format, output_path, out_log): 

234 """Removes from a PDBQT output file the lines that are not PDBQT records""" 

235 if not is_valid_output(output_format): 

236 output_format = PurePath(output_path).suffix[1:] 

237 if output_format != "pdbqt" or not Path(output_path).exists(): 

238 return 

239 

240 lines = Path(output_path).read_text().splitlines(keepends=True) 

241 pdbqt_lines = [line for line in lines if not line.strip() or line.startswith(PDBQT_RECORDS)] 

242 if len(pdbqt_lines) < len(lines): 

243 Path(output_path).write_text("".join(pdbqt_lines)) 

244 fu.log( 

245 "Removed %d non PDBQT records (copied from the input by Open Babel) from %s" 

246 % (len(lines) - len(pdbqt_lines), output_path), 

247 out_log, 

248 ) 

249 

250 

251def get_coordinates(coordinates, out_log): 

252 """Checks if provided coordinates value is correct""" 

253 crd = str(coordinates) 

254 if crd != "3" and crd != "2": 

255 fu.log("Value %s is not compatible as a coordinates value" % crd, out_log) 

256 crd = "" 

257 

258 return crd 

259 

260 

261def get_ph(p, out_log): 

262 """Checks if provided coordinates value is correct""" 

263 ph = str(p) 

264 if p and not isinstance(p, float) and not isinstance(p, int): 

265 ph = "" 

266 fu.log("Incorrect format for pH, no value assigned", out_log) 

267 

268 return ph 

269 

270 

271def get_default_value(key): 

272 """Gives default values according to the given key""" 

273 default_values = { 

274 "coordinates": 2, 

275 "obabel_path": "obabel", 

276 "obminimize_path": "obminimize", 

277 "criteria": 1e-6, 

278 "method": "cg", 

279 "hydrogens": False, 

280 "steps": 2500, 

281 "cutoff": False, 

282 "rvdw": 6.0, 

283 "rele": 10.0, 

284 "frequency": 10, 

285 } 

286 

287 return default_values[key] 

288 

289 

290def is_valid_input(ext): 

291 """Checks if input file format is compatible with Open Babel""" 

292 formats = [ 

293 "dat", 

294 "ent", 

295 "fa", 

296 "fasta", 

297 "gro", 

298 "inp", 

299 "log", 

300 "mcif", 

301 "mdl", 

302 "mmcif", 

303 "mol", 

304 "mol2", 

305 "pdb", 

306 "pdbqt", 

307 "png", 

308 "sdf", 

309 "smi", 

310 "smiles", 

311 "txt", 

312 "xml", 

313 "xtc", 

314 ] 

315 return ext in formats 

316 

317 

318def is_valid_output(ext): 

319 """Checks if output file format is compatible with Open Babel""" 

320 formats = [ 

321 "ent", 

322 "fa", 

323 "fasta", 

324 "gro", 

325 "inp", 

326 "mcif", 

327 "mdl", 

328 "mmcif", 

329 "mol", 

330 "mol2", 

331 "pdb", 

332 "pdbqt", 

333 "png", 

334 "sdf", 

335 "smi", 

336 "smiles", 

337 "txt", 

338 ] 

339 return ext in formats 

340 

341 

342def is_valid_input_minimize(ext): 

343 """Checks if input file format is compatible with Obminimize""" 

344 formats = ["pdb", "mol2"] 

345 return ext in formats 

346 

347 

348def is_valid_output_minimize(ext): 

349 """Checks if output file format is compatible with Obminimize""" 

350 formats = ["pdb", "mol2"] 

351 return ext in formats 

352 

353 

354# TODO: Move this function to biobb_common.tools.file_utils 

355def _from_string_to_list(input_data: Optional[Union[str, list[str]]]) -> list[str]: 

356 """ 

357 Converts a string to a list, splitting by commas or spaces. If the input is already a list, returns it as is. 

358 Returns an empty list if input_data is None. 

359 

360 Parameters: 

361 input_data (str, list, or None): The string, list, or None value to convert. 

362 

363 Returns: 

364 list: A list of string elements or an empty list if input_data is None. 

365 """ 

366 if input_data is None: 

367 return [] 

368 

369 if isinstance(input_data, list): 

370 # If input is already a list, return it 

371 return input_data 

372 

373 # If input is a string, determine the delimiter based on presence of commas 

374 delimiter = "," if "," in input_data else " " 

375 items = input_data.split(delimiter) 

376 

377 # Remove whitespace from each item and ignore empty strings 

378 processed_items = [item.strip() for item in items if item.strip()] 

379 

380 return processed_items