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 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 # the options are evaluated in Basics.FoamOptionParser 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 # Redirect output to memory 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 # do this at the earliest possible moment 351 self.parser.restoreEnvironment() 352 353 if self.opts.pickleApplicationData=="stdout": 354 # restore stdout 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
403 - def __getitem__(self,key):
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
411 - def __iter__(self):
412 """Iterate over the application data""" 413 for k in self.__appData: 414 yield k
415
416 - def iterkeys(self):
417 return iter(list(self.__appData.keys()))
418
419 - def iteritems(self):
420 return iter(list(self.__appData.items()))
421
422 - def __getattr__(self,key):
423 try: 424 return self.__appData[key] 425 except KeyError: 426 raise AttributeError(key)
427
428 - def getData(self):
429 """Get the application data""" 430 return deepcopy(self.__appData)
431
432 - def setData(self,data):
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
440 - def ensureGeneralOptions(self):
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
447 - def addOptions(self):
448 """ 449 Add options to the parser 450 """ 451 pass
452
453 - def run(self):
454 """ 455 Run the real application 456 """ 457 error("Not a valid application")
458 459
460 - def error(self,*args):
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
466 - def errorPrint(self,*args):
467 """ 468 Prints an error message and exits 469 @param args: Arguments that are to be printed 470 """ 471 if isatty(sys.stdout): 472 print_(format.error, end=' ') 473 print_("Error in",self.calledName,":", end=' ') 474 for a in args: 475 print_(a, end=' ') 476 if isatty(sys.stdout): 477 print_(format.reset) 478 else: 479 print_() 480 sys.exit(-1)
481
482 - def warning(self,*args):
483 """ 484 Prints a warning message 485 @param args: Arguments that are to be printed 486 """ 487 if isatty(sys.stdout): 488 print_(format.warn, end=' ') 489 print_("Warning in",self.calledName,":", end=' ') 490 for a in args: 491 print_(a, end=' ') 492 if isatty(sys.stdout): 493 print_(format.reset) 494 else: 495 print_()
496
497 - def silent(self,*args):
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
533 - def addToCaseLog(self,name,*text):
534 """ 535 Add information about the application that was run to the case-log 536 """ 537 538 logline=[NoTouchSolutionDirectory(name)] 539 if self.calledName==sys.argv[0]: 540 logline+=["Application:",path.basename(sys.argv[0])]+sys.argv[1:] 541 else: 542 logline+=["Application:",self.calledName] 543 544 logline+=[" | with cwd",getcwd()," | "] 545 logline+=text 546 NoTouchSolutionDirectory.addToHistory(*logline)
547
548 - def addLocalConfig(self,directory=None):
549 """ 550 Adds a local directory (assuming it is found) 551 """ 552 if directory!=None: 553 configuration().addFile(path.join(directory,"LocalConfigPyFoam"),silent=True)
554 555 # Should work with Python3 and Python2 556