From 683de4a46a8e9ef624bb56ce72cd292144fec6fd Mon Sep 17 00:00:00 2001 From: Tuan-Dat Tran Date: Fri, 27 Feb 2026 02:31:35 +0100 Subject: [PATCH] Add retry logic with exponential backoff for 429 rate limits --- README.md | 8 ++++---- hydrate.py | 50 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 991263d..1fa395b 100644 --- a/README.md +++ b/README.md @@ -352,12 +352,12 @@ for name, count in sorted(cards.items()): ## Collection Stats -- **Total cards:** 0 -- **Unique cards:** 0 +- **Total cards:** 1356 +- **Unique cards:** 916 - **By type:** - Artifacts: 77 - - Creatures: 390 - - Enchantments: 63 + - Creatures: 392 + - Enchantments: 64 - Instants: 109 - Lands: 72 - Planeswalkers: 2 diff --git a/hydrate.py b/hydrate.py index ac23050..63f879d 100644 --- a/hydrate.py +++ b/hydrate.py @@ -40,27 +40,43 @@ def parse_decklist(filepath: str) -> list[dict]: return cards -def fetch_card(name: str) -> Optional[dict]: +def fetch_card(name: str, retry_count: int = 3) -> Optional[dict]: """Fetch card data from Scryfall API using fuzzy search.""" encoded = urllib.parse.quote(name) url = f"{SCRYFALL_API}/cards/named?fuzzy={encoded}" - try: - req = urllib.request.Request(url, headers={ - "User-Agent": "EDHDeckBuilder/1.0", - "Accept": "*/*" - }) - with urllib.request.urlopen(req, timeout=30) as response: - return json.loads(response.read().decode("utf-8")) - except urllib.error.HTTPError as e: - print(f" Error fetching '{name}': HTTP {e.code}", file=sys.stderr) - return None - except urllib.error.URLError as e: - print(f" Error fetching '{name}': {e.reason}", file=sys.stderr) - return None - except json.JSONDecodeError: - print(f" Error parsing response for '{name}'", file=sys.stderr) - return None + for attempt in range(retry_count): + try: + req = urllib.request.Request(url, headers={ + "User-Agent": "EDHDeckBuilder/1.0", + "Accept": "*/*" + }) + with urllib.request.urlopen(req, timeout=30) as response: + return json.loads(response.read().decode("utf-8")) + except urllib.error.HTTPError as e: + if e.code == 429: + retry_after = int(e.headers.get("Retry-After", 60)) + print(f" Rate limited. Waiting {retry_after}s...", file=sys.stderr) + time.sleep(retry_after) + elif e.code == 404: + print(f" Error fetching '{name}': Not found", file=sys.stderr) + return None + else: + if attempt < retry_count - 1: + wait_time = 2 ** attempt + print(f" HTTP {e.code}, retrying in {wait_time}s...", file=sys.stderr) + time.sleep(wait_time) + else: + print(f" Error fetching '{name}': HTTP {e.code}", file=sys.stderr) + return None + except urllib.error.URLError as e: + print(f" Error fetching '{name}': {e.reason}", file=sys.stderr) + return None + except json.JSONDecodeError: + print(f" Error parsing response for '{name}'", file=sys.stderr) + return None + + return None def extract_card_info(card_data: dict) -> dict: