0
|
1 #
|
|
2 # Copyright (C) 2014 INRA
|
|
3 #
|
|
4 # This program is free software: you can redistribute it and/or modify
|
|
5 # it under the terms of the GNU General Public License as published by
|
|
6 # the Free Software Foundation, either version 3 of the License, or
|
|
7 # (at your option) any later version.
|
|
8 #
|
|
9 # This program is distributed in the hope that it will be useful,
|
|
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12 # GNU General Public License for more details.
|
|
13 #
|
|
14 # You should have received a copy of the GNU General Public License
|
|
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16 #
|
|
17
|
|
18 __author__ = 'Frederic Escudie - Plateforme bioinformatique Toulouse'
|
|
19 __copyright__ = 'Copyright (C) 2015 INRA'
|
|
20 __license__ = 'GNU General Public License'
|
|
21 __version__ = '0.2.0'
|
|
22 __email__ = 'frogs@inra.fr'
|
|
23 __status__ = 'prod'
|
|
24
|
|
25 import os
|
|
26 import sys
|
|
27 import time
|
|
28 import subprocess
|
|
29 from subprocess import Popen, PIPE
|
|
30
|
|
31
|
|
32 def which(exec_name):
|
|
33 """
|
|
34 @summary: Returns the software absolute path.
|
|
35 @param exec_name: [str] The software file (example : blastn or classifier.jar).
|
|
36 @return: [str] The software absolute path.
|
|
37 """
|
|
38 path_locations = os.getenv("PATH").split(os.pathsep) + sys.path
|
|
39 exec_path = None
|
|
40 for current_location in path_locations:
|
|
41 if exec_path is None and os.path.isfile(os.path.join(current_location, exec_name)):
|
|
42 exec_path = os.path.abspath( os.path.join(current_location, exec_name) )
|
|
43 if exec_path is None:
|
|
44 raise Exception( "The software '" + exec_name + "' cannot be retrieved in path." )
|
|
45 return exec_path
|
|
46
|
|
47
|
|
48 def prevent_shell_injections(argparse_namespace, excluded_args=None):
|
|
49 """
|
|
50 @summary: Raises an exception if one parameter contains a backquote or a semi-colon.
|
|
51 @param argparse_namespace: [Namespase] The result of parser.parse_args().
|
|
52 @param excluded_args: [list] List of unchecked parameters.
|
|
53 """
|
|
54 exceptions = list() if excluded_args is None else excluded_args
|
|
55 for param_name in argparse_namespace.__dict__.keys():
|
|
56 if not param_name in exceptions:
|
|
57 param_val = getattr(argparse_namespace, param_name)
|
|
58 if issubclass(param_val.__class__, list):
|
|
59 new_param_val = list()
|
|
60 for val in param_val:
|
|
61 if ';' in val.encode('utf8') or '`' in val.encode('utf8') or '|' in val.encode('utf8'):
|
|
62 raise Exception( "';' and '`' are unauthorized characters." )
|
|
63 elif param_val is not None and issubclass(param_val.__class__, str):
|
|
64 if ';' in param_val.encode('utf8') or '`' in param_val.encode('utf8') or '|' in param_val.encode('utf8'):
|
|
65 raise Exception( "';' and '`' are unauthorized characters." )
|
|
66
|
|
67
|
|
68 class Cmd:
|
|
69 """
|
|
70 @summary : Command wrapper.
|
|
71 """
|
|
72 def __init__(self, program, description, exec_parameters, version_parameters=None):
|
|
73 """
|
|
74 @param exec_parameters: [str] The parameters to execute the program. Two possibles syntaxes.
|
|
75 If the parameter contains the string '##PROGRAM##', this tag will be replaced by the program parameter before submit.
|
|
76 Otherwise the parameters will be added after the program in command line.
|
|
77 @param version_parameters: [str] The parameters to get the program version. Two possibles syntaxes.
|
|
78 If the parameter contains the string '##PROGRAM##', this tag will be replaced by the program parameter before submit.
|
|
79 Otherwise the parameters will be added after the program in command line.
|
|
80 """
|
|
81 self.program = program
|
|
82 self.description = description
|
|
83 self.exec_parameters = exec_parameters
|
|
84 self.version_parameters = version_parameters
|
|
85
|
|
86 def get_cmd(self):
|
|
87 """
|
|
88 @summary : Returns the command line.
|
|
89 @return : [str] The command line.
|
|
90 """
|
|
91 cmd = None
|
|
92 if '##PROGRAM##' in self.exec_parameters:
|
|
93 cmd = self.exec_parameters.replace('##PROGRAM##', self.program)
|
|
94 else:
|
|
95 cmd = self.program + ' ' + self.exec_parameters
|
|
96 return cmd
|
|
97
|
|
98 def get_version(self, location='stderr'):
|
|
99 """
|
|
100 @summary : Returns the program version number.
|
|
101 @param location : [str] If the version command returns the version number on 'stdout' or on 'stderr'.
|
|
102 @return : [str] version number if this is possible, otherwise this method return 'unknown'.
|
|
103 """
|
|
104 if self.version_parameters is None:
|
|
105 return "unknown"
|
|
106 else:
|
|
107 try:
|
|
108 cmd = self.program + ' ' + self.version_parameters
|
|
109 if '##PROGRAM##' in self.exec_parameters:
|
|
110 cmd = self.version_parameters.replace('##PROGRAM##', self.program)
|
|
111 p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
|
|
112 stdout, stderr = p.communicate()
|
|
113 if location == 'stderr':
|
|
114 return stderr.strip()
|
|
115 else:
|
|
116 return stdout.strip()
|
|
117 except:
|
|
118 raise Exception( "Version cannot be retrieve for the software '" + self.program + "'." )
|
|
119
|
|
120 def parser(self, log_file):
|
|
121 """
|
|
122 @summary : Parse the command results to add information in log_file.
|
|
123 @log_file : [str] Path to the sample process log file.
|
|
124 """
|
|
125 pass
|
|
126
|
|
127 def submit(self, log_file=None):
|
|
128 """
|
|
129 @summary : Launch command, trace this action in log and parse results.
|
|
130 @log_file : [str] Path to the sample process log file.
|
|
131 """
|
|
132 # Log
|
|
133 if log_file is not None:
|
|
134 FH_log = Logger( log_file )
|
|
135 FH_log.write( '# ' + self.description + ' (' + os.path.basename(self.program) + ' version : ' + self.get_version() + ')\n' )
|
|
136 FH_log.write( 'Command:\n\t' + self.get_cmd() + '\n\n' )
|
|
137 FH_log.write( 'Execution:\n\tstart: ' + time.strftime("%d %b %Y %H:%M:%S", time.localtime()) + '\n' )
|
|
138 FH_log.close()
|
|
139 # Process
|
|
140 subprocess.check_output( self.get_cmd(), shell=True )
|
|
141 # Log
|
|
142 if log_file is not None:
|
|
143 FH_log = Logger( log_file )
|
|
144 FH_log.write( '\tend: ' + time.strftime("%d %b %Y %H:%M:%S", time.localtime()) + '\n\n' )
|
|
145 FH_log.close()
|
|
146 # Post-process results
|
|
147 self.parser(log_file)
|
|
148
|
|
149
|
|
150 class Logger:
|
|
151 """
|
|
152 @summary: Log file handler.
|
|
153 """
|
|
154
|
|
155 def __init__(self, filepath=None):
|
|
156 """
|
|
157 @param filepath: [str] The log filepath. [default : STDOUT]
|
|
158 """
|
|
159 self.filepath = filepath
|
|
160 if self.filepath is not None and self.filepath is not sys.stdout:
|
|
161 self.file_handle = open( self.filepath, "a" )
|
|
162 else:
|
|
163 self.file_handle = sys.stdout
|
|
164
|
|
165 def __del__(self):
|
|
166 """
|
|
167 @summary: Closed file handler when the logger is detroyed.
|
|
168 """
|
|
169 self.close()
|
|
170
|
|
171 def close(self):
|
|
172 """
|
|
173 @summary: Closed file handler.
|
|
174 """
|
|
175 if self.filepath is not None and self.filepath is not sys.stdout:
|
|
176 if self.file_handle is not None:
|
|
177 self.file_handle.close()
|
|
178 self.file_handle = None
|
|
179
|
|
180 def write(self, msg):
|
|
181 """
|
|
182 @summary: Writes msg on file.
|
|
183 @param msg: [str] The message to write.
|
|
184 """
|
|
185 self.file_handle.write( msg )
|
|
186
|
|
187 @staticmethod
|
|
188 def static_write(filepath, msg):
|
|
189 """
|
|
190 @summary: Writes msg on file.
|
|
191 @param filepath: [str] The log filepath. [default : STDOUT]
|
|
192 @param msg: [str] The message to write.
|
|
193 """
|
|
194 if filepath is not None and filepath is not sys.stdout:
|
|
195 FH_log = open( filepath, "a" )
|
|
196 FH_log.write( msg )
|
|
197 FH_log.close()
|
|
198 else:
|
|
199 sys.stdout.write( msg )
|
|
200
|
|
201
|
|
202 class TmpFiles:
|
|
203 """
|
|
204 @summary: Manager for temporary files.
|
|
205 @note:
|
|
206 tmpFiles = TmpFiles(out_dir)
|
|
207 try:
|
|
208 ...
|
|
209 tmp_seq = tmpFiles.add( "toto.fasta" )
|
|
210 ...
|
|
211 tmp_log = tmpFiles.add( "log.txt" )
|
|
212 ...
|
|
213 finaly:
|
|
214 tmpFiles.deleteAll()
|
|
215 """
|
|
216 def __init__(self, tmp_dir, prefix=None):
|
|
217 """
|
|
218 @param tmp_dir: [str] The temporary directory path.
|
|
219 @param prefix: [str] The prefix added to each temporary file [default: <TIMESTAMP>_<PID>].
|
|
220 """
|
|
221 if prefix is None:
|
|
222 prefix = str(time.time()) + "_" + str(os.getpid())
|
|
223 self.files = list()
|
|
224 self.dirs = list()
|
|
225 self.tmp_dir = tmp_dir
|
|
226 self.prefix = prefix
|
|
227
|
|
228 def add(self, filename, prefix=None, dir=None):
|
|
229 """
|
|
230 @summary: Add a temporary file.
|
|
231 @param filename: The filename without prefix.
|
|
232 @param prefix: The prefix added [default: TmpFiles.prefix].
|
|
233 @param dir: The directory path [default: TmpFiles.tmp_dir].
|
|
234 @return: [str] The filepath.
|
|
235 """
|
|
236 # Default
|
|
237 if prefix is None:
|
|
238 prefix = self.prefix
|
|
239 if dir is None:
|
|
240 dir = self.tmp_dir
|
|
241 # Process
|
|
242 filepath = os.path.join(dir, prefix + "_" + filename)
|
|
243 self.files.append(filepath)
|
|
244 return filepath
|
|
245
|
|
246 def add_dir(self, dirname, prefix=None, dir=None):
|
|
247 """
|
|
248 @summary: Add a temporary dir.
|
|
249 @param filename: The dirname without prefix.
|
|
250 @param prefix: The prefix added [default: TmpFiles.prefix].
|
|
251 @param dir: The directory path [default: TmpFiles.tmp_dir].
|
|
252 @return: [str] The filepath.
|
|
253 """
|
|
254 # Default
|
|
255 if prefix is None:
|
|
256 prefix = self.prefix
|
|
257 if dir is None:
|
|
258 dir = self.tmp_dir
|
|
259 # Process
|
|
260 dirpath = os.path.join(dir, prefix + "_" + dirname)
|
|
261 self.dirs.append(dirpath)
|
|
262 return dirpath
|
|
263
|
|
264 def delete(self, filepath):
|
|
265 """
|
|
266 @summary: Deletes the specified temporary file.
|
|
267 @param filepath: [str] The file path to delete.
|
|
268 """
|
|
269 self.files.remove(filepath)
|
|
270 if os.path.exists(filepath): os.remove(filepath)
|
|
271
|
|
272 def delete_dir(self, dirpath):
|
|
273 """
|
|
274 @summary: Deletes the specified temporary dir.
|
|
275 @param filepath: [str] The file path to delete.
|
|
276 """
|
|
277 if dirpath in self.dirs: self.dirs.remove(dirpath)
|
|
278
|
|
279 if os.path.exists(dirpath):
|
|
280 for root, dirnames,filenames in os.walk(dirpath):
|
|
281 for f in filenames:
|
|
282 if f in self.files: self.files.remove(os.path.join(dirpath,f))
|
|
283 if os.path.exists(os.path.join(dirpath,f)): os.remove(os.path.join(dirpath,f))
|
|
284 for d in dirnames:
|
|
285 if d in self.dirs: self.dirs.remove(os.path.join(dirpath,d))
|
|
286 if os.path.exists(os.path.join(dirpath,d)): self.delete_dir(os.path.join(dirpath,d))
|
|
287 os.rmdir(dirpath)
|
|
288
|
|
289 def deleteAll(self):
|
|
290 """
|
|
291 @summary: Deletes all temporary files.
|
|
292 """
|
|
293 all_tmp_files = [tmp_file for tmp_file in self.files]
|
|
294 for tmp_file in all_tmp_files:
|
|
295 self.delete(tmp_file)
|
|
296
|
|
297 all_tmp_dirs=[tmp_dir for tmp_dir in self.dirs]
|
|
298 for tmp_dir in all_tmp_dirs:
|
|
299 self.delete_dir(tmp_dir) |