App/Gui: Changes to application Python init script

* Better module import error handling

* Allow co-installation of Python2 and Python3 version of FreeCAD

* Add FreeCAD.Logger class for convenient logging in Python. See class
  document for more details.
This commit is contained in:
Zheng, Lei
2019-07-12 09:00:51 +08:00
committed by wmayer
parent 4158673d35
commit ebd60c8595
2 changed files with 419 additions and 40 deletions

View File

@@ -50,11 +50,6 @@ FreeCAD._importFromFreeCAD = removeFromPath
def InitApplications():
try:
import sys,os,traceback,io
except ImportError:
FreeCAD.Console.PrintError("\n\nSeems the python standard libs are not installed, bailing out!\n\n")
raise
# Checking on FreeCAD module path ++++++++++++++++++++++++++++++++++++++++++
ModDir = FreeCAD.getHomePath()+'Mod'
ModDir = os.path.realpath(ModDir)
@@ -62,10 +57,22 @@ def InitApplications():
ExtDir = os.path.realpath(ExtDir)
BinDir = FreeCAD.getHomePath()+'bin'
BinDir = os.path.realpath(BinDir)
libpaths = []
LibDir = FreeCAD.getHomePath()+'lib'
LibDir = os.path.realpath(LibDir)
if os.path.exists(LibDir):
libpaths.append(LibDir)
Lib64Dir = FreeCAD.getHomePath()+'lib64'
Lib64Dir = os.path.realpath(Lib64Dir)
if os.path.exists(Lib64Dir):
libpaths.append(Lib64Dir)
if sys.version_info[0] == 3:
LibPyDir = FreeCAD.getHomePath()+'lib-py3'
else:
LibPyDir = FreeCAD.getHomePath()+'lib-py2'
LibPyDir = os.path.realpath(LibPyDir)
if (os.path.exists(LibPyDir)):
libpaths.append(LibPyDir)
AddPath = FreeCAD.ConfigGet("AdditionalModulePaths").split(";")
HomeMod = FreeCAD.getUserAppDataDir()+"Mod"
HomeMod = os.path.realpath(HomeMod)
@@ -117,12 +124,12 @@ def InitApplications():
# this allows importing with:
# from FreeCAD.Module import package
FreeCAD.__path__ = [ModDir, Lib64Dir, LibDir, HomeMod]
FreeCAD.__path__ = [ModDir] + libpaths + [HomeMod]
# also add these directories to the sys.path to
# not change the old behaviour. once we have moved to
# proper python modules this can eventuelly be removed.
sys.path = [ModDir, Lib64Dir, LibDir, ExtDir] + sys.path
sys.path = [ModDir] + libpaths + [ExtDir] + sys.path
for Dir in ModDict.values():
if ((Dir != '') & (Dir != 'CVS') & (Dir != '__init__.py')):
@@ -140,7 +147,7 @@ def InitApplications():
Log('-'*100+'\n')
Log(traceback.format_exc())
Log('-'*100+'\n')
Err('During initialization the error ' + str(inst) + ' occurred in ' + InstallFile + '\n')
Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n')
Err('Please look into the log file for further information\n')
else:
Log('Init: Initializing ' + Dir + '... done\n')
@@ -156,25 +163,25 @@ def InitApplications():
for _, freecad_module_name, freecad_module_ispkg in pkgutil.iter_modules(freecad.__path__, "freecad."):
if freecad_module_ispkg:
Log('Init: Initializing ' + freecad_module_name + '\n')
freecad_module = importlib.import_module(freecad_module_name)
extension_modules += [freecad_module_name]
if any (module_name == 'init' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)):
try:
try:
freecad_module = importlib.import_module(freecad_module_name)
extension_modules += [freecad_module_name]
if any (module_name == 'init' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)):
importlib.import_module(freecad_module_name + '.init')
Log('Init: Initializing ' + freecad_module_name + '... done\n')
except Exception as inst:
Err('During initialization the error ' + str(inst) + ' occurred in ' + freecad_module_name + '\n')
Err('-'*80+'\n')
Err(traceback.format_exc())
Err('-'*80+'\n')
Log('Init: Initializing ' + freecad_module_name + '... failed\n')
Log('-'*80+'\n')
Log(traceback.format_exc())
Log('-'*80+'\n')
else:
Log('Init: No init module found in ' + freecad_module_name + ', skipping\n')
else:
Log('Init: No init module found in ' + freecad_module_name + ', skipping\n')
except Exception as inst:
Err('During initialization the error "' + str(inst) + '" occurred in ' + freecad_module_name + '\n')
Err('-'*80+'\n')
Err(traceback.format_exc())
Err('-'*80+'\n')
Log('Init: Initializing ' + freecad_module_name + '... failed\n')
Log('-'*80+'\n')
Log(traceback.format_exc())
Log('-'*80+'\n')
except ImportError as inst:
Err('During initialization the error ' + str(inst) + ' occurred\n')
Err('During initialization the error "' + str(inst) + '" occurred\n')
Log("Using "+ModDir+" as module path!\n")
# In certain cases the PathExtension list can contain invalid strings. We concatenate them to a single string
@@ -235,6 +242,378 @@ App.__unit_test__ = []
Log ('Init: starting App::FreeCADInit.py\n')
try:
import sys,os,traceback,io,inspect
from datetime import datetime
except ImportError:
FreeCAD.Console.PrintError("\n\nSeems the python standard libs are not installed, bailing out!\n\n")
raise
class FCADLogger(object):
'''Convenient class for tagged logging.
Example usage:
>>> logger = FreeCAD.Logger('MyModule')
>>> logger.info('log test {}',1)
24.36053 <MyModule> <input>(1): test log 1
The default output format is:
<timestamp> <tag> <source file>(line number): message
The message is formated using new style Python string formating, e.g.
'test {}'.format(1). It is strongly recommanded to not directly use
Python string formating, but pass additional argument indirectly through
various logger print function, because the logger can skip string
evaluation in case the logging level is disabled. For more options,
please consult the docstring of __init__(), catch() and report().
To set/get logger level:
>>> FreeCAD.setLogLevel('MyModule','Trace')
>>> FreeCAD.getLogLevel('MyModule')
4
There are five predefined logger level, each corresponding to an integer
value, as shown below together with the corresponding logger print
method,
0: Error, Logger.error()
1: Warning, Logger.warn()
2: Message, Logger.msg() or info()
3: Log, Logger.log() or debug()
4: Trace, Logger.trace()
FreeCAD.setLogLevel() supports both text and integer value, which allows
you to define your own levels. The level set is persisted to user
configuration file.
By defult any tag has a log level of 2 for release, and 3 for debug
build.
'''
_string_type = str if sys.version_info[0] >= 3 else basestring
_levels = { 'Error':0, 'error':0,
'Warning':1, 'warn':1,
'Message':2, 'msg':2, 'info':2,
'Log':3, 'log':3, 'debug':3,
'Trace':4, 'trace':4,}
_printer = [
FreeCAD.Console.PrintError,
FreeCAD.Console.PrintWarning,
FreeCAD.Console.PrintMessage,
FreeCAD.Console.PrintLog,
FreeCAD.Console.PrintLog ]
def __init__(self, tag, **kargs):
'''Construct a logger instance.
Supported arguments are their default values are,
* tag: a string tag for this logger. The log level of this logger can be
accessed using FreeCAD.getLogLevel(tag)/setLogLevel(tag,level).
All logger instance with the same tag shares the same level
setting.
* printTag (True): whether to print tag
* noUpdateUI (True): whether to update GUI when printing. This is useful
to show log output on lengthy operations. Be
careful though, this may allow unexpected user
interaction when the application is busy, which may
lead to crash
* timing (True): whether to print time stamp
* lineno (True): whether to print source file and line number
* parent (None): provide a parent logger, so that the log printting will
check for parent's log level in addition of its own
* title ('FreeCAD'): message box title used by report()
'''
self.tag = tag
self.laststamp = datetime.now()
for key,default in (('printTag',True),('noUpdateUI',True),
('timing',True),('lineno',True),('parent',None),
('title','FreeCAD')) :
setattr(self,key,kargs.get(key,default))
def _isEnabledFor(self,level):
'''Internal function to check for an integer log level.
* level: integer log level
'''
if self.parent and not self.parent._isEnabledFor(level):
return False
return FreeCAD.getLogLevel(self.tag) >= level
def isEnabledFor(self,level):
'''To check for an integer or text log level.
* level: integer or text log level
'''
if not isinstance(level,int):
level = self.__class__._levels[level]
return self._isEnabledFor(level)
def error(self,msg,*args,**kargs):
'''"Error" level log printer
* msg: message string. May contain new style Python string formatter.
This function accepts additional positional and keyword arguments,
which are forward to string.format() to generate the logging
message. It is strongly recommanded to not directly use Python
string formating, but pass additional arguments here, because the
printer can skip string evaluation in case the logging level is
disabled.
'''
if self._isEnabledFor(0):
frame = kargs.pop('frame',0)+1
self._log(0,msg,frame,args,kargs)
def warn(self,msg,*args,**kargs):
'''"Warning" level log printer
* msg: message string. May contain new style Python string formatter.
This function accepts additional positional and keyword arguments,
which are forward to string.format() to generate the logging
message. It is strongly recommanded to not directly use Python
string formating, but pass additional arguments here, because the
printer can skip string evaluation in case the logging level is
disabled.
'''
if self._isEnabledFor(1):
frame = kargs.pop('frame',0)+1
self._log(1,msg,frame,args,kargs)
def msg(self,msg,*args,**kargs):
'''"Message" level log printer
* msg: message string. May contain new style Python string formatter.
This function accepts additional positional and keyword arguments,
which are forward to string.format() to generate the logging
message. It is strongly recommanded to not directly use Python
string formating, but pass additional arguments here, because the
printer can skip string evaluation in case the logging level is
disabled.
'''
if self._isEnabledFor(2):
frame = kargs.pop('frame',0)+1
self._log(2,msg,frame,args,kargs)
info = msg
def log(self,msg,*args,**kargs):
'''"Log" level log printer
* msg: message string. May contain new style Python string formatter.
This function accepts additional positional and keyword arguments,
which are forward to string.format() to generate the logging
message. It is strongly recommanded to not directly use Python
string formating, but pass additional arguments here, because the
printer can skip string evaluation in case the logging level is
disabled.
'''
if self._isEnabledFor(3):
frame = kargs.pop('frame',0)+1
self._log(3,msg,frame,args,kargs)
debug = log
def trace(self,msg,*args,**kargs):
'''"Trace" level log printer
* msg: message string. May contain new style Python string formatter.
This function accepts additional positional and keyword arguments,
which are forward to string.format() to generate the logging
message. It is strongly recommanded to not directly use Python
string formating, but pass additional arguments here, because the
printer can skip string evaluation in case the logging level is
disabled.
'''
if self._isEnabledFor(4):
frame = kargs.pop('frame',0)+1
self._log(4,msg,frame,args,kargs)
def _log(self,level,msg,frame=0,args=(),kargs=None):
'''Internal log printing function.
* level: integer log level
* msg: message, may contain new style string format specifier
* frame (0): the calling frame for printing source file and line
number. For example, in case you have your own logging
function, and you want to show the callers source
location, then set frame to one.
* args: tuple for postiional arguments to be passed to
string.format()
* kargs: dictionary for keyword arguments to be passed to
string.format()
'''
if (args or kargs) and isinstance(msg,self.__class__._string_type):
if not kargs:
msg = msg.format(*args)
else:
msg = msg.format(*args,**kargs)
prefix = ''
if self.timing:
now = datetime.now()
prefix += '{} '.format((now-self.laststamp).total_seconds())
self.laststamp = now
if self.printTag:
prefix += '<{}> '.format(self.tag)
if self.lineno:
try:
frame = sys._getframe(frame+1)
prefix += '{}({}): '.format(os.path.basename(
frame.f_code.co_filename),frame.f_lineno)
except Exception:
frame = inspect.stack()[frame+1]
prefix += '{}({}): '.format(os.path.basename(frame[1]),frame[2])
self.__class__._printer[level]('{}{}\n'.format(prefix,msg))
if not self.noUpdateUI and FreeCAD.GuiUp:
import FreeCADGui
try:
FreeCADGui.updateGui()
except Exception:
pass
def _catch(self,level,msg,func,args=None,kargs=None):
'''Internal function to log exception of any callable.
* level: integer log level
* msg: message string. Unlike _log(), this argument must not contain
any string formatter.
* func: a callable object
* args: tuple of positional arguments to be passed to func.
* kargs: dictionary of keyword arguments to be passed to func.
'''
try:
if not args:
args = []
if not kargs:
kargs = {}
return func(*args,**kargs)
except Exception:
if self._isEnabledFor(level):
self._log(level,msg+'\n'+traceback.format_exc(),frame=2)
def catch(self,msg,func,*args,**kargs):
'''Catch any exception from a function and print as "Error".
* msg: message string. Unlike log printer, this argument must not
contain any string formatter.
* func: a callable object
* args: tuple of positional arguments to be passed to func.
* kargs: dictionary of keyword arguments to be passed to func.
'''
return self._catch(0,msg,func,args,kargs)
def catchWarn(self,msg,func,*args,**kargs):
'''Catch any exception from a function and print as "Warning".
* msg: message string. Unlike log printer, this argument must not
contain any string formatter.
* func: a callable object
* args: tuple of positional arguments to be passed to func.
* kargs: dictionary of keyword arguments to be passed to func.
'''
return self._catch(1,msg,func,args,kargs)
def catchMsg(self,msg,func,*args,**kargs):
'''Catch any exception from a function and print as "Message".
* msg: message string. Unlike log printer, this argument must not
contain any string formatter.
* func: a callable object
* args: tuple of positional arguments to be passed to func.
* kargs: dictionary of keyword arguments to be passed to func.
'''
return self._catch(2,msg,func,args,kargs)
catchInfo = catchMsg
def catchLog(self,msg,func,*args,**kargs):
'''Catch any exception from a function and print as "Log".
* msg: message string. Unlike log printer, this argument must not
contain any string formatter.
* func: a callable object
* args: tuple of positional arguments to be passed to func.
* kargs: dictionary of keyword arguments to be passed to func.
'''
return self._catch(3,msg,func,args,kargs)
catchDebug = catchLog
def catchTrace(self,msg,func,*args,**kargs):
'''Catch any exception from a function and print as "Trace".
* msg: message string. Unlike log printer, this argument must not
contain any string formatter.
* func: a callable object
* args: tuple of positional arguments to be passed to func.
* kargs: dictionary of keyword arguments to be passed to func.
'''
return self._catch(4,msg,func,args,kargs)
def report(self,msg,func,*args,**kargs):
'''Catch any exception report it with a message box.
* msg: message string. Unlike log printer, this argument must not
contain any string formatter.
* func: a callable object
* args: tuple of positional arguments to be passed to func.
* kargs: dictionary of keyword arguments to be passed to func.
'''
try:
return func(*args,**kargs)
except Exception as e:
self.error(msg+'\n'+traceback.format_exc(),frame=1)
if FreeCAD.GuiUp:
import FreeCADGui,PySide
PySide.QtGui.QMessageBox.critical(
FreeCADGui.getMainWindow(),self.title,str(e))
FreeCAD.Logger = FCADLogger
# init every application by importing Init.py
try:
import traceback

