Skip to content

Commit

Permalink
Merge pull request #21402 from ccordoba12/issue-21365
Browse files Browse the repository at this point in the history
PR: Improve mechanism to check for updates and message that informs about them
  • Loading branch information
mrclary authored Oct 11, 2023
2 parents 734d65a + 01ef029 commit 5013d4a
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 98 deletions.
82 changes: 56 additions & 26 deletions spyder/plugins/application/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from qtpy.QtWidgets import QAction, QMessageBox, QPushButton

# Local imports
from spyder import __docs_url__, __forum_url__, __trouble_url__, __version__
from spyder import __docs_url__, __forum_url__, __trouble_url__
from spyder import dependencies
from spyder.api.translations import _
from spyder.api.widgets.main_container import PluginMainContainer
Expand All @@ -33,7 +33,7 @@
running_in_mac_app)
from spyder.plugins.application.widgets.status import ApplicationUpdateStatus
from spyder.plugins.console.api import ConsoleActions
from spyder.utils.conda import is_anaconda_pkg
from spyder.utils.conda import is_anaconda_pkg, get_spyder_conda_channel
from spyder.utils.environ import UserEnvDialog
from spyder.utils.qthelpers import start_file, DialogManager
from spyder.widgets.about import AboutDialog
Expand Down Expand Up @@ -283,15 +283,13 @@ def _check_updates_ready(self):
box.set_checked(self.get_conf(option))

header = _(
"<b>Spyder {} is available!</b> "
"<i>(you&nbsp;have&nbsp;{})</i><br><br>"
).format(latest_release, __version__)
"<h3>Spyder {} is available!</h3><br>"
).format(latest_release)

if error_msg is not None:
box.setText(error_msg)
box.set_check_visible(False)
box.exec_()

box.show()
elif update_available:
# Update using our installers
if (
Expand Down Expand Up @@ -341,43 +339,75 @@ def _check_updates_ready(self):
box.setStandardButtons(QMessageBox.Ok)
box.setDefaultButton(QMessageBox.Ok)

msg = _("")
msg = ""
if not box.result():
msg += header

terminal = _("terminal")
if os.name == "nt":
if is_anaconda():
terminal = "Anaconda prompt"
msg += _("Run the following commands in the Anaconda "
"prompt to update manually:<br><br>")
else:
terminal = _("cmd prompt")
msg += _("Run the following commands in the {} to update "
"manually:<br><br>").format(terminal)
msg += _("Run the following commands in a cmd prompt "
"to update manually:<br><br>")
else:
msg += _("Run the following commands in a terminal to "
"update manually:<br><br>")

if is_anaconda():
if is_anaconda_pkg():
msg += _("<code>conda update anaconda</code><br>")
msg += _("<code>conda install spyder={}"
"</code><br><br>").format(latest_release)
msg += _("<b>Important note:</b> Since you installed "
"Spyder with Anaconda, please <b>don't</b> use "
"<code>pip</code> to update it as that will "
"break your installation.")
channel, __ = get_spyder_conda_channel()
is_pypi = channel == 'pypi'

if is_anaconda_pkg() and not is_pypi:
msg += "<code>conda update anaconda</code><br>"

if is_pypi:
dont_mix_pip_conda_video = (
"https://youtu.be/Ul79ihg41Rs"
)

msg += (
"<code>pip install --upgrade spyder</code>"
"<br><br><br>"
)

msg += _(
"<b>Important note:</b> You installed Spyder with "
"pip in a Conda environment, which is not a good "
"idea. See <a href=\"{}\">our video</a> for more "
"details about it."
).format(dont_mix_pip_conda_video)
else:
if channel == 'pkgs/main':
channel = ''
else:
channel = f'-c {channel}'

msg += (
f"<code>conda install {channel} "
f"spyder={latest_release}"
f"</code><br><br><br>"
)

msg += _(
"<b>Important note:</b> Since you installed "
"Spyder with Anaconda, please don't use pip "
"to update it as that will break your "
"installation."
)
else:
msg += _("<code>pip install --upgrade spyder"
"</code>")
msg += "<code>pip install --upgrade spyder</code><br>"

msg += _(
"<br><br>For more information, visit our "
"<a href=\"{}\">installation guide</a>."
).format(url_i)

box.setText(msg)
box.exec_()

box.show()
elif feedback:
box.setText(_("Spyder is up to date."))
box.exec_()
box.show()
if self.application_update_status:
self.application_update_status.set_no_status()
else:
Expand Down
30 changes: 29 additions & 1 deletion spyder/utils/conda.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,35 @@ def is_anaconda_pkg(prefix=sys.prefix):
"""Detect if the anaconda meta package is installed."""
if is_conda_env(prefix):
conda_meta = osp.join(prefix, "conda-meta")
if glob("anaconda-[0-9]*.json", root_dir=conda_meta):
if glob(f"{conda_meta}{os.sep}anaconda-[0-9]*.json"):
return True

return False


def get_spyder_conda_channel():
"""Get the conda channel from which Spyder was installed."""
conda = find_conda()

if conda is None:
return None

env = get_conda_env_path(sys.executable)
cmdstr = ' '.join([conda, 'list', 'spyder', '--json', '--prefix', env])

try:
out, __ = run_shell_command(cmdstr, env={}).communicate()
out = out.decode()
out = json.loads(out)
except Exception:
return None

for package_info in out:
if package_info["name"] == 'spyder':
channel = package_info["channel"]
channel_url = package_info["base_url"]

if "<develop>" in channel_url:
channel_url = None

return channel, channel_url
10 changes: 9 additions & 1 deletion spyder/utils/tests/test_conda.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from spyder.config.utils import is_anaconda
from spyder.utils.conda import (
add_quotes, find_conda, get_conda_activation_script, get_conda_env_path,
get_conda_root_prefix, get_list_conda_envs, get_list_conda_envs_cache)
get_conda_root_prefix, get_list_conda_envs, get_list_conda_envs_cache,
get_spyder_conda_channel)


