Skip to content

Commit ef9ff07

Browse files
committed
BUG: Preserve sys.path during Slicer module discovery
This fixes a regression introduced in fa915a0 ("BUG: Fix improper Python initialization causing inconsistent interpreter state", 2025-07-17) in which calling `_PyInterpreterState_SetConfig` allowed to update the the argv values associated with the Python config but with the unintended side effect of resetting the `config.module_search_paths`. This commit ensures the `sys.path` updated during module discovery and prior calling `qSlicerCoreApplication::handleCommandLineArguments` where the config is "reinitialized" is restored. Here are the steps: - Captures the initial `sys.path` before applying configuration. - Restores the exact `sys.path` after configuration is applied. - Ensures modifications to `sys.path` by modules are preserved. Notes: - Setting `config.module_search_paths_set=1` alone is insufficient because the copied config may not include runtime additions to `sys.path`.
1 parent 8aee490 commit ef9ff07

File tree

1 file changed

+25
-0
lines changed

1 file changed

+25
-0
lines changed

Base/QTCore/qSlicerCoreApplication.cxx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
#ifdef Slicer_USE_PYTHONQT
6767
// PythonQt includes
6868
# include <PythonQt.h>
69+
# include <PythonQtConversion.h>
6970
#endif
7071

7172
#ifdef Slicer_USE_PYTHONQT_WITH_OPENSSL
@@ -1217,6 +1218,25 @@ void qSlicerCoreApplication::handleCommandLineArguments()
12171218
#else
12181219
if (!qSlicerCoreApplication::testAttribute(qSlicerCoreApplication::AA_DisablePython))
12191220
{
1221+
// Snapshot current sys.path BEFORE applying the config
1222+
// Rationale: _PyInterpreterState_SetConfig will replace sys.path from config.
1223+
// We want an exact restore of the sys.path augmented during Slicer module discovery.
1224+
PythonQtObjectPtr sys;
1225+
sys.setNewRef(PyImport_ImportModule("sys"));
1226+
PythonQtObjectPtr sysPath;
1227+
if (sys)
1228+
{
1229+
sysPath.setNewRef(PyObject_GetAttrString(sys, "path"));
1230+
}
1231+
if (!sys || !sysPath)
1232+
{
1233+
PyErr_Print();
1234+
qSlicerCoreApplication::terminate(EXIT_FAILURE);
1235+
}
1236+
bool ok = false;
1237+
QStringList savedSysPath = PythonQtConv::PyObjToStringList(sysPath, true, ok);
1238+
Q_UNUSED(ok);
1239+
12201240
// Note that 'pythonScript' is ignored if 'extraPythonScript' is specified
12211241
QString pythonScript = options->pythonScript();
12221242
QString extraPythonScript = options->extraPythonScript();
@@ -1263,13 +1283,18 @@ void qSlicerCoreApplication::handleCommandLineArguments()
12631283
Py_ExitStatusException(status);
12641284
}
12651285

1286+
// Apply the updated config back to the running interpreter (This call *replaces*
1287+
// sys.path from the config's module_search_paths)
12661288
if (_PyInterpreterState_SetConfig(&config) < 0)
12671289
{
12681290
PyConfig_Clear(&config);
12691291
PyErr_Print();
12701292
qSlicerCoreApplication::terminate(EXIT_FAILURE);
12711293
}
12721294

1295+
// Restore sys.path exactly as it was before calling "_PyInterpreterState_SetConfig"
1296+
PythonQt::self()->overwriteSysPath(savedSysPath);
1297+
12731298
PyConfig_Clear(&config);
12741299

12751300
// Set 'sys.executable' so that Slicer can be used as a "regular" python interpreter

0 commit comments

Comments
 (0)