Skip to content

Commit fb76ccd

Browse files
itomekTomasz Iniewicz
andauthored
fix(packaging): add keyring to base install_requires so gaia connectors works on bare install (amd#1621) (amd#1624)
Closes amd#1621 ## Why this matters `gaia connectors` is a **base** CLI command, but `keyring` — its OS credential-store backend for OAuth tokens (amd#915) — shipped only in the `[ui]`/`[api]`/`[dev]` extras. So a bare `pip install amd-gaia` crashed `gaia connectors list`/`status` with a raw `ModuleNotFoundError`. This promotes `keyring` to base `install_requires`, matching the command's tier, so connectors works out of the box on every platform. **Decision record (amd#1621, Option 1).** keyring is featherweight (pure-Python + `jaraco.*`; the cryptography/SecretStorage chain is Linux-only and ships as a prebuilt wheel) and base already pulls transformers/accelerate, so the added footprint is negligible — whereas a `[connectors]` extra (Option 2) would leave a base command needing an extra to avoid crashing. The guarded import in amd#1622 ships as defense-in-depth for stripped/minimal installs; the extras keep their own keyring declaration (so amd#1617's `[api]` guard stays green). ## Test plan - [ ] `pytest tests/unit/test_base_keyring_dep.py` — new regression guard: base `install_requires` declares keyring - [ ] `pytest tests/unit/test_api_extras.py` — no regression; extras keep keyring - [ ] `util/lint.py --all` - [ ] Real-world: bare `pip install` pulls keyring + `gaia connectors list`/`status` works on macOS / Windows / Linux; guarded actionable error when keyring absent Co-authored-by: Tomasz Iniewicz <heaters-nays0p@icloud.com>
1 parent 08ca5aa commit fb76ccd

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@
111111
# multipart uploads via python_multipart at import time. Base — not an
112112
# extra — so a plain `pip install amd-gaia` ships a working gaia-mcp.
113113
"python-multipart>=0.0.9",
114+
# gaia connectors is a base CLI command; keyring is its OS credential store (OAuth tokens #915). #1621
115+
"keyring>=24.0.0,<26.0.0",
114116
],
115117
extras_require={
116118
"image": [
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2+
# SPDX-License-Identifier: MIT
3+
4+
"""Regression test for issue #1621.
5+
6+
``gaia connectors`` (list, status, …) is a BASE CLI command — registered in
7+
``gaia.cli:main`` unconditionally, without any extras gate. Its import chain
8+
reaches ``gaia.connectors.store`` which does ``import keyring`` at module load
9+
time and at request time (OAuth token storage via ``gaia.connectors.api``
10+
introduced in #915).
11+
12+
Before this fix, ``keyring`` was only in the ``[ui]``, ``[api]``, and ``[dev]``
13+
extras. A bare ``pip install amd-gaia`` therefore caused ``gaia connectors
14+
list`` to crash with ``ModuleNotFoundError: No module named 'keyring'``.
15+
16+
Fix: promote ``keyring`` to ``install_requires`` so the base wheel ships a
17+
working ``gaia connectors`` out of the box. The extras keep their own
18+
declarations independently (``test_api_extras.py`` guards the [api] entry).
19+
20+
This is a static packaging assertion — it works in the CI unit-test venv that
21+
does not install the package at all. Modelled on ``test_api_extras.py`` (#1617).
22+
"""
23+
24+
from __future__ import annotations
25+
26+
import re
27+
from pathlib import Path
28+
29+
SETUP_PY = Path(__file__).resolve().parents[2] / "setup.py"
30+
31+
32+
def _parse_install_requires() -> list[str]:
33+
"""Extract requirement strings from setup.py install_requires=[...].
34+
35+
Walks the file line by line so brackets inside ``# comments`` don't
36+
confuse a naive non-greedy regex. Skips comment-only lines. Stops
37+
at the first ``]`` that closes the block.
38+
"""
39+
lines = SETUP_PY.read_text().splitlines()
40+
in_block = False
41+
body: list[str] = []
42+
for raw in lines:
43+
stripped = raw.strip()
44+
if not in_block:
45+
if re.match(r"install_requires\s*=\s*\[", stripped):
46+
in_block = True
47+
continue
48+
if stripped.startswith("]"):
49+
break
50+
# Skip comment-only lines so brackets in comments don't matter.
51+
if stripped.startswith("#"):
52+
continue
53+
body.append(raw)
54+
assert in_block, "Could not find install_requires=[ in setup.py"
55+
return re.findall(r'"([^"]+)"', "\n".join(body))
56+
57+
58+
def test_base_install_requires_declares_keyring() -> None:
59+
"""setup.py install_requires must include keyring — see #1621.
60+
61+
``gaia connectors`` is a base CLI command (no extras gate). Its import
62+
chain reaches ``gaia.connectors.store -> import keyring`` (OAuth token
63+
storage introduced in #915). Without keyring in base, a plain
64+
``pip install amd-gaia`` ships a broken ``gaia connectors`` command.
65+
"""
66+
base_reqs = _parse_install_requires()
67+
matches = [r for r in base_reqs if r.lower().startswith("keyring")]
68+
assert matches, (
69+
"setup.py install_requires is missing 'keyring' (needed by "
70+
"gaia.connectors.store -> `import keyring` for OAuth token storage, #915).\n"
71+
"gaia connectors is a base CLI command — keyring must ship with the base wheel.\n"
72+
f"Current install_requires: {base_reqs}"
73+
)

0 commit comments

Comments
 (0)