Skip to content

Commit 173f75c

Browse files
author
wonder
committedNov 7, 2009
Moved most of the python support code to a new module qgis.utils
This will enable more flexibility in plugins handling Hopefully it doesn't break anything :-) git-svn-id: http://svn.osgeo.org/qgis/trunk/qgis@11950 c8812cc2-4d05-0410-92ff-de0c093fc19c

File tree

3 files changed

+192
-141
lines changed

3 files changed

+192
-141
lines changed
 

‎python/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,5 @@ ENDIF (BINDINGS_GLOBAL_INSTALL)
8989

9090

9191
# Step 4: install built libs to python's site packages
92-
INSTALL(FILES __init__.py ${CMAKE_CURRENT_BINARY_DIR}/qgisconfig.py ${BINDINGS_LIBS} DESTINATION ${SITE_PKG_PATH}/qgis)
92+
INSTALL(FILES __init__.py utils.py ${CMAKE_CURRENT_BINARY_DIR}/qgisconfig.py ${BINDINGS_LIBS} DESTINATION ${SITE_PKG_PATH}/qgis)
9393

‎python/utils.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
QGIS utilities module
3+
4+
"""
5+
6+
from PyQt4.QtCore import QCoreApplication
7+
import sys
8+
import traceback
9+
10+
11+
#######################
12+
# ERROR HANDLING
13+
14+
def showException(type, value, tb, msg):
15+
lst = traceback.format_exception(type, value, tb)
16+
if msg == None:
17+
msg = QCoreApplication.translate('Python', 'An error has occured while executing Python code:')
18+
txt = '<font color="red">%s</font><br><br>' % msg
19+
for s in lst:
20+
txt += s
21+
txt += '<br>%s<br>%s<br><br>' % (QCoreApplication.translate('Python','Python version:'), sys.version)
22+
txt += '%s %s' % (QCoreApplication.translate('Python','Python path:'), str(sys.path))
23+
txt = txt.replace('\n', '<br>')
24+
txt = txt.replace(' ', '&nbsp; ') # preserve whitespaces for nicer output
25+
26+
from qgis.core import QgsMessageOutput
27+
msg = QgsMessageOutput.createMessageOutput()
28+
msg.setTitle(QCoreApplication.translate('Python', 'Python error'))
29+
msg.setMessage(txt, QgsMessageOutput.MessageHtml)
30+
msg.showMessage()
31+
32+
def qgis_excepthook(type, value, tb):
33+
showException(type, value, tb, None)
34+
35+
def installErrorHook():
36+
sys.excepthook = qgis_excepthook
37+
38+
def uninstallErrorHook():
39+
sys.excepthook = sys.__excepthook__
40+
41+
# install error hook() on module load
42+
installErrorHook()
43+
44+
# initialize 'iface' object
45+
iface = None
46+
47+
def initInterface(pointer):
48+
from qgis.gui import QgisInterface
49+
from sip import wrapinstance
50+
global iface
51+
iface = wrapinstance(pointer, QgisInterface)
52+
53+
54+
#######################
55+
# CONSOLE OUTPUT
56+
57+
old_stdout = sys.stdout
58+
console_output = None
59+
60+
# hook for python console so all output will be redirected
61+
# and then shown in console
62+
def console_displayhook(obj):
63+
console_output = obj
64+
65+
class QgisOutputCatcher:
66+
def __init__(self):
67+
self.data = ''
68+
def write(self, stuff):
69+
self.data += stuff
70+
def get_and_clean_data(self):
71+
tmp = self.data
72+
self.data = ''
73+
return tmp
74+
75+
def installConsoleHooks():
76+
sys.displayhook = console_displayhook
77+
sys.stdout = QgisOutputCatcher()
78+
79+
def uninstallConsoleHooks():
80+
sys.displayhook = sys.__displayhook__
81+
sys.stdout = old_stdout
82+
83+
84+
#######################
85+
# PLUGINS
86+
87+
# dictionary of plugins
88+
plugins = {}
89+
90+
def pluginMetadata(packageName, fct):
91+
""" fetch metadata from a plugin """
92+
try:
93+
package = sys.modules[packageName]
94+
return getattr(package, fct)()
95+
except:
96+
return "__error__"
97+
98+
def loadPlugin(packageName):
99+
""" load plugin's package and ensure that plugin is reloaded when changed """
100+
101+
try:
102+
__import__(packageName)
103+
return True
104+
except:
105+
pass # continue...
106+
107+
# snake in the grass, we know it's there
108+
sys.path_importer_cache.clear()
109+
110+
# retry
111+
try:
112+
__import__(packageName)
113+
reload(packageName)
114+
return True
115+
except:
116+
msgTemplate = QCoreApplication.translate("Python", "Couldn't load plugin '%1' from ['%2']")
117+
msg = msgTemplate.arg(packageName).arg("', '".join(sys.path))
118+
showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg)
119+
return False
120+
121+
122+
def startPlugin(packageName):
123+
""" initialize the plugin """
124+
global plugins, iface
125+
126+
package = sys.modules[packageName]
127+
128+
errMsg = QCoreApplication.translate("Python", "Couldn't load plugin %1" ).arg(packageName)
129+
130+
# create an instance of the plugin
131+
try:
132+
plugins[packageName] = package.classFactory(iface)
133+
except:
134+
msg = QCoreApplication.translate("Python", "%1 due an error when calling its classFactory() method").arg(errMsg)
135+
showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg)
136+
return False
137+
138+
# initGui
139+
try:
140+
plugins[packageName].initGui()
141+
except:
142+
msg = QCoreApplication.translate("Python", "%1 due an error when calling its initGui() method" ).arg( errMsg )
143+
showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg)
144+
return False
145+
146+
return True
147+
148+
149+
def unloadPlugin(packageName):
150+
""" unload and delete plugin! """
151+
global plugins
152+
153+
try:
154+
plugins[packageName].unload()
155+
del plugins[packageName]
156+
return True
157+
except Exception, e:
158+
errMsg = QCoreApplication.translate("Python", "Error while unloading plugin %1").arg(packageName)
159+
showException(sys.exc_type, sys.exc_value, sys.exc_traceback, msg)
160+
return False

