Skip to content

Commit 56c4207

Browse files
authored
feat(BA-6061): Add root CLI --version option to display loaded package versions (#11641)
1 parent ddb766c commit 56c4207

3 files changed

Lines changed: 92 additions & 0 deletions

File tree

changes/11641.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `--version` option to the root `backend.ai` CLI to display the versions of loaded `backend.ai-*` packages in the current Python environment.

src/ai/backend/cli/main.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .completion import get_completion_command
66
from .extensions import ExtendedCommandGroup
77
from .types import CliContextInfo
8+
from .version import print_version
89

910

1011
@click.group(
@@ -13,6 +14,14 @@
1314
"help_option_names": ["-h", "--help"],
1415
},
1516
)
17+
@click.option(
18+
"--version",
19+
is_flag=True,
20+
is_eager=True,
21+
expose_value=False,
22+
callback=print_version,
23+
help="Show versions of backend.ai-* packages in the current environment and exit.",
24+
)
1625
@click.option(
1726
"--skip-sslcert-validation",
1827
help="(client option) Skip SSL certificate validation for all API requests.",

src/ai/backend/cli/version.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from __future__ import annotations
2+
3+
from importlib.metadata import distributions
4+
from pathlib import Path
5+
from typing import Any
6+
7+
import click
8+
9+
_BACKEND_DIST_PREFIX = "backend.ai-"
10+
11+
12+
def _collect_dist_versions() -> dict[str, str]:
13+
"""Collect versions of installed backend.ai-* distributions."""
14+
versions: dict[str, str] = {}
15+
for dist in distributions():
16+
name = dist.metadata["Name"]
17+
if name and name.lower().startswith(_BACKEND_DIST_PREFIX):
18+
versions[name] = dist.version
19+
return versions
20+
21+
22+
def _collect_namespace_versions() -> dict[str, str]:
23+
"""
24+
Collect versions from VERSION files under the `ai.backend` namespace package.
25+
26+
This handles dev-mode layouts where subpackages are loaded directly from
27+
the source tree and are not registered as separate distributions.
28+
Walks one level into nested namespace packages (e.g., `ai.backend.appproxy.*`)
29+
so multi-distribution namespaces are covered.
30+
"""
31+
versions: dict[str, str] = {}
32+
try:
33+
import ai.backend as ns
34+
except ImportError:
35+
return versions
36+
seen: set[Path] = set()
37+
for root_str in ns.__path__:
38+
root = Path(root_str)
39+
if not root.is_dir():
40+
continue
41+
for child in sorted(root.iterdir()):
42+
if not child.is_dir() or child in seen:
43+
continue
44+
seen.add(child)
45+
version_file = child / "VERSION"
46+
if version_file.is_file():
47+
key = f"{_BACKEND_DIST_PREFIX}{child.name.replace('_', '-')}"
48+
versions.setdefault(key, version_file.read_text().strip())
49+
continue
50+
# Recurse one level for namespace subpackages (e.g., appproxy/*).
51+
for grand in sorted(child.iterdir()):
52+
if not grand.is_dir():
53+
continue
54+
version_file = grand / "VERSION"
55+
if not version_file.is_file():
56+
continue
57+
parent = child.name.replace("_", "-")
58+
leaf = grand.name.replace("_", "-")
59+
key = f"{_BACKEND_DIST_PREFIX}{parent}-{leaf}"
60+
versions.setdefault(key, version_file.read_text().strip())
61+
return versions
62+
63+
64+
def collect_versions() -> list[tuple[str, str]]:
65+
"""Return sorted (name, version) pairs of backend.ai-* packages."""
66+
merged = _collect_namespace_versions()
67+
for name, version in _collect_dist_versions().items():
68+
merged[name] = version
69+
return sorted(merged.items())
70+
71+
72+
def print_version(ctx: click.Context, _param: click.Parameter, value: Any) -> None:
73+
if not value or ctx.resilient_parsing:
74+
return
75+
versions = collect_versions()
76+
if not versions:
77+
click.echo("No backend.ai-* packages found.")
78+
else:
79+
width = max(len(name) for name, _ in versions)
80+
for name, version in versions:
81+
click.echo(f"{name:<{width}} {version}")
82+
ctx.exit()

0 commit comments

Comments
 (0)