1
2 """Base class for pyFoam-applications
3
4 Classes can also be called with a command-line string"""
5
6 from optparse import OptionGroup
7 from PyFoam.Basics.FoamOptionParser import FoamOptionParser
8 from PyFoam.Error import error,warning,FatalErrorPyFoamException,PyFoamException
9 from PyFoam.RunDictionary.SolutionDirectory import NoTouchSolutionDirectory
10
11 from PyFoam.Basics.TerminalFormatter import TerminalFormatter
12 from PyFoam import configuration
13
14 format=TerminalFormatter()
15 format.getConfigFormat("error")
16 format.getConfigFormat("warn")
17
18 import sys
19 from os import path,getcwd,environ
20 from copy import deepcopy
21
22 from PyFoam.ThirdParty.six import print_
23 from PyFoam.ThirdParty.six import iteritems
24
32
34 if hasattr(sys,'ps1'):
35 warning("Interactive mode. No debugger")
36 sys.__excepthook__(type,value,tb)
37 elif not (sys.stderr.isatty() and sys.stdin.isatty() and sys.stdout.isatty()):
38 warning("Not on a terminal. No debugger")
39 sys.__excepthook__(type,value,tb)
40 elif issubclass(type,SyntaxError) and not debugOnSyntaxError:
41 warning("Syntax error. No debugger")
42 sys.__excepthook__(type,value,tb)
43 else:
44 import traceback
45 try:
46 import ipdb as pdb
47 except ImportError:
48 import pdb
49 traceback.print_exception(type,value,tb)
50 print_()
51 pdb.pm()
52
56
58 """This class is the base for all pyFoam-utilities"""
60 "This class is a quick and dirty wrapper to use a dictionary like a struct"
62 try:
63 return self[key]
64 except KeyError:
65 raise AttributeError(key)
66
67 - def __init__(self,
68 args=None,
69 description=None,
70 usage=None,
71 interspersed=False,
72 nr=None,
73 changeVersion=True,
74 exactNr=True,
75 inputApp=None):
76 """
77 @param description: description of the command
78 @param usage: Usage
79 @param interspersed: Is the command line allowed to be interspersed (options after the arguments)
80 @param args: Command line arguments when using the Application as a 'class' from a script
81 @param nr: Number of required arguments
82 @param changeVersion: May this application change the version of OF used?
83 @param exactNr: Must not have more than the required number of arguments
84 @param inputApp: Application with input data. Used to allow a 'pipe-like' behaviour if the class is used from a Script
85 """
86 self.parser=FoamOptionParser(args=args,
87 description=description,
88 usage=usage,
89 interspersed=interspersed)
90
91 self.calledName=sys.argv[0]
92 self.calledAsClass=(args!=None)
93 if self.calledAsClass:
94 self.calledName=self.__class__.__name__+" used by "+sys.argv[0]
95 self.parser.prog=self.calledName
96
97 self.generalOpts=None
98
99 self.__appData=self.iDict()
100 if inputApp:
101 self.__appData["inputData"]=inputApp.getData()
102
103 grp=OptionGroup(self.parser,
104 "Default",
105 "Options common to all PyFoam-applications")
106
107 if changeVersion:
108
109 grp.add_option("--foamVersion",
110 dest="foamVersion",
111 default=None,
112 help="Change the OpenFOAM-version that is to be used")
113 if "WM_PROJECT_VERSION" in environ:
114 grp.add_option("--currentFoamVersion",
115 dest="foamVersion",
116 const=environ["WM_PROJECT_VERSION"],
117 default=None,
118 action="store_const",
119 help="Use the current OpenFOAM-version "+environ["WM_PROJECT_VERSION"])
120
121 grp.add_option("--force-32bit",
122 dest="force32",
123 default=False,
124 action="store_true",
125 help="Forces the usage of a 32-bit-version if that version exists as 32 and 64 bit. Only used when --foamVersion is used")
126 grp.add_option("--force-64bit",
127 dest="force64",
128 default=False,
129 action="store_true",
130 help="Forces the usage of a 64-bit-version if that version exists as 32 and 64 bit. Only used when --foamVersion is used")
131 grp.add_option("--force-debug",
132 dest="compileOption",
133 const="Debug",
134 default=None,
135 action="store_const",
136 help="Forces the value Debug for the WM_COMPILE_OPTION. Only used when --foamVersion is used")
137 grp.add_option("--force-opt",
138 dest="compileOption",
139 const="Opt",
140 default=None,
141 action="store_const",
142 help="Forces the value Opt for the WM_COMPILE_OPTION. Only used when --foamVersion is used")
143
144 grp.add_option("--psyco-accelerated",
145 dest="psyco",
146 default=False,
147 action="store_true",
148 help="Accelerate the script using the psyco-library (EXPERIMENTAL and requires a separatly installed psyco)")
149 grp.add_option("--profile-python",
150 dest="profilePython",
151 default=False,
152 action="store_true",
153 help="Profile the python-script (not the OpenFOAM-program) - mostly of use for developers")
154 grp.add_option("--profile-cpython",
155 dest="profileCPython",
156 default=False,
157 action="store_true",
158 help="Profile the python-script (not the OpenFOAM-program) using the better cProfile library - mostly of use for developers")
159 grp.add_option("--profile-hotshot",
160 dest="profileHotshot",
161 default=False,
162 action="store_true",
163 help="Profile the python-script using the hotshot-library (not the OpenFOAM-program) - mostly of use for developers - EXPERIMENTAL")
164
165 dbg=OptionGroup(self.parser,
166 "Debugging",
167 "Options mainly used for debugging PyFoam-Utilities")
168
169 dbg.add_option("--traceback-on-error",
170 dest="traceback",
171 default=False,
172 action="store_true",
173 help="Prints a traceback when an error is encountered (for debugging)")
174 dbg.add_option("--interactive-debugger",
175 dest="interactiveDebug",
176 default=False,
177 action="store_true",
178 help="In case of an exception start the interactive debugger PDB. Also implies --traceback-on-error")
179 dbg.add_option("--catch-USR1-signal",
180 dest="catchUSR1Signal",
181 default=False,
182 action="store_true",
183 help="If the USR1-signal is sent to the application with 'kill -USR1 <pid>' the application ens and prints a traceback. If interactive debugging is enabled then the debugger is entered. Use to investigate hangups")
184 dbg.add_option("--also-catch-TERM-signal",
185 dest="alsoCatchTERMsignal",
186 default=False,
187 action="store_true",
188 help="In addition to USR1 catch the regular TERM-kill")
189 dbg.add_option("--keyboard-interrupt-trace",
190 dest="keyboardInterrupTrace",
191 default=False,
192 action="store_true",
193 help="Make the application behave like with --catch-USR1-signal if <Ctrl>-C is pressed")
194 dbg.add_option("--syntax-error-debugger",
195 dest="syntaxErrorDebugger",
196 default=False,
197 action="store_true",
198 help="Only makes sense with --interactive-debugger: Do interactive debugging even when a syntax error was encountered")
199 dbg.add_option("--i-am-a-developer",
200 dest="developerMode",
201 default=False,
202 action="store_true",
203 help="Switch on all of the above options. Usually this makes only sense if you're developing PyFoam'")
204 dbg.add_option("--interactive-after-execution",
205 dest="interacticeAfterExecution",
206 default=False,
207 action="store_true",
208 help="Instead of ending execution drop to an interactive shell (which is IPython if possible)")
209
210 grp.add_option("--dump-application-data",
211 dest="dumpAppData",
212 default=False,
213 action="store_true",
214 help="Print the dictionary with the generated application data after running")
215 grp.add_option("--pickle-application-data",
216 dest="pickleApplicationData",
217 default=None,
218 action="store",
219 type="string",
220 help="""\
221 Write a pickled version of the application data to a file. If the
222 filename given is 'stdout' then the pickled data is written to
223 stdout. The usual standard output is then captured and added to the
224 application data as an entry 'stdout' (same for 'stderr'). Be careful
225 with these option for commands that generate a lot of output""")
226
227 self.parser.add_option_group(grp)
228 self.parser.add_option_group(dbg)
229
230 self.addOptions()
231 self.parser.parse(nr=nr,exactNr=exactNr)
232 self.opts=self.parser.getOptions()
233
234 if "WM_PROJECT_VERSION" not in environ:
235 warning("$WM_PROJECT_VERSION unset. PyFoam will not be able to determine the OpenFOAM-version and behave strangely")
236 if self.opts.developerMode:
237 self.opts.syntaxErrorDebugger=True
238 self.opts.keyboardInterrupTrace=True
239 self.opts.alsoCatchTERMsignal=True
240 self.opts.catchUSR1Signal=True
241 self.opts.interactiveDebug=True
242 self.opts.traceback=True
243
244 if self.opts.interactiveDebug:
245 sys.excepthook=lambda a1,a2,a3:pyFoamExceptionHook(a1,
246 a2,
247 a3,
248 debugOnSyntaxError=self.opts.syntaxErrorDebugger)
249 self.opts.traceback=True
250 if self.opts.catchUSR1Signal:
251 import signal
252 signal.signal(signal.SIGUSR1,pyFoamSIG1HandlerPrintStack)
253 if self.opts.alsoCatchTERMsignal:
254 signal.signal(signal.SIGTERM,pyFoamSIG1HandlerPrintStack)
255 self.opts.traceback=True
256
257 if self.opts.keyboardInterrupTrace:
258 import signal
259 signal.signal(signal.SIGINT,pyFoamSIG1HandlerPrintStack)
260 self.opts.traceback=True
261
262 if self.opts.psyco:
263 try:
264 import psyco
265 psyco.full()
266 except ImportError:
267 warning("No psyco installed. Continuing without acceleration")
268
269 if self.opts.profilePython or self.opts.profileCPython or self.opts.profileHotshot:
270 if sum([self.opts.profilePython,self.opts.profileCPython,self.opts.profileHotshot])>1:
271 self.error("Profiling with hotshot and regular profiling are mutual exclusive")
272 print_("Running profiled")
273 if self.opts.profilePython:
274 import profile
275 elif self.opts.profileCPython:
276 import cProfile as profile
277 else:
278 import hotshot
279 profileData=path.basename(sys.argv[0])+".profile"
280 if self.opts.profilePython or self.opts.profileCPython:
281 profile.runctx('self.run()',None,{'self':self},profileData)
282 print_("Reading python profile")
283 import pstats
284 stats=pstats.Stats(profileData)
285 else:
286 profileData+=".hotshot"
287 prof=hotshot.Profile(profileData)
288 prof.runctx('self.run()',{},{'self':self})
289 print_("Writing and reading hotshot profile")
290 prof.close()
291 import hotshot.stats
292 stats=hotshot.stats.load(profileData)
293 stats.strip_dirs()
294 stats.sort_stats('time','calls')
295 stats.print_stats(20)
296
297 self.parser.restoreEnvironment()
298 else:
299 try:
300 if self.opts.pickleApplicationData=="stdout":
301
302 from PyFoam.ThirdParty.six.moves import StringIO
303
304 oldStdout=sys.stdout
305 oldStderr=sys.stderr
306 sys.stdout=StringIO()
307 sys.stderr=StringIO()
308
309 result=self.run()
310
311
312 self.parser.restoreEnvironment()
313
314 if self.opts.pickleApplicationData=="stdout":
315
316 self.__appData["stdout"]=sys.stdout.getvalue()
317 self.__appData["stderr"]=sys.stderr.getvalue()
318 sys.stdout=oldStdout
319 sys.stderr=oldStderr
320
321 if self.opts.pickleApplicationData:
322 from PyFoam.ThirdParty.six.moves import cPickle as pickle
323 if self.opts.pickleApplicationData=="stdout":
324 pick=pickle.Pickler(sys.stdout)
325 else:
326 pick=pickle.Pickler(open(self.opts.pickleApplicationData,'wb'))
327 pick.dump(self.__appData)
328 del pick
329 if self.opts.dumpAppData:
330 import pprint
331 print_("Application data:")
332 printer=pprint.PrettyPrinter()
333 printer.pprint(self.__appData)
334
335 if self.opts.interacticeAfterExecution:
336 print_("\nDropping to interactive shell ... ",end="")
337 ns={}
338 ns.update(locals())
339 ns.update(globals())
340 try:
341 import IPython
342 print_("found IPython ...",end="")
343 if "embed" in dir(IPython):
344 print_("up-to-date IPython\n")
345 IPython.embed(user_ns=ns)
346 else:
347 print_("old-school IPython\n")
348 IPython.Shell.IPythonShellEmbed(argv="",user_ns=ns)()
349
350 except ImportError:
351 print_("no IPython -> regular shell\n")
352 from code import InteractiveConsole
353 c=InteractiveConsole(ns)
354 c.interact()
355 print_("\nEnding interactive shell\n")
356 return result
357 except PyFoamException:
358 e=sys.exc_info()[1]
359 if self.opts.traceback or self.calledAsClass:
360 raise
361 else:
362 self.errorPrint(str(e))
363
365 """Get application data"""
366 try:
367 return self.__appData[key]
368 except KeyError:
369 print_("available keys:",list(self.__appData.keys()))
370 raise
371
373 """Iterate over the application data"""
374 for k in self.__appData:
375 yield k
376
378 return iter(list(self.__appData.keys()))
379
381 return iter(list(self.__appData.items()))
382
384 try:
385 return self.__appData[key]
386 except KeyError:
387 raise AttributeError(key)
388
390 """Get the application data"""
391 return deepcopy(self.__appData)
392
394 """Set the application data
395
396 @param data: dictionary whose entries will be added to the
397 application data (possibly overwriting old entries of the same name)"""
398 for k,v in iteritems(data):
399 self.__appData[k]=deepcopy(v)
400
402 if self.generalOpts==None:
403 self.generalOpts=OptionGroup(self.parser,
404 "General",
405 "General options for the control of OpenFOAM-runs")
406 self.parser.add_option_group(self.generalOpts)
407
409 """
410 Add options to the parser
411 """
412 pass
413
415 """
416 Run the real application
417 """
418 error("Not a valid application")
419
420
422 """Raise a error exception. How it will be handled is a different story
423 @param args: Arguments to the exception
424 """
425 raise PyFoamApplicationException(self,*args)
426
428 """
429 Prints an error message and exits
430 @param args: Arguments that are to be printed
431 """
432 if sys.stdout.isatty():
433 print_(format.error, end=' ')
434 print_("Error in",self.calledName,":", end=' ')
435 for a in args:
436 print_(a, end=' ')
437 if sys.stdout.isatty():
438 print_(format.reset)
439 sys.exit(-1)
440
442 """
443 Prints a warning message
444 @param args: Arguments that are to be printed
445 """
446 if sys.stdout.isatty():
447 print_(format.warn, end=' ')
448 print_("Warning in",self.calledName,":", end=' ')
449 for a in args:
450 print_(a, end=' ')
451 if sys.stdout.isatty():
452 print_(format.reset)
453
455 """
456 Don't print a warning message
457 @param args: Arguments that are to be printed
458 """
459 pass
460
461 - def checkCase(self,name,fatal=True,verbose=True):
462 """
463 Check whether this is a valid OpenFOAM-case
464 @param name: the directory-bame that is supposed to be the case
465 @param fatal: If this is not a case then the application ends
466 @param verbose: If this is not a case no warning is issued
467 """
468 if fatal:
469 func=self.error
470 elif verbose:
471 func=self.warning
472 else:
473 func=self.silent
474
475 if not path.exists(name):
476 func("Case",name,"does not exist")
477 return False
478 if not path.isdir(name):
479 func("Case",name,"is not a directory")
480 return False
481 if not path.exists(path.join(name,"system")):
482 func("Case",name,"does not have a 'system' directory")
483 return False
484 if not path.exists(path.join(name,"constant")):
485 func("Case",name,"does not have a 'constant' directory")
486 return False
487
488 return True
489
504
511
512
513