‎src/python/qgspythonutilsimpl.cpp

Lines changed: 31 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,6 @@ void QgsPythonUtilsImpl::initPython( QgisInterface* interface )
6161
// also add path to plugins
6262
runString( "sys.path = [\"" + pythonPath() + "\", \"" + homePluginsPath() + "\", \"" + pluginsPath() + "\"] + sys.path" );
6363

64-
runString( "import traceback" ); // for formatting stack traces
65-
runString( "import __main__" ); // to access explicitly global variables
66-
6764
// import SIP
6865
if ( !runString( "from sip import wrapinstance, unwrapinstance",
6966
QObject::tr( "Couldn't load SIP module." ) + "\n" + QObject::tr( "Python support will be disabled." ) ) )
@@ -88,49 +85,16 @@ void QgsPythonUtilsImpl::initPython( QgisInterface* interface )
8885
return;
8986
}
9087

91-
// hook that will show information and traceback in message box
92-
runString(
93-
"def qgis_except_hook_msg(type, value, tb, msg):\n"
94-
" lst = traceback.format_exception(type, value, tb)\n"
95-
" if msg == None: msg = '" + QObject::tr( "An error has occured while executing Python code:" ).replace( "'", "\\'" ) + "'\n"
96-
" txt = '<font color=\"red\">'+msg+'</font><br><br>'\n"
97-
" for s in lst:\n"
98-
" txt += s\n"
99-
" txt += '<br>" + QObject::tr( "Python version:" ).replace( "'", "\\'" ) + "<br>' + sys.version + '<br><br>'\n"
100-
" txt += '" + QObject::tr( "Python path:" ).replace( "'", "\\'" ) + "' + str(sys.path)\n"
101-
" txt = txt.replace('\\n', '<br>')\n"
102-
" txt = txt.replace(' ', '&nbsp; ')\n" // preserve whitespaces for nicer output
103-
" \n"
104-
" msg = QgsMessageOutput.createMessageOutput()\n"
105-
" msg.setTitle('" + QObject::tr( "Python error" ).replace( "'", "\\'" ) + "')\n"
106-
" msg.setMessage(txt, QgsMessageOutput.MessageHtml)\n"
107-
" msg.showMessage()\n" );
108-
runString(
109-
"def qgis_except_hook(type, value, tb):\n"
110-
" qgis_except_hook_msg(type, value, tb, None)\n" );
111-
runString(
112-
"class QgisOutputCatcher:\n"
113-
" def __init__(self):\n"
114-
" self.data = ''\n"
115-
" def write(self, stuff):\n"
116-
" self.data += stuff\n"
117-
" def get_and_clean_data(self):\n"
118-
" tmp = self.data\n"
119-
" self.data = ''\n"
120-
" return tmp\n" );
121-
122-
// hook for python console so all output will be redirected
123-
// and then shown in console
124-
runString(
125-
"def console_display_hook(obj):\n"
126-
" __main__.__result = obj\n" );
127-
128-
installErrorHook();
88+
// import QGIS utils
89+
error_msg = QObject::tr( "Couldn't load QGIS utils." ) + "\n" + QObject::tr( "Python support will be disabled." );
90+
if ( !runString("import qgis.utils", error_msg) )
91+
{
92+
exitPython();
93+
return;
94+
}
12995

