diff --git a/src/manage/indexutils.py b/src/manage/indexutils.py index 5169373..6529a4b 100644 --- a/src/manage/indexutils.py +++ b/src/manage/indexutils.py @@ -231,9 +231,11 @@ def find_all(self, tags, *, seen_ids=None, loose_company=False, with_prerelease= seen_ids.add(i["id"].casefold()) yield i - def find_to_install(self, tag, *, loose_company=False, prefer_prerelease=False): + def find_to_install(self, tag, *, loose_company=False, prefer_prerelease=None): tag_list = [tag] if tag else [] LOGGER.debug("Finding %s to install", tag_list) + if prefer_prerelease is None: + prefer_prerelease = any(t.prefers_prerelease for t in tag_list) for i in self.find_all(tag_list, loose_company=loose_company, with_prerelease=prefer_prerelease): return {**i, "source": self.source_url} if not loose_company: diff --git a/src/manage/installs.py b/src/manage/installs.py index 225b41e..862bc3b 100644 --- a/src/manage/installs.py +++ b/src/manage/installs.py @@ -357,6 +357,15 @@ def get_install_to_run( default_platform=default_platform, ) + if tag.prefers_prerelease: + for i, t in best: + try: + if Version(i["sort-version"]).is_prerelease: + return _patch_install_to_run(i, t) + except ValueError as ex: + LOGGER.debug("Failed to parse %s to check for prerelease", + i["sort-version"]) + if best: return _patch_install_to_run(*best[0]) diff --git a/src/manage/list_command.py b/src/manage/list_command.py index 4b27736..c01e806 100644 --- a/src/manage/list_command.py +++ b/src/manage/list_command.py @@ -354,15 +354,19 @@ def execute(cmd): if first_exc: raise SystemExit(1) from first_exc if cmd.one: - # Pick the first non-prerelease that'll install for our platform best = [i for i in installs if any(t.endswith(plat) for t in i.get("install-for", []))] - for i in best: - if not i["sort-version"].is_prerelease: - installs = [i] - break - else: + if any(t.prefers_prerelease for t in tags): + # First result will be the latest prerelease (or release) installs = best[:1] or installs + else: + # Pick the first non-prerelease that'll install for our platform + for i in best: + if not i["sort-version"].is_prerelease: + installs = [i] + break + else: + installs = best[:1] or installs elif cmd.install_dir: try: installs = cmd.get_installs(include_unmanaged=cmd.unmanaged) diff --git a/src/manage/tagutils.py b/src/manage/tagutils.py index e2a22cd..63d289d 100644 --- a/src/manage/tagutils.py +++ b/src/manage/tagutils.py @@ -67,6 +67,13 @@ def startswith(self, other): return not self.s return self.s.startswith(other.s) + def endswith(self, other): + if not isinstance(other, type(self)): + return False + if not other.s: + return True + return self.s.endswith(other.s) + def __hash__(self): return hash(self.s) @@ -213,6 +220,12 @@ def match(self, pattern): def satisfied_by(self, tag): return tag.match(self) + @property + def prefers_prerelease(self, dev_text=_AscendingText("dev")): + if self._sortkey and self._sortkey[-1] == dev_text: + return True + return False + def __str__(self): if self.is_core: return f"{self.tag}{self.platform}" @@ -340,6 +353,10 @@ def __init__(self, spec): def __repr__(self): return "".format(", ".join(repr(r) for r in self.ranges)) + @property + def prefers_prerelease(self): + return any(r.prefers_prerelease for r in self.ranges) + def satisfied_by(self, tag): return all(r(tag) for r in self.ranges) @@ -359,6 +376,10 @@ def __init__(self, tag): assert isinstance(tag, CompanyTag) self.tag = tag + @property + def prefers_prerelease(self): + return self.tag.prefers_prerelease + def __repr__(self): return f"{self.OP}{self.tag}" @@ -404,6 +425,9 @@ class RangeExclude(Range): def __call__(self, other): return not other.matches_bound(self.tag) + @property + def prefers_prerelease(self): + return False def tag_or_range(tag): if not isinstance(tag, str): diff --git a/tests/test_tagutils.py b/tests/test_tagutils.py index 9f5a5f8..ead3963 100644 --- a/tests/test_tagutils.py +++ b/tests/test_tagutils.py @@ -1,6 +1,6 @@ import pytest -from manage.tagutils import CompanyTag, TagRange +from manage.tagutils import CompanyTag, TagRange, tag_or_range @pytest.mark.parametrize("tag_str", [ @@ -42,6 +42,24 @@ def test_tag_match(): assert not CompanyTag("PythonCore\\4.56").match(CompanyTag("", "3")) +def test_prefer_prerelease(): + assert not tag_or_range("3").prefers_prerelease + assert not tag_or_range("3-64").prefers_prerelease + assert not tag_or_range("3.13").prefers_prerelease + assert tag_or_range("3-dev").prefers_prerelease + assert tag_or_range("3.13-DEV").prefers_prerelease + assert tag_or_range("3.13-dev-64").prefers_prerelease + + assert not tag_or_range(">3").prefers_prerelease + assert not tag_or_range("<3-64").prefers_prerelease + assert not tag_or_range("==3.13").prefers_prerelease + assert not tag_or_range("!=3.13").prefers_prerelease + assert not tag_or_range("!=3.13-dev").prefers_prerelease + assert tag_or_range(">3-dev").prefers_prerelease + assert tag_or_range("<3.13-DEV").prefers_prerelease + assert tag_or_range("==3.13-dev-64").prefers_prerelease + + def test_tag_platform_match(): assert CompanyTag("3.10-64").match(CompanyTag("3.10")) assert CompanyTag("3.10-64").match(CompanyTag("3.10-64"))