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
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 12:40 +0000
1"""Common functions for package biobb_chemistry.babel"""
3import re
4from pathlib import Path, PurePath
5from typing import Optional, Union
7from biobb_common.tools import file_utils as fu
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))
27 return path
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 )
47 return path
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))
67 return path
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
89def get_binary_path(properties, type):
90 """Gets binary path"""
91 return properties.get(type, get_default_value(type))
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:]
106 return infr
109def check_minimize_property(type, value, out_log):
110 """Checks all minimize properties"""
111 value = str(value)
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
208 return False
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:]
223 return oufr
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")
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
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 )
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 = ""
258 return crd
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)
268 return ph
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 }
287 return default_values[key]
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
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
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
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
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.
360 Parameters:
361 input_data (str, list, or None): The string, list, or None value to convert.
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 []
369 if isinstance(input_data, list):
370 # If input is already a list, return it
371 return input_data
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)
377 # Remove whitespace from each item and ignore empty strings
378 processed_items = [item.strip() for item in items if item.strip()]
380 return processed_items