Skip to content

Commit

Permalink
Merge from 5.x: PR #19253
Browse files Browse the repository at this point in the history
Fixes #19205
  • Loading branch information
ccordoba12 committed Sep 7, 2022
2 parents b882512 + 38e3adf commit fce2b9c
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 38 deletions.
81 changes: 44 additions & 37 deletions spyder/app/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,8 +671,8 @@ def setup(self):
from spyder.plugins.help.utils.sphinxify import CSS_PATH, DARK_CSS_PATH
logger.info("*** Start of MainWindow setup ***")
logger.info("Updating PYTHONPATH")
path_dict = self.get_spyder_pythonpath_dict()
self.update_python_path(path_dict)
self.load_python_path()
self.update_python_path()

logger.info("Applying theme configuration...")
ui_theme = self.get_conf('ui_theme', section='appearance')
Expand Down Expand Up @@ -1591,33 +1591,38 @@ def load_python_path(self):

def save_python_path(self, new_path_dict):
"""
Save path in Spyder configuration folder.
Save Spyder PYTHONPATH to configuration folder and update attributes.
`new_path_dict` is an OrderedDict that has the new paths as keys and
the state as values. The state is `True` for active and `False` for
inactive.
"""
path = [p for p in new_path_dict]
not_active_path = [p for p in new_path_dict if not new_path_dict[p]]
try:
encoding.writelines(path, self.SPYDER_PATH)
encoding.writelines(not_active_path, self.SPYDER_NOT_ACTIVE_PATH)
except EnvironmentError as e:
logger.error(str(e))
self.set_conf('spyder_pythonpath', self.get_spyder_pythonpath())
path = tuple(p for p in new_path_dict)
not_active_path = tuple(p for p in new_path_dict
if not new_path_dict[p])

if path != self.path or not_active_path != self.not_active_path:
# Do not write unless necessary
try:
encoding.writelines(path, self.SPYDER_PATH)
encoding.writelines(not_active_path,
self.SPYDER_NOT_ACTIVE_PATH)
except EnvironmentError as e:
logger.error(str(e))

self.path = path
self.not_active_path = not_active_path

def get_spyder_pythonpath_dict(self):
"""
Return Spyder PYTHONPATH.
Return Spyder PYTHONPATH plus project path as dictionary of paths.
The returned ordered dictionary has the paths as keys and the state
as values. The state is `True` for active and `False` for inactive.
Example:
OrderedDict([('/some/path, True), ('/some/other/path, False)])
"""
self.load_python_path()

path_dict = OrderedDict()
for path in self.path:
path_dict[path] = path not in self.not_active_path
Expand All @@ -1629,28 +1634,40 @@ def get_spyder_pythonpath_dict(self):

def get_spyder_pythonpath(self):
"""
Return Spyder PYTHONPATH.
Return active Spyder PYTHONPATH plus project path as a list of paths.
"""
path_dict = self.get_spyder_pythonpath_dict()
path = [k for k, v in path_dict.items() if v]
return path

def update_python_path(self, new_path_dict):
"""Update python path on Spyder interpreter and kernels."""
# Load previous path
path_dict = self.get_spyder_pythonpath_dict()
def update_python_path(self, new_path_dict=None):
"""
Update python path on Spyder interpreter and kernels.
The new_path_dict should not include the project path.
"""
# Load existing path plus project path
old_path_dict_p = self.get_spyder_pythonpath_dict()

# Save path
if path_dict != new_path_dict:
# It doesn't include the project_path
# Save new path
if new_path_dict is not None:
self.save_python_path(new_path_dict)

# Load new path
new_path_dict_p = self.get_spyder_pythonpath_dict() # Includes project
# Update project path
projects = self.get_plugin(Plugins.Projects, error=False)
if projects:
self.project_path = tuple(projects.get_pythonpath())

# Any plugin that needs to do some work based on this signal should
# connect to it on plugin registration
self.sig_pythonpath_changed.emit(path_dict, new_path_dict_p)
# Load new path plus project path
new_path_dict_p = self.get_spyder_pythonpath_dict()

if new_path_dict_p != old_path_dict_p:
# Do not notify observers unless necessary
self.set_conf('spyder_pythonpath', self.get_spyder_pythonpath())

# Any plugin that needs to do some work based on this signal should
# connect to it on plugin registration
self.sig_pythonpath_changed.emit(old_path_dict_p, new_path_dict_p)

@Slot()
def show_path_manager(self):
Expand Down Expand Up @@ -1679,16 +1696,6 @@ def _dialog_finished(result_code):
self._path_manager.raise_()
self._path_manager.setFocus()

def pythonpath_changed(self):
"""Project's PYTHONPATH contribution has changed."""
projects = self.get_plugin(Plugins.Projects, error=False)

self.project_path = ()
if projects:
self.project_path = tuple(projects.get_pythonpath())
path_dict = self.get_spyder_pythonpath_dict()
self.update_python_path(path_dict)

# ---- Preferences
# -------------------------------------------------------------------------
def apply_settings(self):
Expand Down
43 changes: 43 additions & 0 deletions spyder/app/tests/test_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,49 @@ def test_run_cython_code(main_window, qtbot):
main_window.editor.close_file()


@pytest.mark.slow
def test_project_path(main_window, tmpdir, qtbot):
"""Test project path added to spyder_pythonpath and IPython Console."""
projects = main_window.projects

# Create a project path
path = str(tmpdir.mkdir('project_path'))
assert path not in projects.get_conf('spyder_pythonpath', section='main')

# Ensure project path is added to spyder_pythonpath
projects.open_project(path=path)
assert path in projects.get_conf('spyder_pythonpath', section='main')

# Ensure project path is added to IPython console
shell = main_window.ipyconsole.get_current_shellwidget()
qtbot.waitUntil(
lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

with qtbot.waitSignal(shell.executed):
shell.execute("import sys; import os; "
"sys_path = sys.path; "
"os_path = os.environ.get('PYTHONPATH', [])")
assert path in shell.get_value("sys_path")
assert path in shell.get_value("os_path")

projects.close_project()

# Ensure that project path is removed from spyder_pythonpath
assert path not in projects.get_conf('spyder_pythonpath', section='main')

# Ensure that project path is removed from IPython console
shell = main_window.ipyconsole.get_current_shellwidget()
qtbot.waitUntil(
lambda: shell._prompt_html is not None, timeout=SHELL_TIMEOUT)

with qtbot.waitSignal(shell.executed):
shell.execute("import sys; import os; "
"sys_path = sys.path; "
"os_path = os.environ.get('PYTHONPATH', [])")
assert path not in shell.get_value("sys_path")
assert path not in shell.get_value("os_path")


@pytest.mark.slow
@flaky(max_runs=3)
@pytest.mark.skipif(os.name == 'nt', reason="It fails on Windows.")
Expand Down
2 changes: 1 addition & 1 deletion spyder/plugins/projects/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def on_initialize(self):
lambda v: self.main.set_window_title())
self.main.restore_scrollbar_position.connect(
self.restore_scrollbar_position)
self.sig_pythonpath_changed.connect(self.main.pythonpath_changed)
self.sig_pythonpath_changed.connect(self.main.update_python_path)

self.register_project_type(self, EmptyProject)
self.setup()
Expand Down

0 comments on commit fce2b9c

Please sign in to comment.