From b55b353f0f80d3a1411f0ddb95ceab4a43d5750c Mon Sep 17 00:00:00 2001 From: didericis Date: Tue, 21 Jul 2026 13:25:33 -0400 Subject: [PATCH] feat(cli): make bot_bottle.cli runnable with python -m MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `python -m bot_bottle.cli` failed with "is a package and cannot be directly executed" — the package had a `if __name__ == "__main__"` block in `__init__.py`, which never fires for a package and made the invocation look supported when it wasn't. Add a real `__main__.py` and drop the dead block. Matters for `bb login`, whose docstring documents a `bb` entry point that nothing installs; `-m` is the closest thing to it until there's a console-script. Co-Authored-By: Claude Opus 4.8 --- bot_bottle/cli/__init__.py | 4 --- bot_bottle/cli/__main__.py | 15 +++++++++++ tests/unit/test_cli_module_entry.py | 40 +++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 bot_bottle/cli/__main__.py create mode 100644 tests/unit/test_cli_module_entry.py diff --git a/bot_bottle/cli/__init__.py b/bot_bottle/cli/__init__.py index 0061724..cf62f68 100644 --- a/bot_bottle/cli/__init__.py +++ b/bot_bottle/cli/__init__.py @@ -114,7 +114,3 @@ def main(argv: list[str] | None = None) -> int: return e.code if isinstance(e.code, int) else 1 except KeyboardInterrupt: return 130 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/bot_bottle/cli/__main__.py b/bot_bottle/cli/__main__.py new file mode 100644 index 0000000..3cf5f19 --- /dev/null +++ b/bot_bottle/cli/__main__.py @@ -0,0 +1,15 @@ +"""Entry point for `python -m bot_bottle.cli`. + +`cli.py` at the repo root is the usual way in; this makes the package +runnable too, so the CLI works from an installed copy where there is no +`cli.py` on disk to point at. +""" + +from __future__ import annotations + +import sys + +from . import main + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/unit/test_cli_module_entry.py b/tests/unit/test_cli_module_entry.py new file mode 100644 index 0000000..2ff003b --- /dev/null +++ b/tests/unit/test_cli_module_entry.py @@ -0,0 +1,40 @@ +"""The CLI package is runnable as `python -m bot_bottle.cli`.""" + +from __future__ import annotations + +import subprocess +import sys +import unittest +from pathlib import Path + +_REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _run(*args: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [sys.executable, "-m", "bot_bottle.cli", *args], + cwd=_REPO_ROOT, + capture_output=True, + text=True, + check=False, + ) + + +class TestModuleEntry(unittest.TestCase): + def test_help_exits_zero(self) -> None: + result = _run("--help") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("login", result.stderr) + + def test_no_args_prints_usage(self) -> None: + # main() returns 2 with no command, matching the cli.py entry point. + self.assertEqual(_run().returncode, 2) + + def test_subcommand_help_reaches_handler(self) -> None: + result = _run("login", "--help") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("--console-url", result.stderr) + + +if __name__ == "__main__": + unittest.main()