From e870801f2fe1c447cb667c764a5aca85f22969d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karsten=20M=C3=BCller?= <52325194+karstenm1987@users.noreply.github.com> Date: Sun, 19 Apr 2026 09:35:50 +0200 Subject: [PATCH] Wrap raw errors in GMDCommandError for invalid input and offline Two places leaked internal exceptions to users: 1. `_tokens()` used `for item in value:` with no type guard. Passing a scalar like `gmd(country=840)` produced TypeError: 'int' object is not iterable instead of a clean command error. Now `_tokens()` rejects non-str / non-list / non-tuple inputs with a `GMDCommandError(code=198)` and a message that names the actual type received. 2. `get_available_versions()` re-raised the internal `RuntimeError` from `_fetch_from` when both the S3 primary and GitHub fallback failed and there was no local cache. Wrap that terminal raise in `GMDCommandError` with a human-readable message pointing at the likely cause (no internet / transient outage). Internal issue: KMueller-Lab/Global-Macro-Database-Internal#414 Co-Authored-By: Claude Opus 4.7 (1M context) --- global_macro_data/gmd.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/global_macro_data/gmd.py b/global_macro_data/gmd.py index 07e7afa..52ca310 100644 --- a/global_macro_data/gmd.py +++ b/global_macro_data/gmd.py @@ -139,6 +139,11 @@ def _tokens(value: Union[str, Sequence[str], None]) -> List[str]: if isinstance(value, str): bits = value.replace(",", " ").split() return [bit.strip() for bit in bits if bit.strip()] + if not isinstance(value, (list, tuple)): + raise GMDCommandError( + f"Expected a string or sequence of strings, got {type(value).__name__}.", + code=198, + ) out: List[str] = [] for item in value: if isinstance(item, str): @@ -330,13 +335,16 @@ def _strip_source_prefix_cols(df: pd.DataFrame, source: str) -> List[str]: def get_available_versions() -> List[str]: try: return _versions_df()["versions"].astype(str).tolist() - except RuntimeError: + except RuntimeError as exc: versions = _cache_versions() if versions: return versions if _default_local_gmd_path() is not None: return ["local"] - raise + raise GMDCommandError( + "Unable to fetch the list of available GMD versions. " + "Check your internet connection or raise an issue." + ) from exc def get_current_version() -> str: