Skip to content

Commit c9a152f

Browse files
committed
Restrict prerelease versions when updating by id.
Fixes #332
1 parent 156b952 commit c9a152f

2 files changed

Lines changed: 100 additions & 31 deletions

File tree

src/manage/install_command.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def _expand_versions_by_tag(versions):
4848
yield {**v, "tag": t}
4949

5050

51-
def select_package(index_downloader, tag, platform=None, *, urlopen=_urlopen, by_id=False):
51+
def select_package(index_downloader, tag, platform=None, *, urlopen=_urlopen, by_id=False, allow_pre=True):
5252
"""Finds suitable package from index.json that looks like:
5353
{"versions": [
5454
{"id": ..., "company": ..., "tag": ..., "url": ..., "hash": {"sha256": hexdigest}},
@@ -63,17 +63,24 @@ def select_package(index_downloader, tag, platform=None, *, urlopen=_urlopen, by
6363
try:
6464
if by_id:
6565
for v in index.versions:
66+
if not allow_pre and v["sort-version"].is_prerelease:
67+
continue
6668
if v["id"].casefold() == tag.casefold():
6769
return v
6870
raise LookupError("Could not find a runtime matching '{}' at '{}'".format(
6971
tag, sanitise_url(index.source_url)
7072
))
7173
if platform:
7274
try:
73-
return index.find_to_install(tag + platform)
75+
v = index.find_to_install(tag + platform)
7476
except LookupError:
7577
pass
76-
return index.find_to_install(tag)
78+
else:
79+
if allow_pre or not v["sort-version"].is_prerelease:
80+
return v
81+
v = index.find_to_install(tag)
82+
if allow_pre or not v["sort-version"].is_prerelease:
83+
return v
7784
except LookupError as ex:
7885
first_exc = ex
7986

@@ -367,7 +374,7 @@ def _same_install(i, j):
367374
return i["id"] == j["id"] and i["sort-version"] == j["sort-version"]
368375

369376

370-
def _find_one(cmd, source, tag, *, installed=None, by_id=False):
377+
def _find_one(cmd, source, tag, *, installed=None, by_id=False, allow_pre=True):
371378
if by_id:
372379
LOGGER.debug("Searching for Python with ID %s", tag)
373380
elif tag:
@@ -377,7 +384,7 @@ def _find_one(cmd, source, tag, *, installed=None, by_id=False):
377384

378385
download_cache = cmd.scratch.setdefault("install_command.download_cache", {})
379386
downloader = IndexDownloader(cmd, source, Index, {}, download_cache)
380-
install = select_package(downloader, tag, cmd.default_platform, by_id=by_id)
387+
install = select_package(downloader, tag, cmd.default_platform, by_id=by_id, allow_pre=allow_pre)
381388

382389
# Ensure the requested source URL is in the install
383390
if install and source:
@@ -829,14 +836,16 @@ def execute(cmd):
829836
# Fallthrough is safe - cmd.tags is empty
830837
elif cmd.update:
831838
LOGGER.verbose("No tags provided, updating all installs:")
839+
from .verutils import Version
832840
for install in installed:
833841
first_exc = None
834842
update = None
843+
allow_pre = Version(install['sort-version']).is_prerelease
835844
for source in [install.get('source'), cmd.source, cmd.fallback_source]:
836845
if not source:
837846
continue
838847
try:
839-
update = _find_one(cmd, source, install['id'], by_id=True)
848+
update = _find_one(cmd, source, install['id'], by_id=True, allow_pre=allow_pre)
840849
if update:
841850
break
842851
except LookupError:

tests/test_install_command.py

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from manage import install_command as IC
77
from manage import installs
88
from manage.exceptions import NoInstallFoundError
9+
from manage.logging import LOGGER
910

1011

1112
def test_print_cli_shortcuts(patched_installs, assert_log, monkeypatch, tmp_path):
@@ -225,50 +226,51 @@ def __init__(self, tmp_path, *args, **kwargs):
225226
self.scratch = {
226227
"install_command.download_cache": self.download_cache,
227228
}
228-
self.automatic = kwargs.get("automatic", False)
229-
self.by_id = kwargs.get("by_id", False)
230-
self.default_install_tag = kwargs.get("default_install_tag", "1")
231-
self.default_platform = kwargs.get("default_platform", "-32")
232-
self.default_tag = kwargs.get("default_tag", "1")
233-
self.download = kwargs.get("download")
229+
self.automatic = kwargs.pop("automatic", False)
230+
self.by_id = kwargs.pop("by_id", False)
231+
self.default_install_tag = kwargs.pop("default_install_tag", "1")
232+
self.default_platform = kwargs.pop("default_platform", "-32")
233+
self.default_tag = kwargs.pop("default_tag", "1")
234+
self.download = kwargs.pop("download", None)
234235
if self.download:
235236
self.download = tmp_path / self.download
236-
self.download_dir = tmp_path / kwargs.get("download_dir", "_cache")
237-
self.dry_run = kwargs.get("dry_run", True)
238-
self.fallback_source = kwargs.get("fallback_source")
239-
self.force = kwargs.get("force", True)
240-
self.from_script = kwargs.get("from_script")
241-
self.log_file = kwargs.get("log_file")
242-
self.refresh = kwargs.get("refresh", False)
243-
self.repair = kwargs.get("repair", False)
244-
self.shebang_can_run_anything = kwargs.get("shebang_can_run_anything", False)
245-
self.shebang_can_run_anything_silently = kwargs.get("shebang_can_run_anything_silently", False)
246-
self.source = kwargs.get("source", "http://example.com/index.json")
247-
self.target = kwargs.get("target")
237+
self.download_dir = tmp_path / kwargs.pop("download_dir", "_cache")
238+
self.dry_run = kwargs.pop("dry_run", True)
239+
self.fallback_source = kwargs.pop("fallback_source", None)
240+
self.force = kwargs.pop("force", True)
241+
self.from_script = kwargs.pop("from_script", None)
242+
self.log_file = kwargs.pop("log_file", None)
243+
self.refresh = kwargs.pop("refresh", False)
244+
self.repair = kwargs.pop("repair", False)
245+
self.shebang_can_run_anything = kwargs.pop("shebang_can_run_anything", False)
246+
self.shebang_can_run_anything_silently = kwargs.pop("shebang_can_run_anything_silently", False)
247+
self.source = kwargs.pop("source", "http://example.com/index.json")
248+
self.target = kwargs.pop("target", None)
248249
if self.target:
249250
self.target = tmp_path / self.target
250-
self.update = kwargs.get("update", False)
251-
self.virtual_env = kwargs.get("virtual_env")
251+
self.update = kwargs.pop("update", False)
252+
self.virtual_env = kwargs.pop("virtual_env", None)
252253

253254
self.index_installs = [
255+
*kwargs.pop("index_installs", ()),
254256
{
255257
"schema": 1,
256-
"id": "test-1.1-32",
258+
"id": "test-32",
257259
"sort-version": "1.1",
258260
"company": "Test",
259261
"tag": "1.1-32",
260-
"install-for": ["1", "1.1", "1.1-32"],
262+
"install-for": ["1-32", "1.1-32", "1.1-32"],
261263
"display-name": "Test 1.1 (32)",
262264
"executable": "test.exe",
263265
"url": "about:blank",
264266
},
265267
{
266268
"schema": 1,
267-
"id": "test-1.0-32",
269+
"id": "test-32",
268270
"sort-version": "1.0",
269271
"company": "Test",
270272
"tag": "1.0-32",
271-
"install-for": ["1", "1.0", "1.0-32"],
273+
"install-for": ["1-32", "1.0-32", "1.0-32"],
272274
"display-name": "Test 1.0 (32)",
273275
"executable": "test.exe",
274276
"url": "about:blank",
@@ -280,8 +282,13 @@ def __init__(self, tmp_path, *args, **kwargs):
280282
self.installs = [{
281283
**self.index_installs[-1],
282284
"source": self.source,
283-
"prefix": tmp_path / "test-1.0-32",
285+
"prefix": tmp_path / "test-32",
284286
}]
287+
assert not kwargs
288+
289+
def ask_yn(self, prompt, *args):
290+
LOGGER.info(prompt, *args)
291+
return True
285292

286293
def get_log_file(self):
287294
return self.log_file
@@ -302,6 +309,7 @@ def test_install_simple(tmp_path, assert_log):
302309
IC.execute(cmd)
303310
assert_log(
304311
assert_log.skip_until("Searching for Python matching %s", ["1.1"]),
312+
assert_log.skip_until(".*Your existing %s install.*", ["Test 1.0 (32)", "Test 1.1 (32)"]),
305313
assert_log.skip_until("Installing %s", ["Test 1.1 (32)"]),
306314
("Tag: %s\\\\%s", ["Test", "1.1-32"]),
307315
)
@@ -317,6 +325,58 @@ def test_install_already_installed(tmp_path, assert_log):
317325
)
318326

319327

328+
329+
def test_install_update(tmp_path, assert_log):
330+
cmd = InstallCommandTestCmd(tmp_path, update=True, force=False,
331+
index_installs=[
332+
{"schema": 1, "id": "test-32", "sort-version": "1.2rc1",
333+
"display-name": "Test 1.2rc1 (32)",
334+
"company": "Test", "tag": "1.2rc-32",
335+
"install-for": ["1-32", "1.2-32", "1.2rc1-32"]}
336+
],
337+
)
338+
339+
IC.execute(cmd)
340+
assert_log(
341+
assert_log.skip_until("Searching for Python with ID %s", ["test-32"]),
342+
assert_log.skip_until("Updating to %s", ["Test 1.1 (32)"]),
343+
)
344+
345+
346+
def test_install_update_explicit(tmp_path, assert_log):
347+
cmd = InstallCommandTestCmd(tmp_path, "1", update=True, force=False,
348+
index_installs=[
349+
{"schema": 1, "id": "test-32", "sort-version": "1.2rc1",
350+
"display-name": "Test 1.2rc1 (32)",
351+
"company": "Test", "tag": "1.2rc-32",
352+
"install-for": ["1-32", "1.2-32", "1.2rc1-32"]}
353+
],
354+
)
355+
356+
IC.execute(cmd)
357+
assert_log(
358+
assert_log.skip_until("Searching for Python matching %s", ["1"]),
359+
assert_log.skip_until("Updating to %s", ["Test 1.1 (32)"]),
360+
)
361+
362+
363+
def test_install_update_explicit_pre(tmp_path, assert_log):
364+
cmd = InstallCommandTestCmd(tmp_path, "1rc", update=True, force=False,
365+
index_installs=[
366+
{"schema": 1, "id": "test-32", "sort-version": "1.2rc1",
367+
"display-name": "Test 1.2rc1 (32)",
368+
"company": "Test", "tag": "1.2rc-32",
369+
"install-for": ["1-32", "1.2-32", "1.2rc1-32"]}
370+
],
371+
)
372+
373+
IC.execute(cmd)
374+
assert_log(
375+
assert_log.skip_until("Searching for Python matching %s", ["1rc"]),
376+
assert_log.skip_until("Updating to %s", ["Test 1.2rc1 (32)"]),
377+
)
378+
379+
320380
def test_install_from_script(tmp_path, assert_log):
321381
cmd = InstallCommandTestCmd(tmp_path, from_script=tmp_path / "t.py")
322382

0 commit comments

Comments
 (0)