Skip to content

Commit 3f2d2fc

Browse files
fix: lfx env var resolution when value is empty (#12907)
* fix(lfx): allow env-var fallback when user_id is None get_api_key_for_provider short-circuited to None whenever user_id was None, making the os.getenv(variable_name) fallback at the bottom of the function unreachable. lfx run has no concept of user_id, so any flow exported with an empty credential field (the typical case after a re-export) failed with 'API key is required' even when the canonical env var was set in the shell. Restructured to: try the database-backed variable service only when a user_id is available, then always fall through to os.getenv. The shell-exported credential is now picked up regardless of whether a user is present. * ruff : g
1 parent 7d4d3e1 commit 3f2d2fc

2 files changed

Lines changed: 54 additions & 23 deletions

File tree

src/backend/tests/unit/test_credential_resolution.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,32 @@ def test_should_return_db_value_when_db_lookup_succeeds(self, mock_run, mock_map
100100
result = get_api_key_for_provider(user_id, "OpenAI", None)
101101

102102
assert result == "sk-from-database"
103+
104+
@patch("lfx.base.models.unified_models.credentials.get_model_provider_variable_mapping")
105+
def test_should_fallback_to_env_when_user_id_is_none(self, mock_mapping, monkeypatch):
106+
"""No user_id (lfx run) must still resolve credentials from os.environ.
107+
108+
Reproducer: a flow exported with empty api_key + load_from_db=False is executed
109+
via `lfx run`. user_id is None, api_key is empty/None — the function should still
110+
try the canonical env var (e.g. WATSONX_APIKEY) before giving up.
111+
"""
112+
from lfx.base.models.unified_models.credentials import get_api_key_for_provider
113+
114+
mock_mapping.return_value = {"IBM WatsonX": "WATSONX_APIKEY"}
115+
monkeypatch.setenv("WATSONX_APIKEY", "shell-exported-key")
116+
117+
result = get_api_key_for_provider(None, "IBM WatsonX", None)
118+
119+
assert result == "shell-exported-key"
120+
121+
@patch("lfx.base.models.unified_models.credentials.get_model_provider_variable_mapping")
122+
def test_should_return_none_when_user_id_none_and_env_unset(self, mock_mapping, monkeypatch):
123+
"""No user_id and no env var: nothing to return."""
124+
from lfx.base.models.unified_models.credentials import get_api_key_for_provider
125+
126+
mock_mapping.return_value = {"IBM WatsonX": "WATSONX_APIKEY"}
127+
monkeypatch.delenv("WATSONX_APIKEY", raising=False)
128+
129+
result = get_api_key_for_provider(None, "IBM WatsonX", None)
130+
131+
assert result is None

src/lfx/src/lfx/base/models/unified_models/credentials.py

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -67,36 +67,38 @@ async def _get_by_var_name():
6767
# Literal API key (e.g. sk-...)
6868
return var_name
6969

70-
# If no user_id or user_id is the string "None", we can't look up global variables
71-
if user_id is None or (isinstance(user_id, str) and user_id == "None"):
72-
return None
73-
7470
# Get primary variable (first required secret) from provider metadata
7571
provider_variable_map = get_model_provider_variable_mapping()
7672
variable_name = provider_variable_map.get(provider)
7773
if not variable_name:
7874
return None
7975

80-
# Try to get from global variables, fall back to environment
81-
async def _get_variable():
82-
async with session_scope() as session:
83-
variable_service = get_variable_service()
84-
if variable_service is None:
85-
return None
86-
try:
87-
return await variable_service.get_variable(
88-
user_id=UUID(user_id) if isinstance(user_id, str) else user_id,
89-
name=variable_name,
90-
field="",
91-
session=session,
92-
)
93-
except ValueError:
94-
return None
76+
# Try the database-backed variable service first when a user_id is available.
77+
# Fall through to os.environ regardless so lfx run (no user_id) can still pick
78+
# up canonical credentials from the shell.
79+
has_user = user_id is not None and not (isinstance(user_id, str) and user_id == "None")
80+
api_key = None
81+
if has_user:
82+
83+
async def _get_variable():
84+
async with session_scope() as session:
85+
variable_service = get_variable_service()
86+
if variable_service is None:
87+
return None
88+
try:
89+
return await variable_service.get_variable(
90+
user_id=UUID(user_id) if isinstance(user_id, str) else user_id,
91+
name=variable_name,
92+
field="",
93+
session=session,
94+
)
95+
except ValueError:
96+
return None
9597

96-
try:
97-
api_key = run_until_complete(_get_variable())
98-
except (ValueError, Exception): # noqa: BLE001
99-
api_key = None
98+
try:
99+
api_key = run_until_complete(_get_variable())
100+
except (ValueError, Exception): # noqa: BLE001
101+
api_key = None
100102

101103
if api_key:
102104
return api_key

0 commit comments

Comments
 (0)