View File

@@ -123,7 +123,7 @@ def InitApplications():
Log('-'*100+'\n')
Log(traceback.format_exc())
Log('-'*100+'\n')
Err('During initialization the error ' + str(inst) + ' occurred in ' + InstallFile + '\n')
Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n')
Err('Please look into the log file for further information\n')
else:
Log('Init: Initializing ' + Dir + '... done\n')
@@ -139,24 +139,24 @@ def InitApplications():
for _, freecad_module_name, freecad_module_ispkg in pkgutil.iter_modules(freecad.__path__, "freecad."):
if freecad_module_ispkg:
Log('Init: Initializing ' + freecad_module_name + '\n')
freecad_module = importlib.import_module(freecad_module_name)
if any (module_name == 'init_gui' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)):
try:
try:
freecad_module = importlib.import_module(freecad_module_name)
if any (module_name == 'init_gui' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)):
importlib.import_module(freecad_module_name + '.init_gui')
Log('Init: Initializing ' + freecad_module_name + '... done\n')
except Exception as inst:
Err('During initialization the error ' + str(inst) + ' occurred in ' + freecad_module_name + '\n')
Err('-'*80+'\n')
Err(traceback.format_exc())
Err('-'*80+'\n')
Log('Init: Initializing ' + freecad_module_name + '... failed\n')
Log('-'*80+'\n')
Log(traceback.format_exc())
Log('-'*80+'\n')
else:
Log('Init: No init_gui module found in ' + freecad_module_name + ', skipping\n')
else:
Log('Init: No init_gui module found in ' + freecad_module_name + ', skipping\n')
except Exception as inst:
Err('During initialization the error "' + str(inst) + '" occurred in ' + freecad_module_name + '\n')
Err('-'*80+'\n')
Err(traceback.format_exc())
Err('-'*80+'\n')
Log('Init: Initializing ' + freecad_module_name + '... failed\n')
Log('-'*80+'\n')
Log(traceback.format_exc())
Log('-'*80+'\n')
except ImportError as inst:
Err('During initialization the error ' + str(inst) + ' occurred\n')
Err('During initialization the error "' + str(inst) + '" occurred\n')
Log ('Init: Running FreeCADGuiInit.py start script...\n')