13096
// initialize 'iface' object
131-
runString( "iface = wrapinstance(" + QString::number(( unsigned long ) interface ) + ", QgisInterface)" );
132-
runString( "plugins = {}" );
133-
97+
runString( "qgis.utils.initInterface(" + QString::number(( unsigned long ) interface ) + ")" );
13498
}
13599

136100
void QgsPythonUtilsImpl::exitPython()
@@ -149,26 +113,22 @@ bool QgsPythonUtilsImpl::isEnabled()
149113

150114
void QgsPythonUtilsImpl::installErrorHook()
151115
{
152-
runString( "sys.excepthook = qgis_except_hook" );
116+
runString( "qgis.utils.installErrorHook()" );
153117
}
154118

155119
void QgsPythonUtilsImpl::uninstallErrorHook()
156120
{
157-
runString( "sys.excepthook = sys.__excepthook__" );
121+
runString( "qgis.utils.uninstallErrorHook()" );
158122
}
159123

160124
void QgsPythonUtilsImpl::installConsoleHooks()
161125
{
162-
runString( "sys.displayhook = console_display_hook\n" );
163-
164-
runString( "_old_stdout = sys.stdout\n" );
165-
runString( "sys.stdout = QgisOutputCatcher()\n" );
126+
runString( "qgis.utils.installConsoleHooks()" );
166127
}
167128

168129
void QgsPythonUtilsImpl::uninstallConsoleHooks()
169130
{
170-
runString( "sys.displayhook = sys.__displayhook__" );
171-
runString( "sys.stdout = _old_stdout" );
131+
runString( "qgis.utils.uninstallConsoleHooks()" );
172132
}
173133

174134

@@ -193,6 +153,8 @@ bool QgsPythonUtilsImpl::runString( const QString& command, QString msgOnError )
193153
msgOnError = QObject::tr( "An error occured during execution of following code:" ) + "\n<tt>" + command + "</tt>";
194154
}
195155

156+
// TODO: use python implementation
157+
196158
QString traceback = getTraceback();
197159
QString path, version;
198160
evalString( "str(sys.path)", path );
@@ -339,7 +301,7 @@ bool QgsPythonUtilsImpl::getError( QString& errorClassName, QString& errorText )
339301

340302
QString QgsPythonUtilsImpl::getResult()
341303
{
342-
return getVariableFromMain( "__result" );
304+
return getVariableFromMain( "qgis.utils.console_output" );
343305
}
344306

