diff --git a/insto/cli.py b/insto/cli.py index 80d0542..8df3bc4 100644 --- a/insto/cli.py +++ b/insto/cli.py @@ -27,6 +27,7 @@ import asyncio import contextlib import getpass +import importlib.util import logging import os import shlex @@ -38,6 +39,7 @@ from insto import __version__ from insto._redact import redact_secrets +from insto.backends import AIOGRAPI_INSTALL_HINT from insto.commands import ( # noqa: F401 — importing registers all commands COMMANDS, CommandUsageError, @@ -77,6 +79,11 @@ SETUP_HINT = "no HIKERAPI_TOKEN configured. Run `insto setup` to create one." +def _is_aiograpi_installed() -> bool: + """Return whether the optional aiograpi backend dependency is importable.""" + return importlib.util.find_spec("aiograpi") is not None + + # --------------------------------------------------------------------------- # Logging # --------------------------------------------------------------------------- @@ -348,6 +355,8 @@ def _run_setup_non_interactive(*, out: IO[str] | None = None) -> int: if backend not in {BACKEND_HIKERAPI, BACKEND_AIOGRAPI}: print(f"--non-interactive: unknown backend {backend!r}", file=sys.stderr) return 2 + if backend == BACKEND_AIOGRAPI and not _is_aiograpi_installed(): + print(AIOGRAPI_INSTALL_HINT, file=stream) token = os.environ.get("HIKERAPI_TOKEN") or (existing.hiker_token if existing else None) proxy = os.environ.get("HIKERAPI_PROXY") or (existing.hiker_proxy if existing else None) @@ -451,6 +460,8 @@ def _run_setup( if backend not in {BACKEND_HIKERAPI, BACKEND_AIOGRAPI}: print(f"unknown backend {backend!r}; falling back to hikerapi", file=stream) backend = BACKEND_HIKERAPI + if backend == BACKEND_AIOGRAPI and not _is_aiograpi_installed(): + print(AIOGRAPI_INSTALL_HINT, file=stream) token_default = existing.hiker_token if existing else None if backend == BACKEND_HIKERAPI: diff --git a/tests/test_cli.py b/tests/test_cli.py index 8e9601c..e10f757 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -45,7 +45,16 @@ @pytest.fixture(autouse=True) def _isolated_home(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Iterator[Path]: monkeypatch.setenv(cfgmod.CONFIG_HOME_ENV, str(tmp_path / ".insto")) - for var in (cfgmod.ENV_TOKEN, cfgmod.ENV_PROXY, cfgmod.ENV_OUTPUT_DIR, cfgmod.ENV_DB_PATH): + for var in ( + "INSTO_BACKEND", + cfgmod.ENV_TOKEN, + cfgmod.ENV_PROXY, + cfgmod.ENV_OUTPUT_DIR, + cfgmod.ENV_DB_PATH, + cfgmod.ENV_AIOGRAPI_USERNAME, + cfgmod.ENV_AIOGRAPI_PASSWORD, + cfgmod.ENV_AIOGRAPI_TOTP, + ): monkeypatch.delenv(var, raising=False) yield tmp_path # Detach our log handlers to not bleed across tests. @@ -300,6 +309,34 @@ def prompt(text: str) -> str: assert "https://hikerapi.com/tokens" in token_prompt +def test_setup_aiograpi_warns_when_optional_dependency_missing( + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + monkeypatch.setattr(cli_mod, "_is_aiograpi_installed", lambda: False) + + rc = _run_setup( + prompt=_scripted_prompt( + [ + "aiograpi", + "instag", + "secret", + "", + "", + "", + "", + ] + ) + ) + + assert rc == 0 + out = capsys.readouterr().out + assert "aiograpi backend requested" in out + assert "pipx inject insto aiograpi" in out + assert "insto[aiograpi]" in out + assert load_config().backend == "aiograpi" + + def test_setup_writes_hikerapi_backend_and_section() -> None: rc = _run_setup(prompt=_scripted_prompt(["", "tok-1234567890", "", "", ""])) @@ -610,3 +647,20 @@ def test_setup_non_interactive_missing_token_fails(capsys: pytest.CaptureFixture rc = cli_mod.main(["setup", "--non-interactive"]) # no token in env or config assert rc == 2 assert "HIKERAPI_TOKEN" in capsys.readouterr().err + + +def test_setup_non_interactive_aiograpi_warns_when_optional_dependency_missing( + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + monkeypatch.setattr(cli_mod, "_is_aiograpi_installed", lambda: False) + monkeypatch.setenv("INSTO_BACKEND", "aiograpi") + monkeypatch.setenv("AIOGRAPI_USERNAME", "instag") + monkeypatch.setenv("AIOGRAPI_PASSWORD", "secret") + + rc = cli_mod.main(["setup", "--non-interactive"]) + + assert rc == 0 + out = capsys.readouterr().out + assert "pipx inject insto aiograpi" in out + assert load_config().backend == "aiograpi"