"""Kaggle PaySim dataset download script (SETUP-02). Downloads the `ealaxi/paysim1` dataset from Kaggle into `data/` and unzips it so the PaySim CSV lands directly under `data/`. Note: the dataset originally lived at `ntnu-testimon/paysim1`; that slug now returns HTTP 403 (dataset moved/retired under the old owner). `ealaxi/paysim1` is the current live mirror of the same PaySim simulation output (same ~6.36M-row schema: step, type, amount, nameOrig/nameDest, balances, isFraud, isFlaggedFraud). Reads `KAGGLE_USERNAME`/`KAGGLE_KEY` from the environment (via `.env`, loaded with python-dotenv) and fails loud -- naming the missing variable(s) -- BEFORE attempting any network call or importing the `kaggle` package. This check must run before `import kaggle`: the installed `kaggle` client authenticates eagerly as a side effect of importing the package (any submodule import triggers it), which would otherwise surface its own, less specific error before our validation ever runs. Idempotent: if a PaySim CSV already exists under `data/`, the download is skipped and the existing path is reported. Usage: python training/fetch_data.py """ from __future__ import annotations import sys from pathlib import Path from dotenv import load_dotenv DATASET = "ealaxi/paysim1" DATA_DIR = Path(__file__).resolve().parent.parent / "data" class FetchDataError(RuntimeError): """Raised when the PaySim dataset cannot be fetched. Messages never include credential values -- only variable names. """ def _require_kaggle_credentials() -> None: """Fail loud, naming any missing/empty Kaggle credential env var. Runs before any network call and before `kaggle` is imported. """ import os missing = [ name for name in ("KAGGLE_USERNAME", "KAGGLE_KEY") if not os.environ.get(name, "").strip() ] if missing: raise FetchDataError( "Missing required Kaggle credential(s) in the environment: " f"{', '.join(missing)}. Set them in .env (see README) before " "running training/fetch_data.py. No network call was made." ) def _existing_csv() -> Path | None: if not DATA_DIR.exists(): return None csvs = sorted(DATA_DIR.glob("*.csv")) return csvs[0] if csvs else None def main() -> Path: load_dotenv() _require_kaggle_credentials() existing = _existing_csv() if existing is not None: print(f"PaySim CSV already present, skipping download: {existing}") return existing # Imported lazily, after credential validation above: importing the # `kaggle` package (any submodule) triggers its own eager # authentication check as a side effect of import. from kaggle.api.kaggle_api_extended import KaggleApi from requests.exceptions import HTTPError DATA_DIR.mkdir(parents=True, exist_ok=True) try: api = KaggleApi() api.authenticate() print(f"Downloading {DATASET} into {DATA_DIR} ...") api.dataset_download_files(DATASET, path=str(DATA_DIR), unzip=True) except HTTPError as exc: status = exc.response.status_code if exc.response is not None else "unknown" raise FetchDataError( f"Kaggle API request failed (HTTP {status}) while downloading " f"'{DATASET}'. This is a Kaggle-side authorization/response " "issue, not a missing-credential problem -- KAGGLE_USERNAME/" "KAGGLE_KEY were present and accepted by the auth step. " "Verify the API token is current (regenerate at " "https://www.kaggle.com/settings/api if needed) and that the " "account has access to this dataset." ) from exc csv_path = _existing_csv() if csv_path is None: raise FetchDataError( f"Download completed but no .csv file was found under {DATA_DIR}. " "Check the Kaggle dataset contents." ) print(f"PaySim CSV available at: {csv_path}") return csv_path if __name__ == "__main__": try: main() except FetchDataError as exc: print(f"ERROR: {exc}", file=sys.stderr) sys.exit(1)