345307
QString QgsPythonUtilsImpl::PyObjectToQString( PyObject* obj )
@@ -425,6 +387,8 @@ bool QgsPythonUtilsImpl::evalString( const QString& command, QString& result )
425387
{
426388
PyObject* res = PyRun_String( command.toUtf8().data(), Py_eval_input, mMainDict, mMainDict );
427389

390+
// TODO: error handling
391+
428392
if ( res != NULL )
429393
{
430394
result = PyObjectToQString( res );
@@ -472,106 +436,33 @@ QStringList QgsPythonUtilsImpl::pluginList()
472436

473437
QString QgsPythonUtilsImpl::getPluginMetadata( QString pluginName, QString function )
474438
{
475-
QString command = pluginName + "." + function + "()";
476-
QString retval = "???";
477-
478-
// temporary disable error hook - UI will handle this gracefully
479-
uninstallErrorHook();
480-
PyObject* obj = PyRun_String( command.toUtf8().data(), Py_eval_input, mMainDict, mMainDict );
481-
482-
if ( PyErr_Occurred() )
483-
{
484-
PyErr_Print(); // just print it to console
485-
PyErr_Clear();
486-
retval = "__error__";
487-
}
488-
else if ( PyUnicode_Check( obj ) )
489-
{
490-
PyObject* utf8 = PyUnicode_AsUTF8String( obj );
491-
if ( utf8 )
492-
retval = QString::fromUtf8( PyString_AS_STRING( utf8 ) );
493-
else
494-
retval = "__error__";
495-
Py_XDECREF( utf8 );
496-
}
497-
else if ( PyString_Check( obj ) )
498-
{
499-
retval = PyString_AS_STRING( obj );
500-
}
501-
else
502-
{
503-
// bad python return value
504-
retval = "__error__";
505-
}
506-
Py_XDECREF( obj );
507-
508-
installErrorHook();
509-
return retval;
439+
QString res;
440+
QString str = "qgis.utils.pluginMetadata('" + pluginName + "', '"+function+"')";
441+
evalString(str, res);
442+
//QgsDebugMsg("metadata "+pluginName+" - '"+function+"' = "+res);
443+
return res;
510444
}
511445

512446

513447
bool QgsPythonUtilsImpl::loadPlugin( QString packageName )
514448
{
515-
// load plugin's package and ensure that plugin is reloaded when changed
516-
runString(
517-
"try:\n"
518-
" import " + packageName + "\n"
519-
" __main__.__plugin_result = 'OK'\n"
520-
"except:\n"
521-
" __main__.__plugin_result = 'ERROR'\n" );
522-
523-
if ( getVariableFromMain( "__plugin_result" ) == "OK" )
524-
return true;
525-
526-
// snake in the grass, we know it's there
527-
runString( "sys.path_importer_cache.clear()" );
528-
529-
// retry
530-
runString(
531-
"try:\n"
532-
" import " + packageName + "\n"
533-
" reload(" + packageName + ")\n"
534-
" __main__.__plugin_result = 'OK'\n"
535-
"except:\n"
536-
" qgis_except_hook_msg(sys.exc_type, sys.exc_value, sys.exc_traceback, "
537-
"'Couldn\\'t load plugin \"" + packageName + "\" from [\\'' + '\\', \\''.join(sys.path) + '\\']')\n"
538-
" __main__.__plugin_result = 'ERROR'\n" );
539-
540-
return getVariableFromMain( "__plugin_result" ) == "OK";
449+
QString output;
450+
evalString("qgis.utils.loadPlugin('" + packageName + "')", output);
451+
return (output == "True");
541452
}
542453

543454

544455
bool QgsPythonUtilsImpl::startPlugin( QString packageName )
545456
{
546-
QString pluginPythonVar = "plugins['" + packageName + "']";
547-
548-
QString errMsg = QObject::tr( "Couldn't load plugin %1" ).arg( packageName );
549-
550-
// create an instance of the plugin
551-
if ( !runString( pluginPythonVar + " = " + packageName + ".classFactory(iface)",
552-
QObject::tr( "%1 due an error when calling its classFactory() method" ).arg( errMsg ) ) )
553-
return false;
554-
555-
// initGui
556-
if ( !runString( pluginPythonVar + ".initGui()",
557-
QObject::tr( "%1 due an error when calling its initGui() method" ).arg( errMsg ) ) )
558-
return false;
559-
560-
return true;
457+
QString output;
458+
evalString("qgis.utils.startPlugin('" + packageName + "')", output);
459+
return (output == "True");
561460
}
562461

563462

564463
bool QgsPythonUtilsImpl::unloadPlugin( QString packageName )
565464
{
566-
// unload and delete plugin!
567-
QString varName = "plugins['" + packageName + "']";
568-
569-
QString errMsg = QObject::tr( "Error while unloading plugin %1" ).arg( packageName );
570-
571-
if ( !runString( varName + ".unload()", errMsg ) )
572-
return false;
573-
if ( !runString( "del " + varName, errMsg ) )
574-
return false;
575-
576-
return true;
465+
QString output;
466+
evalString("qgis.utils.unloadPlugin('" + packageName + "')", output);
467+
return (output == "True");
577468
}

0 commit comments

Comments
 (0)
Please sign in to comment.