Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions osipy/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,20 @@


def _get_version() -> str:
"""Read version without importing the full osipy package."""
from importlib.metadata import version
"""Read version without importing the full osipy package.

return version("osipy")
Falls back to a placeholder instead of raising when osipy isn't
installed with discoverable package metadata (e.g. a source checkout
run without ``pip install -e .``). This is called eagerly while
building the parser, so a raise here would take down the entire CLI,
not just ``--version``.
"""
from importlib.metadata import PackageNotFoundError, version

try:
return version("osipy")
except PackageNotFoundError:
return "(version unknown)"


def create_parser() -> argparse.ArgumentParser:
Expand Down
52 changes: 51 additions & 1 deletion tests/unit/cli/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
dump_defaults,
load_config,
)
from osipy.cli.main import create_parser
from osipy.cli.main import _get_version, create_parser

# ---------------------------------------------------------------------------
# Fixtures
Expand Down Expand Up @@ -1114,3 +1114,53 @@ def test_output_override(self) -> None:

args_short = parser.parse_args(["-o", "/out", "config.yaml", "/data"])
assert args_short.output == "/out"


# ---------------------------------------------------------------------------
# TestVersionLookupRegression — GH-173
# ---------------------------------------------------------------------------


class TestVersionLookupRegression:
"""create_parser() must never crash when osipy's package metadata isn't
discoverable (e.g. a source checkout without `pip install -e .`), since
it runs on every CLI invocation, not just `--version`."""

def test_get_version_falls_back_when_metadata_missing(self, monkeypatch) -> None:
"""_get_version() returns a placeholder instead of raising."""
import importlib.metadata

def _raise(_name: str) -> str:
raise importlib.metadata.PackageNotFoundError("osipy")

monkeypatch.setattr(importlib.metadata, "version", _raise)

assert _get_version() == "(version unknown)"

def test_create_parser_succeeds_when_metadata_missing(self, monkeypatch) -> None:
"""create_parser() doesn't raise even if package metadata lookup fails."""
import importlib.metadata

def _raise(_name: str) -> str:
raise importlib.metadata.PackageNotFoundError("osipy")

monkeypatch.setattr(importlib.metadata, "version", _raise)

parser = create_parser()
assert parser is not None

def test_version_flag_exits_cleanly_when_metadata_missing(
self, monkeypatch
) -> None:
"""--version still exits 0 with a fallback string, not a traceback."""
import importlib.metadata

def _raise(_name: str) -> str:
raise importlib.metadata.PackageNotFoundError("osipy")

monkeypatch.setattr(importlib.metadata, "version", _raise)

parser = create_parser()
with pytest.raises(SystemExit) as exc_info:
parser.parse_args(["--version"])
assert exc_info.value.code == 0
Loading