From 9fc117b105c67aae4c580dcc5d5c622ce878fef1 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Sat, 19 Jul 2025 14:46:16 -0400 Subject: [PATCH] feat(cli): auto install GUI backend --- src/seedpass/cli.py | 32 +++++++++++++++++++++++++++++--- src/tests/test_typer_cli.py | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/seedpass/cli.py b/src/seedpass/cli.py index 3fceb5a..7cc3b20 100644 --- a/src/seedpass/cli.py +++ b/src/seedpass/cli.py @@ -26,6 +26,7 @@ from . import api as api_module import importlib import importlib.util +import subprocess app = typer.Typer( help="SeedPass command line interface", @@ -750,11 +751,36 @@ def api_stop(ctx: typer.Context, host: str = "127.0.0.1", port: int = 8000) -> N @app.command() def gui() -> None: - """Launch the BeeWare GUI.""" + """Launch the BeeWare GUI. + + If the platform specific backend is missing, attempt to install it and + retry launching the GUI. + """ + if not _gui_backend_available(): + if sys.platform.startswith("linux"): + pkg = "toga-gtk" + elif sys.platform == "win32": + pkg = "toga-winforms" + elif sys.platform == "darwin": + pkg = "toga-cocoa" + else: + typer.echo( + f"Unsupported platform '{sys.platform}' for BeeWare GUI.", + err=True, + ) + raise typer.Exit(1) + + typer.echo(f"Attempting to install {pkg} for GUI support...") + try: + subprocess.check_call([sys.executable, "-m", "pip", "install", pkg]) + typer.echo(f"Successfully installed {pkg}.") + except subprocess.CalledProcessError as exc: + typer.echo(f"Failed to install {pkg}: {exc}", err=True) + raise typer.Exit(1) + if not _gui_backend_available(): typer.echo( - "No BeeWare GUI backend found. Install 'toga-gtk' (Linux), " - "'toga-winforms' (Windows) or 'toga-cocoa' (macOS) to run the GUI.", + "BeeWare GUI backend still unavailable after installation attempt.", err=True, ) raise typer.Exit(1) diff --git a/src/tests/test_typer_cli.py b/src/tests/test_typer_cli.py index fc48c82..35b3d0d 100644 --- a/src/tests/test_typer_cli.py +++ b/src/tests/test_typer_cli.py @@ -564,7 +564,35 @@ def test_gui_command(monkeypatch): def test_gui_command_no_backend(monkeypatch): - monkeypatch.setattr(cli.importlib.util, "find_spec", lambda n: None) + """Install backend if missing and launch GUI.""" + + call_count = {"n": 0} + + def backend_available() -> bool: + call_count["n"] += 1 + return call_count["n"] > 1 + + monkeypatch.setattr(cli, "_gui_backend_available", backend_available) + + installed = {} + + def fake_check_call(cmd): + installed["cmd"] = cmd + + monkeypatch.setattr(cli.subprocess, "check_call", fake_check_call) + + called = {} + + def fake_main(): + called["gui"] = True + + monkeypatch.setitem( + sys.modules, + "seedpass_gui.app", + SimpleNamespace(main=fake_main), + ) + result = runner.invoke(app, ["gui"]) - assert result.exit_code == 1 - assert "BeeWare GUI backend" in result.stderr + assert result.exit_code == 0 + assert installed.get("cmd") is not None + assert called.get("gui") is True