From ba1b1b36428cf28f7d811fa5614dc811021c8403 Mon Sep 17 00:00:00 2001 From: Asad Date: Mon, 21 Sep 2026 21:38:51 -0400 Subject: [PATCH] Fix chadwick_register() ignoring stale local cache file chadwick_register() checked only whether the local chadwick-register.csv existed, with no expiration check, so once a user called it with save=True the local copy was trusted forever and new players added upstream (e.g. new rookies) were never picked up. Added an age check against the existing cache.config.default_expiration threshold, matching the pattern already used elsewhere in the cache system. Fixes #493 --- pybaseball/playerid_lookup.py | 32 +++++++++++++-------- tests/pybaseball/test_playerid_lookup.py | 36 ++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 tests/pybaseball/test_playerid_lookup.py diff --git a/pybaseball/playerid_lookup.py b/pybaseball/playerid_lookup.py index 5ccbbc7a..bbc7b54b 100644 --- a/pybaseball/playerid_lookup.py +++ b/pybaseball/playerid_lookup.py @@ -2,6 +2,7 @@ import io import os import re +import time import zipfile from typing import List, Tuple, Iterable @@ -40,12 +41,19 @@ def _extract_people_table(zip_archive: zipfile.ZipFile) -> pd.DataFrame: return pd.concat(dfs, axis=0) +def _is_register_stale(path): + age = time.time() - os.path.getmtime(path) + max_age = cache.config.default_expiration * 86400 # days -> seconds + return age > max_age + + @cache.df_cache() def chadwick_register(save: bool = False) -> pd.DataFrame: ''' Get the Chadwick register Database ''' - if os.path.exists(get_register_file()): - table = pd.read_csv(get_register_file()) + path = get_register_file() + if os.path.exists(path) and not _is_register_stale(path): + table = pd.read_csv(path) return table print('Gathering player lookup table. This may take a moment.') @@ -67,7 +75,7 @@ def chadwick_register(save: bool = False) -> pd.DataFrame: table = table[cols_to_keep] if save: - table.to_csv(get_register_file(), index=False) + table.to_csv(path, index=False) return table @@ -136,10 +144,9 @@ def search(self, last: str, first: str = None, fuzzy: bool = False, ignore_accen # If no matches, return 5 closest names if len(results) == 0 and fuzzy: print("No identically matched names found! Returning the 5 most similar names.") - results=get_closest_names(last=last, first=first, player_table=self.table) - - return results + results = get_closest_names(last=last, first=first, player_table=self.table) + return results def search_list(self, player_list: List[Tuple[str, str]]) -> pd.DataFrame: ''' @@ -150,14 +157,13 @@ def search_list(self, player_list: List[Tuple[str, str]]) -> pd.DataFrame: Returns: pd.DataFrame: DataFrame of playerIDs, name, years played - ''' + ''' results = pd.DataFrame() for last, first in player_list: results = results.append(self.search(last, first), ignore_index=True) - - return results + return results def reverse_lookup(self, player_ids: List[str], key_type: str = 'mlbam') -> pd.DataFrame: """Retrieve a table of player information given a list of player ids @@ -193,6 +199,7 @@ def _get_client() -> _PlayerSearchClient: _client = _PlayerSearchClient() return _client + def playerid_lookup(last: str, first: str = None, fuzzy: bool = False, ignore_accents: bool = False) -> pd.DataFrame: """Lookup playerIDs (MLB AM, bbref, retrosheet, FG) for a given player @@ -208,6 +215,7 @@ def playerid_lookup(last: str, first: str = None, fuzzy: bool = False, ignore_ac client = _get_client() return client.search(last, first, fuzzy, ignore_accents) + def player_search_list(player_list: List[Tuple[str, str]]) -> pd.DataFrame: ''' Lookup playerIDs (MLB AM, bbref, retrosheet, FG) for a list of players. @@ -217,10 +225,11 @@ def player_search_list(player_list: List[Tuple[str, str]]) -> pd.DataFrame: Returns: pd.DataFrame: DataFrame of playerIDs, name, years played - ''' + ''' client = _get_client() return client.search_list(player_list) + def playerid_reverse_lookup(player_ids: List[str], key_type: str = 'mlbam') -> pd.DataFrame: """Retrieve a table of player information given a list of player ids @@ -234,6 +243,7 @@ def playerid_reverse_lookup(player_ids: List[str], key_type: str = 'mlbam') -> p client = _get_client() return client.reverse_lookup(player_ids, key_type) + def normalize_accents(s: str) -> str: """Removes accented letters from a string @@ -243,4 +253,4 @@ def normalize_accents(s: str) -> str: Returns: str: string with accented letters normalized """ - return ''.join(c for c in unicodedata.normalize('NFD', str(s)) if unicodedata.category(c) != 'Mn') + return ''.join(c for c in unicodedata.normalize('NFD', str(s)) if unicodedata.category(c) != 'Mn') \ No newline at end of file diff --git a/tests/pybaseball/test_playerid_lookup.py b/tests/pybaseball/test_playerid_lookup.py new file mode 100644 index 00000000..b19e0b8f --- /dev/null +++ b/tests/pybaseball/test_playerid_lookup.py @@ -0,0 +1,36 @@ +import os +import sys +import time +import tempfile +from pathlib import Path + +import pytest + +from pybaseball.playerid_lookup import chadwick_register + +pil = sys.modules["pybaseball.playerid_lookup"] + + +def test_chadwick_register_refreshes_when_stale(monkeypatch): + monkeypatch.setattr(pil.cache.config, "enabled", False) + with tempfile.TemporaryDirectory() as td: + fake_register = Path(td) / "chadwick-register.csv" + fake_register.write_text("name_last,name_first,key_mlbam\nstale,player,1\n") + + monkeypatch.setattr(pil, "get_register_file", lambda: str(fake_register)) + + old = time.time() - 100 * 86400 + os.utime(fake_register, (old, old)) + + calls = {"n": 0} + + def fake_get(*args, **kwargs): + calls["n"] += 1 + raise RuntimeError("network hit") + + monkeypatch.setattr(pil.requests, "get", fake_get) + + with pytest.raises(RuntimeError, match="network hit"): + chadwick_register() + + assert calls["n"] == 1 \ No newline at end of file