Skip to content

Commit f64d739

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 a125c00 commit f64d739

File tree

1 file changed

+26
-0
lines changed

1 file changed

+26
-0
lines changed

Base/QTCore/qSlicerCoreApplication.cxx

Lines changed: 26 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
@@ -1263,13 +1264,38 @@ void qSlicerCoreApplication::handleCommandLineArguments()
12631264
Py_ExitStatusException(status);
12641265
}
12651266

1267+
// Snapshot current sys.path BEFORE applying the config
1268+
// Rationale: _PyInterpreterState_SetConfig will replace sys.path from config.
1269+
// We want an exact restore of the sys.path augmented during Slicer module discovery.
1270+
PythonQtObjectPtr sys;
1271+
sys.setNewRef(PyImport_ImportModule("sys"));
1272+
PythonQtObjectPtr sysPath;
1273+
if (sys)
1274+
{
1275+
sysPath.setNewRef(PyObject_GetAttrString(sys, "path"));
1276+
}
1277+
if (!sys || !sysPath)
1278+
{
1279+
PyConfig_Clear(&config);
1280+
PyErr_Print();
1281+
qSlicerCoreApplication::terminate(EXIT_FAILURE);
1282+
}
1283+
bool ok = false;
1284+
QStringList savedSysPath = PythonQtConv::PyObjToStringList(sysPath, true, ok);
1285+
Q_UNUSED(ok);
1286+
1287+
// Apply the updated config back to the running interpreter (This call *replaces*
1288+
// sys.path from the config's module_search_paths)
12661289
if (_PyInterpreterState_SetConfig(&config) < 0)
12671290
{
12681291
PyConfig_Clear(&config);
12691292
PyErr_Print();
12701293
qSlicerCoreApplication::terminate(EXIT_FAILURE);
12711294
}
12721295

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

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

0 commit comments

Comments
 (0)