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