Package PyFoam :: Package Applications :: Module PyFoamApplication
[hide private]
[frames] | no frames]

Source Code for Module PyFoam.Applications.PyFoamApplication

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