From 815124bcbab8ea084fcf4aa3c98f6ab44dc42260 Mon Sep 17 00:00:00 2001 From: Punisheroot <44579963+Punisheroot@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:37:52 +0200 Subject: [PATCH] gh-407: Mark optional platform tags in online aliases Each online feed entry lists both the bare alias and its platform variant, but format_table only shows the first occurrence of each name. The -32 install claims python3.exe first, leaving the default -64 install with lone suffixed names shown as plain python3-64.exe while the Tag column correctly shows 3.15-dev[-64]. Forward the default platform to get_install_alias_names so a lone default-platform suffix still renders as optional ([-64]), mirroring the Tag column. --- src/manage/installs.py | 10 +++++++-- src/manage/list_command.py | 9 +++++--- tests/test_list.py | 44 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/manage/installs.py b/src/manage/installs.py index f8150cd..1673c89 100644 --- a/src/manage/installs.py +++ b/src/manage/installs.py @@ -174,7 +174,7 @@ def _make_alias_name_sortkey(n): return re.sub(r"(\d+|\[|\])", _sk_sub, n) -def get_install_alias_names(aliases, friendly=True, windowed=True): +def get_install_alias_names(aliases, friendly=True, windowed=True, default_platform=None): if not windowed: aliases = [a for a in aliases if not a.get("windowed")] if not friendly: @@ -191,11 +191,17 @@ def get_install_alias_names(aliases, friendly=True, windowed=True): result = [] for k, (n1, n2, n3) in seen.items(): + plat_parts = plats.get(k) + plat = _make_opt_part(plat_parts) + if default_platform and plat_parts == {default_platform}: + # The bare alias was already shown for another install, but the + # suffix is still optional when it matches the default platform. + plat = f"[{default_platform}]" result.append("".join([ n1, _make_opt_part(has_w.get(k)), n2, - _make_opt_part(plats.get(k)), + plat, n3, ])) return sorted(result, key=_make_alias_name_sortkey) diff --git a/src/manage/list_command.py b/src/manage/list_command.py index 879acfc..2db9e34 100644 --- a/src/manage/list_command.py +++ b/src/manage/list_command.py @@ -6,13 +6,16 @@ LOGGER = logging.LOGGER -def _format_alias(i, seen): +def _format_alias(cmd, i, seen): from manage.installs import get_install_alias_names aliases = [a for a in i.get("alias", ()) if a["name"].casefold() not in seen] seen.update(a["name"].casefold() for a in aliases) include_w = LOGGER.would_log_to_console(logging.VERBOSE) - names = get_install_alias_names(aliases, windowed=include_w) + default_platform = cmd.default_platform if cmd else None + names = get_install_alias_names( + aliases, windowed=include_w, default_platform=default_platform + ) return ", ".join(names) @@ -46,7 +49,7 @@ def format_table(cmd, installs): seen_alias = set() installs = [{ **i, - "alias": _format_alias(i, seen_alias), + "alias": _format_alias(cmd, i, seen_alias), "sort-version": str(i['sort-version']), "default-star": "", "tag-with-co": _format_tag_with_co(cmd, i), diff --git a/tests/test_list.py b/tests/test_list.py index cddbe79..9cea254 100644 --- a/tests/test_list.py +++ b/tests/test_list.py @@ -139,6 +139,50 @@ def test_format_table_aliases(assert_log): ) +def test_format_table_aliases_default_platform(assert_log): + # https://github.com/python/pymanager/issues/407 + # Aliases from the online index, in feed order. Each install lists both + # the bare alias and its platform-specific variant; format_table shows + # only the first occurrence of each name, so the -64 install is left + # with lone "-64" names. Those must still render as optional ("[-64]"), + # mirroring how the Tag column marks the default platform. + import types + + def online_aliases(plat): + return [ + {"name": "python3.15.exe", "target": "python.exe"}, + {"name": f"python3.15{plat}.exe", "target": "python.exe"}, + {"name": "python3.exe", "target": "python.exe"}, + {"name": f"python3{plat}.exe", "target": "python.exe"}, + {"name": "pythonw3.15.exe", "target": "pythonw.exe", "windowed": 1}, + {"name": f"pythonw3.15{plat}.exe", "target": "pythonw.exe", "windowed": 1}, + {"name": "pythonw3.exe", "target": "pythonw.exe", "windowed": 1}, + {"name": f"pythonw3{plat}.exe", "target": "pythonw.exe", "windowed": 1}, + ] + + def online_install(tag, plat): + return { + "company": "PythonCore", + "tag": tag, + "display-name": "Python 3.15.0rc2", + "sort-version": "3.15.0rc2", + "alias": online_aliases(plat), + } + + cmd = types.SimpleNamespace(default_platform="-64") + list_command.format_table(cmd, [ + online_install("3.15-dev-32", "-32"), + online_install("3.15-dev-64", "-64"), + online_install("3.15-dev-arm64", "-arm64"), + ]) + assert_log( + (r"!B!Tag\s+Name\s+Managed By\s+Version\s+Alias\s*!W!", ()), + (r"3\.15-dev-32.*" + re.escape("python[w]3[-32].exe, python[w]3.15[-32].exe"), ()), + (r"3\.15-dev\[-64\].*" + re.escape("python[w]3[-64].exe, python[w]3.15[-64].exe"), ()), + (r"3\.15-dev-arm64.*" + re.escape("python[w]3-arm64.exe, python[w]3.15-arm64.exe"), ()), + ) + + def test_format_table_truncated(assert_log): list_command.format_table(None, [ {