fix(vertex): distinguish missing/unreadable credential file from invalid JSON

This commit is contained in:
LyraCS 2026-09-08 15:05:24 +07:00
parent 1af7a403c6
commit 713ab8fd29
2 changed files with 70 additions and 15 deletions

View file

@ -127,27 +127,49 @@ class VertexBase:
) -> tuple[_VertexCredentialsObject | None, str]:
if credentials is not None:
if isinstance(credentials, str):
_is_path: Final = os.path.exists(
credentials
) # credentials is from server config (litellm_params), not user input
verbose_logger.debug(
"Vertex: Loading vertex credentials, is_file_path=%s, current dir %s",
_is_path,
"Vertex: Loading vertex credentials, current dir %s",
os.getcwd(),
)
try:
if _is_path:
# Decide by value shape, not os.path.exists(): exists()
# swallows every OSError (missing, unreadable, transient),
# which previously misreported all of them as invalid JSON.
if credentials.lstrip().startswith("{"):
try:
json_obj = json.loads(credentials)
except json.JSONDecodeError as e:
raise Exception(
"Unable to load vertex credentials from environment. "
"Ensure the JSON is valid (check for unescaped newlines in private_key). "
f"Parse error: {type(e).__name__}: {e}"
) from e
else:
try:
with open(credentials) as f:
json_obj = json.load(f)
else:
json_obj = json.loads(credentials)
except Exception as e:
raise Exception(
"Unable to load vertex credentials from environment. "
"Ensure the JSON is valid (check for unescaped newlines in private_key). "
f"Parse error: {type(e).__name__}"
)
except FileNotFoundError as e:
raise Exception(
"Unable to load vertex credentials from file path: "
f"{credentials}. File not found. ({e})"
) from e
except PermissionError as e:
raise Exception(
"Unable to load vertex credentials from file path: "
f"{credentials}. File not readable (permission denied). ({e})"
) from e
except OSError as e:
raise Exception(
"Unable to load vertex credentials from file path: "
f"{credentials}. Unable to read file. ({e})"
) from e
except json.JSONDecodeError as e:
raise Exception(
"Unable to load vertex credentials from file path: "
f"{credentials}. Ensure the JSON is valid "
"(check for unescaped newlines in private_key). "
f"Parse error: {type(e).__name__}: {e}"
) from e
elif isinstance(credentials, dict):
json_obj = credentials
else:

View file

@ -2193,3 +2193,36 @@ class TestVertexBase:
assert token == "cached-token"
assert not mock_get_lock.called, "Fast path should not acquire lock"
class TestLoadAuthCredentialFileErrors:
def test_missing_credential_file_names_path(self):
vertex_base = VertexBase()
with pytest.raises(Exception, match="File not found"):
vertex_base.load_auth(
credentials="/nonexistent/vertexai.json", project_id="p"
)
def test_unreadable_credential_file_names_path(self):
vertex_base = VertexBase()
with patch(
"builtins.open", side_effect=PermissionError(13, "Permission denied")
):
with pytest.raises(Exception, match="not readable"):
vertex_base.load_auth(
credentials="/some/vertexai.json", project_id="p"
)
def test_malformed_credential_file_keeps_json_advice(self, tmp_path):
bad_file = tmp_path / "vertexai.json"
bad_file.write_text("{not json")
vertex_base = VertexBase()
with pytest.raises(Exception, match="Ensure the JSON is valid"):
vertex_base.load_auth(
credentials=str(bad_file), project_id="p"
)
def test_malformed_inline_json_keeps_environment_message(self):
vertex_base = VertexBase()
with pytest.raises(Exception, match="from environment"):
vertex_base.load_auth(credentials="{not json", project_id="p")