if not is_anaconda():
Expand Down Expand Up @@ -90,5 +91,12 @@ def test_get_list_conda_envs_cache():
assert (time1 - time0) < 0.01


@pytest.mark.skipif(not running_in_ci(), reason="Only meant for CIs")
def test_get_spyder_conda_channel():
channel, channel_url = get_spyder_conda_channel()
assert channel == "pypi"
assert channel_url == "https://conda.anaconda.org/pypi"


if __name__ == "__main__":
pytest.main()
89 changes: 68 additions & 21 deletions spyder/workers/tests/test_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,103 @@

import pytest

from spyder.config.utils import is_anaconda
from spyder.workers.updates import WorkerUpdates


def test_update(qtbot):
"""Test we offer updates for lower versions."""
worker = WorkerUpdates(None, False, version="1.0.0")
@pytest.mark.parametrize("is_anaconda", [True, False])
@pytest.mark.parametrize("is_pypi", [True, False])
@pytest.mark.parametrize("version", ["1.0.0", "1000.0.0"])
@pytest.mark.parametrize(
"spyder_conda_channel", [
("pkgs/main", "https://repo.anaconda.com/pkgs/main"),
("conda-forge", "https://conda.anaconda.org/conda-forge")
]
)
def test_updates(qtbot, mocker, is_anaconda, is_pypi, version,
spyder_conda_channel):
"""
Test whether or not we offer updates for Anaconda and PyPI according to the
current Spyder version.
"""
mocker.patch(
"spyder.workers.updates.is_anaconda",
return_value=is_anaconda
)

if is_anaconda:
if is_pypi:
channel = ("pypi", "https://conda.anaconda.org/pypi")
else:
channel = spyder_conda_channel

mocker.patch(
"spyder.workers.updates.get_spyder_conda_channel",
return_value=channel
)

worker = WorkerUpdates(None, False, version=version)
worker.start()
assert worker.update_available

update = worker.update_available
assert update if version.split('.')[0] == '1' else not update
assert len(worker.releases) == 1


@pytest.mark.parametrize("version", ["1.0.0", "1000.0.0"])
def test_updates_for_installers(qtbot, mocker, version):
"""
Test whether or not we offer updates for our installers according to the
current Spyder version.
"""
mocker.patch("spyder.workers.updates.is_anaconda", return_value=False)
mocker.patch("spyder.workers.updates.is_pynsist", return_value=True)

def test_no_update(qtbot):
"""Test we don't offer updates for very high versions."""
worker = WorkerUpdates(None, False, version="1000.0.0")
worker = WorkerUpdates(None, False, version=version)
worker.start()
assert not worker.update_available

update = worker.update_available
assert update if version.split('.')[0] == '1' else not update
assert len(worker.releases) > 1


def test_no_update_development(qtbot):
def test_no_update_development(qtbot, mocker):
"""Test we don't offer updates for development versions."""
mocker.patch(
"spyder.workers.updates.get_spyder_conda_channel",
return_value=("pypi", "https://conda.anaconda.org/pypi")
)

worker = WorkerUpdates(None, False, version="3.3.2.dev0",
releases=['3.3.1'])
worker.start()
assert not worker.update_available


def test_update_pre_to_pre(qtbot):
def test_update_pre_to_pre(qtbot, mocker):
"""Test we offer updates between prereleases."""
mocker.patch(
"spyder.workers.updates.get_spyder_conda_channel",
return_value=("pypi", "https://conda.anaconda.org/pypi")
)

worker = WorkerUpdates(None, False, version="4.0.0a1",
releases=['4.0.0b5'])
worker.start()
assert worker.update_available


def test_update_pre_to_final(qtbot):
def test_update_pre_to_final(qtbot, mocker):
"""Test we offer updates from prereleases to the final versions."""
mocker.patch(
"spyder.workers.updates.get_spyder_conda_channel",
return_value=("pypi", "https://conda.anaconda.org/pypi")
)

worker = WorkerUpdates(None, False, version="4.0.0b3",
releases=['4.0.0'])
worker.start()
assert worker.update_available


@pytest.mark.skipif(not is_anaconda(),
reason='It only makes sense for Anaconda.')
def test_releases_anaconda(qtbot):
"""Test we don't include spyder-kernels releases in detected releases."""
worker = WorkerUpdates(None, False, version="3.3.1")
worker.start()
assert '0.2.4' not in worker.releases


if __name__ == "__main__":
pytest.main()
Loading

0 comments on commit 5013d4a

Please sign in to comment.