From 4be0e2345bd57f649c3bba76c953b517d39aa039 Mon Sep 17 00:00:00 2001 From: Robbie Date: Thu, 28 Nov 2024 12:50:40 -0500 Subject: [PATCH 01/14] handle data is in list and handle appropriately --- espn_api/base_league.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/espn_api/base_league.py b/espn_api/base_league.py index 9f7c3fd2..969eb19b 100644 --- a/espn_api/base_league.py +++ b/espn_api/base_league.py @@ -31,6 +31,12 @@ def __repr__(self): def _fetch_league(self, SettingsClass = BaseSettings): data = self.espn_request.get_league() + # Check if the data is a list (which happens when year is 2018 for some leagues) + if isinstance(data, list): + # If it's a list, we assume the relevant data is at index 0 + data = data[0] + + self.currentMatchupPeriod = data['status']['currentMatchupPeriod'] self.scoringPeriodId = data['scoringPeriodId'] self.firstScoringPeriod = data['status']['firstScoringPeriod'] From 59f57ef46e143115122bd622fc0d8eaca7276ccf Mon Sep 17 00:00:00 2001 From: Robbie Date: Thu, 28 Nov 2024 12:56:15 -0500 Subject: [PATCH 02/14] added function check_league_endpoint to validate if either endpoint works in the event of 401 error --- espn_api/requests/espn_requests.py | 31 +++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/espn_api/requests/espn_requests.py b/espn_api/requests/espn_requests.py index 924d12f5..e93caac2 100644 --- a/espn_api/requests/espn_requests.py +++ b/espn_api/requests/espn_requests.py @@ -48,7 +48,9 @@ def __init__(self, sport: str, year: int, league_id: int, cookies: dict = None, self.LEAGUE_ENDPOINT += "/leagueHistory/" + str(league_id) + "?seasonId=" + str(year) else: self.LEAGUE_ENDPOINT += "/seasons/" + str(year) + "/segments/0/leagues/" + str(league_id) - + + # Try the first endpoint and fall back to the second if the first fails + self.check_league_endpoint() def league_get(self, params: dict = None, headers: dict = None, extend: str = ''): endpoint = self.LEAGUE_ENDPOINT + extend r = requests.get(endpoint, params=params, headers=headers, cookies=self.cookies) @@ -131,6 +133,33 @@ def get_player_card(self, playerIds: List[int], max_scoring_period: int, additio data = self.league_get(params=params, headers=headers) return data + + def check_league_endpoint(self): + # First, try the current LEAGUE_ENDPOINT + r = requests.get(self.LEAGUE_ENDPOINT, cookies=self.cookies) + + # If the response is 401 Unauthorized, switch to the other endpoint + if r.status_code == 401: + # If the current LEAGUE_ENDPOINT was using the /leagueHistory/ endpoint, switch to the /seasons/{year}/segments/0 endpoint + if "/leagueHistory/" in self.LEAGUE_ENDPOINT: + alternate_endpoint = self.LEAGUE_ENDPOINT.replace("/leagueHistory/", "/seasons/" + str(self.year) + "/segments/0/leagues/") + else: + # Otherwise, switch to the /leagueHistory/{league_id}?seasonId={year} endpoint + alternate_endpoint = self.LEAGUE_ENDPOINT.replace("/seasons/" + str(self.year) + "/segments/0/leagues/", "/leagueHistory/") + "?seasonId=" + str(self.year) + + # Try the alternate endpoint + r = requests.get(alternate_endpoint, cookies=self.cookies) + + # If the alternate endpoint works (status 200), update the LEAGUE_ENDPOINT + if r.status_code == 200: + self.LEAGUE_ENDPOINT = alternate_endpoint + if self.logger: + self.logger.logging.info(f"Switched to alternate endpoint: {self.LEAGUE_ENDPOINT}") + else: + # If both endpoints fail, log the error and raise an exception + if self.logger: + self.logger.logging.error(f"Both endpoints failed with status: {r.status_code}") + raise Exception(f"Both endpoints for league {self.league_id} failed with status {r.status_code}") # Username and password no longer works using their API without using google recaptcha # Possibly revisit in future if anything changes From 6de93f4f063c5397bbee8a19ed8b72f11e3402bf Mon Sep 17 00:00:00 2001 From: Robbie Date: Thu, 28 Nov 2024 13:00:31 -0500 Subject: [PATCH 03/14] added check & handle for data in list to fetch_draft function --- espn_api/base_league.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/espn_api/base_league.py b/espn_api/base_league.py index 969eb19b..c0b97fa7 100644 --- a/espn_api/base_league.py +++ b/espn_api/base_league.py @@ -56,6 +56,11 @@ def _fetch_draft(self): '''Creates list of Pick objects from the leagues draft''' data = self.espn_request.get_league_draft() + # Check if the data is a list (which happens when year is 2018 or earlier) + if isinstance(data, list): + # If it's a list, we assume the relevant data is at index 0 + data = data[0] + # League has not drafted yet if not data.get('draftDetail', {}).get('drafted'): return From cbbe2308bd415ce80c193dc86615adcd5ca0a11b Mon Sep 17 00:00:00 2001 From: Robbie Date: Thu, 28 Nov 2024 13:12:13 -0500 Subject: [PATCH 04/14] updated espn_requests to check & handle 2018 JSON format --- espn_api/requests/espn_requests.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/espn_api/requests/espn_requests.py b/espn_api/requests/espn_requests.py index e93caac2..eb6cc1df 100644 --- a/espn_api/requests/espn_requests.py +++ b/espn_api/requests/espn_requests.py @@ -75,6 +75,10 @@ def get_league(self): 'view': ['mTeam', 'mRoster', 'mMatchup', 'mSettings', 'mStandings'] } data = self.league_get(params=params) + # Check if the data is a list (which happens when year is 2018 or earlier) + if isinstance(data, list): + # If it's a list, we assume the relevant data is at index 0 + data = data[0] return data def get_pro_schedule(self): @@ -101,6 +105,10 @@ def get_league_draft(self): 'view': 'mDraftDetail', } data = self.league_get(params=params) + # Check if the data is a list (which happens when year is 2018 in some leagues) + if isinstance(data, list): + # If it's a list, we assume the relevant data is at index 0 + data = data[0] return data def get_league_message_board(self, msg_types = None): @@ -132,8 +140,13 @@ def get_player_card(self, playerIds: List[int], max_scoring_period: int, additio headers = {'x-fantasy-filter': json.dumps(filters)} data = self.league_get(params=params, headers=headers) - return data + # Check if the data is a list (which happens when year is 2018 or earlier) + if isinstance(data, list): + # If it's a list, we assume the relevant data is at index 0 + data = data[0] + return data + def check_league_endpoint(self): # First, try the current LEAGUE_ENDPOINT r = requests.get(self.LEAGUE_ENDPOINT, cookies=self.cookies) From f31f0b8fbd60fc6d6af1a8b842940d6f6bb839a4 Mon Sep 17 00:00:00 2001 From: Robbie Date: Thu, 28 Nov 2024 13:12:40 -0500 Subject: [PATCH 05/14] moved JSON checking from base_league to espn_requests for 2018 data --- espn_api/base_league.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/espn_api/base_league.py b/espn_api/base_league.py index c0b97fa7..4c69e599 100644 --- a/espn_api/base_league.py +++ b/espn_api/base_league.py @@ -30,13 +30,6 @@ def __repr__(self): def _fetch_league(self, SettingsClass = BaseSettings): data = self.espn_request.get_league() - - # Check if the data is a list (which happens when year is 2018 for some leagues) - if isinstance(data, list): - # If it's a list, we assume the relevant data is at index 0 - data = data[0] - - self.currentMatchupPeriod = data['status']['currentMatchupPeriod'] self.scoringPeriodId = data['scoringPeriodId'] self.firstScoringPeriod = data['status']['firstScoringPeriod'] @@ -55,12 +48,6 @@ def _fetch_league(self, SettingsClass = BaseSettings): def _fetch_draft(self): '''Creates list of Pick objects from the leagues draft''' data = self.espn_request.get_league_draft() - - # Check if the data is a list (which happens when year is 2018 or earlier) - if isinstance(data, list): - # If it's a list, we assume the relevant data is at index 0 - data = data[0] - # League has not drafted yet if not data.get('draftDetail', {}).get('drafted'): return From dd582c37662d319e8ac6db8970b62e5cfaf8a3d9 Mon Sep 17 00:00:00 2001 From: Robbie Date: Thu, 26 Dec 2024 14:16:45 -0500 Subject: [PATCH 06/14] moved JSON response list check to league_get --- espn_api/requests/espn_requests.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/espn_api/requests/espn_requests.py b/espn_api/requests/espn_requests.py index eb6cc1df..8959b54c 100644 --- a/espn_api/requests/espn_requests.py +++ b/espn_api/requests/espn_requests.py @@ -58,7 +58,14 @@ def league_get(self, params: dict = None, headers: dict = None, extend: str = '' if self.logger: self.logger.log_request(endpoint=endpoint, params=params, headers=headers, response=r.json()) - return r.json() if self.year > 2017 else r.json()[0] + + data = r.json() + # Check if the data is a list + if isinstance(data, list): + # If it's a list, assume the relevant data is at index 0 + data = data[0] + + return data def get(self, params: dict = None, headers: dict = None, extend: str = ''): endpoint = self.ENDPOINT + extend @@ -74,12 +81,7 @@ def get_league(self): params = { 'view': ['mTeam', 'mRoster', 'mMatchup', 'mSettings', 'mStandings'] } - data = self.league_get(params=params) - # Check if the data is a list (which happens when year is 2018 or earlier) - if isinstance(data, list): - # If it's a list, we assume the relevant data is at index 0 - data = data[0] - return data + data = self.league_get(params=params) def get_pro_schedule(self): '''Gets the current sports professional team schedules''' @@ -105,11 +107,6 @@ def get_league_draft(self): 'view': 'mDraftDetail', } data = self.league_get(params=params) - # Check if the data is a list (which happens when year is 2018 in some leagues) - if isinstance(data, list): - # If it's a list, we assume the relevant data is at index 0 - data = data[0] - return data def get_league_message_board(self, msg_types = None): '''Gets league message board and can filter by msg types''' @@ -140,12 +137,6 @@ def get_player_card(self, playerIds: List[int], max_scoring_period: int, additio headers = {'x-fantasy-filter': json.dumps(filters)} data = self.league_get(params=params, headers=headers) - - # Check if the data is a list (which happens when year is 2018 or earlier) - if isinstance(data, list): - # If it's a list, we assume the relevant data is at index 0 - data = data[0] - return data def check_league_endpoint(self): # First, try the current LEAGUE_ENDPOINT From 07acba1609d1a99d5ed0f2bee5d45059c68f31f8 Mon Sep 17 00:00:00 2001 From: Robbie Date: Thu, 26 Dec 2024 15:29:54 -0500 Subject: [PATCH 07/14] added incorrectly removed returns & finalized logic in league_get --- espn_api/requests/espn_requests.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/espn_api/requests/espn_requests.py b/espn_api/requests/espn_requests.py index 8959b54c..41f0685c 100644 --- a/espn_api/requests/espn_requests.py +++ b/espn_api/requests/espn_requests.py @@ -58,14 +58,8 @@ def league_get(self, params: dict = None, headers: dict = None, extend: str = '' if self.logger: self.logger.log_request(endpoint=endpoint, params=params, headers=headers, response=r.json()) - - data = r.json() - # Check if the data is a list - if isinstance(data, list): - # If it's a list, assume the relevant data is at index 0 - data = data[0] - - return data + response = r.json() + return response[0] if isinstance(response, list) else response def get(self, params: dict = None, headers: dict = None, extend: str = ''): endpoint = self.ENDPOINT + extend @@ -81,7 +75,8 @@ def get_league(self): params = { 'view': ['mTeam', 'mRoster', 'mMatchup', 'mSettings', 'mStandings'] } - data = self.league_get(params=params) + data = self.league_get(params=params) + return data def get_pro_schedule(self): '''Gets the current sports professional team schedules''' @@ -107,6 +102,7 @@ def get_league_draft(self): 'view': 'mDraftDetail', } data = self.league_get(params=params) + return data def get_league_message_board(self, msg_types = None): '''Gets league message board and can filter by msg types''' @@ -137,6 +133,7 @@ def get_player_card(self, playerIds: List[int], max_scoring_period: int, additio headers = {'x-fantasy-filter': json.dumps(filters)} data = self.league_get(params=params, headers=headers) + return data def check_league_endpoint(self): # First, try the current LEAGUE_ENDPOINT From 0f6692c8f810744bbbd0470ff29c59486d6e90c7 Mon Sep 17 00:00:00 2001 From: Robert Litts Date: Thu, 2 Jan 2025 15:52:26 -0500 Subject: [PATCH 08/14] refactored check_league_endpoint to handle 401/404 errors, using checkRequestStatus --- espn_api/requests/espn_requests.py | 52 +++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/espn_api/requests/espn_requests.py b/espn_api/requests/espn_requests.py index 41f0685c..00fdd183 100644 --- a/espn_api/requests/espn_requests.py +++ b/espn_api/requests/espn_requests.py @@ -136,31 +136,51 @@ def get_player_card(self, playerIds: List[int], max_scoring_period: int, additio return data def check_league_endpoint(self): - # First, try the current LEAGUE_ENDPOINT r = requests.get(self.LEAGUE_ENDPOINT, cookies=self.cookies) - - # If the response is 401 Unauthorized, switch to the other endpoint - if r.status_code == 401: - # If the current LEAGUE_ENDPOINT was using the /leagueHistory/ endpoint, switch to the /seasons/{year}/segments/0 endpoint + + if self.logger: + self.logger.log_request( + endpoint=self.LEAGUE_ENDPOINT, + params={}, + headers={}, + response=r.json() + ) + + # If the response is 401 or 404, try alternate endpoint + if r.status_code in [401, 404]: + # If the current LEAGUE_ENDPOINT was using the /leagueHistory/ endpoint if "/leagueHistory/" in self.LEAGUE_ENDPOINT: - alternate_endpoint = self.LEAGUE_ENDPOINT.replace("/leagueHistory/", "/seasons/" + str(self.year) + "/segments/0/leagues/") + alternate_endpoint = self.LEAGUE_ENDPOINT.replace( + "/leagueHistory/", + f"/seasons/{self.year}/segments/0/leagues/" + ) else: - # Otherwise, switch to the /leagueHistory/{league_id}?seasonId={year} endpoint - alternate_endpoint = self.LEAGUE_ENDPOINT.replace("/seasons/" + str(self.year) + "/segments/0/leagues/", "/leagueHistory/") + "?seasonId=" + str(self.year) - + # Switch to the /leagueHistory/{league_id}?seasonId={year} endpoint + base_endpoint = self.LEAGUE_ENDPOINT.replace( + f"/seasons/{self.year}/segments/0/leagues/", + "/leagueHistory/" + ) + alternate_endpoint = f"{base_endpoint}?seasonId={self.year}" + # Try the alternate endpoint r = requests.get(alternate_endpoint, cookies=self.cookies) - # If the alternate endpoint works (status 200), update the LEAGUE_ENDPOINT + if self.logger: + self.logger.log_request( + endpoint=alternate_endpoint, + params={}, + headers={}, + response=r.json() + ) + + # If the alternate endpoint works, update the LEAGUE_ENDPOINT if r.status_code == 200: self.LEAGUE_ENDPOINT = alternate_endpoint - if self.logger: - self.logger.logging.info(f"Switched to alternate endpoint: {self.LEAGUE_ENDPOINT}") else: - # If both endpoints fail, log the error and raise an exception - if self.logger: - self.logger.logging.error(f"Both endpoints failed with status: {r.status_code}") - raise Exception(f"Both endpoints for league {self.league_id} failed with status {r.status_code}") + checkRequestStatus(r.status_code, self.cookies, self.league_id) + else: + # Check status of initial request if it wasn't 401/404 + checkRequestStatus(r.status_code, self.cookies, self.league_id) # Username and password no longer works using their API without using google recaptcha # Possibly revisit in future if anything changes From a5ccc56b970b967c65af849101087622113bccd4 Mon Sep 17 00:00:00 2001 From: Robert Litts Date: Thu, 2 Jan 2025 21:08:28 -0500 Subject: [PATCH 09/14] moved check_league_endpoint into existing checkRequestStatus function --- espn_api/requests/espn_requests.py | 98 +++++++++++++----------------- 1 file changed, 43 insertions(+), 55 deletions(-) diff --git a/espn_api/requests/espn_requests.py b/espn_api/requests/espn_requests.py index 00fdd183..e46c872b 100644 --- a/espn_api/requests/espn_requests.py +++ b/espn_api/requests/espn_requests.py @@ -17,19 +17,49 @@ class ESPNUnknownError(Exception): pass -def checkRequestStatus(status: int, cookies=None, league_id=None) -> None: +def checkRequestStatus(status: int, cookies=None, league_id=None, league_endpoint=None, year=None, extend="") -> tuple: if cookies is None: cookies = {} if league_id is None: - league_id = "" + league_id = "" if status == 401: - raise ESPNAccessDenied(f"League {league_id} cannot be accessed with espn_s2={cookies.get('espn_s2')} and swid={cookies.get('SWID')}") + if league_endpoint and year: + # If the current LEAGUE_ENDPOINT was using the /leagueHistory/ endpoint + if "/leagueHistory/" in league_endpoint: + alternate_endpoint = league_endpoint.replace( + "/leagueHistory/", + f"/seasons/{year}/segments/0/leagues/" + ) + else: + # Switch to the /leagueHistory/{league_id}?seasonId={year} endpoint + base_endpoint = league_endpoint.replace( + f"/seasons/{year}/segments/0/leagues/", + "/leagueHistory/" + ) + alternate_endpoint = f"{base_endpoint}?seasonId={year}" + + endpoint = alternate_endpoint + extend + #try alternate endpoint + r = requests.get(endpoint, cookies=cookies) + + if r.status_code == 200: + # Return the updated league endpoint if alternate works & the response + return alternate_endpoint, r.json() + else: + # If alternate fails, raise the corresponding error + raise ESPNAccessDenied(f"League {league_id} cannot be accessed with espn_s2={cookies.get('espn_s2')} and swid={cookies.get('SWID')}") + else: + # If no league endpoint or year info provided, raise the error directly + raise ESPNAccessDenied(f"League {league_id} cannot be accessed with espn_s2={cookies.get('espn_s2')} and swid={cookies.get('SWID')}") elif status == 404: raise ESPNInvalidLeague(f"League {league_id} does not exist") elif status != 200: raise ESPNUnknownError(f"ESPN returned an HTTP {status}") + + # If no issues with the status code, return None, None (unchanged endpoint) + return None, None class EspnFantasyRequests(object): @@ -49,16 +79,21 @@ def __init__(self, sport: str, year: int, league_id: int, cookies: dict = None, else: self.LEAGUE_ENDPOINT += "/seasons/" + str(year) + "/segments/0/leagues/" + str(league_id) - # Try the first endpoint and fall back to the second if the first fails - self.check_league_endpoint() def league_get(self, params: dict = None, headers: dict = None, extend: str = ''): endpoint = self.LEAGUE_ENDPOINT + extend r = requests.get(endpoint, params=params, headers=headers, cookies=self.cookies) - checkRequestStatus(r.status_code, cookies=self.cookies, league_id=self.league_id) + alternate_endpoint, alternate_response = checkRequestStatus(r.status_code, cookies=self.cookies, league_id=self.league_id, league_endpoint=self.LEAGUE_ENDPOINT, year=self.year, extend=extend) + + if alternate_endpoint: + self.LEAGUE_ENDPOINT = alternate_endpoint + endpoint = alternate_endpoint + extend + response = alternate_response + else: + response = r.json() if self.logger: - self.logger.log_request(endpoint=endpoint, params=params, headers=headers, response=r.json()) - response = r.json() + self.logger.log_request(endpoint=endpoint, params=params, headers=headers, response=response) + return response[0] if isinstance(response, list) else response def get(self, params: dict = None, headers: dict = None, extend: str = ''): @@ -135,53 +170,6 @@ def get_player_card(self, playerIds: List[int], max_scoring_period: int, additio data = self.league_get(params=params, headers=headers) return data - def check_league_endpoint(self): - r = requests.get(self.LEAGUE_ENDPOINT, cookies=self.cookies) - - if self.logger: - self.logger.log_request( - endpoint=self.LEAGUE_ENDPOINT, - params={}, - headers={}, - response=r.json() - ) - - # If the response is 401 or 404, try alternate endpoint - if r.status_code in [401, 404]: - # If the current LEAGUE_ENDPOINT was using the /leagueHistory/ endpoint - if "/leagueHistory/" in self.LEAGUE_ENDPOINT: - alternate_endpoint = self.LEAGUE_ENDPOINT.replace( - "/leagueHistory/", - f"/seasons/{self.year}/segments/0/leagues/" - ) - else: - # Switch to the /leagueHistory/{league_id}?seasonId={year} endpoint - base_endpoint = self.LEAGUE_ENDPOINT.replace( - f"/seasons/{self.year}/segments/0/leagues/", - "/leagueHistory/" - ) - alternate_endpoint = f"{base_endpoint}?seasonId={self.year}" - - # Try the alternate endpoint - r = requests.get(alternate_endpoint, cookies=self.cookies) - - if self.logger: - self.logger.log_request( - endpoint=alternate_endpoint, - params={}, - headers={}, - response=r.json() - ) - - # If the alternate endpoint works, update the LEAGUE_ENDPOINT - if r.status_code == 200: - self.LEAGUE_ENDPOINT = alternate_endpoint - else: - checkRequestStatus(r.status_code, self.cookies, self.league_id) - else: - # Check status of initial request if it wasn't 401/404 - checkRequestStatus(r.status_code, self.cookies, self.league_id) - # Username and password no longer works using their API without using google recaptcha # Possibly revisit in future if anything changes From e63cfe05ac690b1ff9a12f0a5bf18ce8fbeb043c Mon Sep 17 00:00:00 2001 From: Robert Litts Date: Thu, 2 Jan 2025 22:23:41 -0500 Subject: [PATCH 10/14] added params/headers into checkRequestStatus --- espn_api/requests/espn_requests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/espn_api/requests/espn_requests.py b/espn_api/requests/espn_requests.py index e46c872b..fa6fb4f3 100644 --- a/espn_api/requests/espn_requests.py +++ b/espn_api/requests/espn_requests.py @@ -17,7 +17,7 @@ class ESPNUnknownError(Exception): pass -def checkRequestStatus(status: int, cookies=None, league_id=None, league_endpoint=None, year=None, extend="") -> tuple: +def checkRequestStatus(status: int, cookies=None, league_id=None, league_endpoint=None, year=None, extend="", params=None, headers=None) -> tuple: if cookies is None: cookies = {} if league_id is None: @@ -40,7 +40,7 @@ def checkRequestStatus(status: int, cookies=None, league_id=None, league_endpoin endpoint = alternate_endpoint + extend #try alternate endpoint - r = requests.get(endpoint, cookies=cookies) + r = requests.get(endpoint, params=params, headers=headers, cookies=cookies) if r.status_code == 200: # Return the updated league endpoint if alternate works & the response @@ -82,7 +82,7 @@ def __init__(self, sport: str, year: int, league_id: int, cookies: dict = None, def league_get(self, params: dict = None, headers: dict = None, extend: str = ''): endpoint = self.LEAGUE_ENDPOINT + extend r = requests.get(endpoint, params=params, headers=headers, cookies=self.cookies) - alternate_endpoint, alternate_response = checkRequestStatus(r.status_code, cookies=self.cookies, league_id=self.league_id, league_endpoint=self.LEAGUE_ENDPOINT, year=self.year, extend=extend) + alternate_endpoint, alternate_response = checkRequestStatus(r.status_code, cookies=self.cookies, league_id=self.league_id, league_endpoint=self.LEAGUE_ENDPOINT, year=self.year, extend=extend, params=params, headers=headers) if alternate_endpoint: self.LEAGUE_ENDPOINT = alternate_endpoint From ef926265ac54ed65b5b7ba17485d01ba3143057c Mon Sep 17 00:00:00 2001 From: Robert Litts Date: Fri, 3 Jan 2025 14:11:55 -0500 Subject: [PATCH 11/14] moved checkRequestStatus into class EspnFantasyRequests --- espn_api/requests/espn_requests.py | 90 ++++++++++++------------------ 1 file changed, 36 insertions(+), 54 deletions(-) diff --git a/espn_api/requests/espn_requests.py b/espn_api/requests/espn_requests.py index fa6fb4f3..c60e78ac 100644 --- a/espn_api/requests/espn_requests.py +++ b/espn_api/requests/espn_requests.py @@ -17,51 +17,6 @@ class ESPNUnknownError(Exception): pass -def checkRequestStatus(status: int, cookies=None, league_id=None, league_endpoint=None, year=None, extend="", params=None, headers=None) -> tuple: - if cookies is None: - cookies = {} - if league_id is None: - league_id = "" - if status == 401: - if league_endpoint and year: - # If the current LEAGUE_ENDPOINT was using the /leagueHistory/ endpoint - if "/leagueHistory/" in league_endpoint: - alternate_endpoint = league_endpoint.replace( - "/leagueHistory/", - f"/seasons/{year}/segments/0/leagues/" - ) - else: - # Switch to the /leagueHistory/{league_id}?seasonId={year} endpoint - base_endpoint = league_endpoint.replace( - f"/seasons/{year}/segments/0/leagues/", - "/leagueHistory/" - ) - alternate_endpoint = f"{base_endpoint}?seasonId={year}" - - endpoint = alternate_endpoint + extend - #try alternate endpoint - r = requests.get(endpoint, params=params, headers=headers, cookies=cookies) - - if r.status_code == 200: - # Return the updated league endpoint if alternate works & the response - return alternate_endpoint, r.json() - else: - # If alternate fails, raise the corresponding error - raise ESPNAccessDenied(f"League {league_id} cannot be accessed with espn_s2={cookies.get('espn_s2')} and swid={cookies.get('SWID')}") - else: - # If no league endpoint or year info provided, raise the error directly - raise ESPNAccessDenied(f"League {league_id} cannot be accessed with espn_s2={cookies.get('espn_s2')} and swid={cookies.get('SWID')}") - - elif status == 404: - raise ESPNInvalidLeague(f"League {league_id} does not exist") - - elif status != 200: - raise ESPNUnknownError(f"ESPN returned an HTTP {status}") - - # If no issues with the status code, return None, None (unchanged endpoint) - return None, None - - class EspnFantasyRequests(object): def __init__(self, sport: str, year: int, league_id: int, cookies: dict = None, logger: Logger = None): if sport not in FANTASY_SPORTS: @@ -78,28 +33,55 @@ def __init__(self, sport: str, year: int, league_id: int, cookies: dict = None, self.LEAGUE_ENDPOINT += "/leagueHistory/" + str(league_id) + "?seasonId=" + str(year) else: self.LEAGUE_ENDPOINT += "/seasons/" + str(year) + "/segments/0/leagues/" + str(league_id) + + def checkRequestStatus(self, status: int, extend: str = "", params: dict = None, headers: dict = None) -> dict: + '''Handles ESPN API response status codes and endpoint format switching''' + if status == 401: + # If the current LEAGUE_ENDPOINT was using the /leagueHistory/ endpoint, switch to "/seasons/" endpoint + if "/leagueHistory/" in self.LEAGUE_ENDPOINT: + base_endpoint = self.LEAGUE_ENDPOINT.split("/leagueHistory/")[0] + self.LEAGUE_ENDPOINT = f"{base_endpoint}/seasons/{self.year}/segments/0/leagues/{self.league_id}" + else: + # If the current LEAGUE_ENDPOINT was using /seasons, switch to the "/leagueHistory/" endpoint + base_endpoint = self.LEAGUE_ENDPOINT.split(f"/seasons/")[0] + self.LEAGUE_ENDPOINT = f"{base_endpoint}/leagueHistory/{self.league_id}?seasonId={self.year}" + + #try the alternate endpoint + r = requests.get(self.LEAGUE_ENDPOINT + extend, params=params, headers=headers, cookies=self.cookies) + + if r.status_code == 200: + # Return the updated response if alternate works + return r.json() + + # If all endpoints failed, raise the corresponding error + raise ESPNAccessDenied(f"League {self.league_id} cannot be accessed with espn_s2={self.cookies.get('espn_s2')} and swid={self.cookies.get('SWID')}") + + elif status == 404: + raise ESPNInvalidLeague(f"League {self.league_id} does not exist") + + elif status != 200: + raise ESPNUnknownError(f"ESPN returned an HTTP {status}") + + # If no issues with the status code, return None + return None def league_get(self, params: dict = None, headers: dict = None, extend: str = ''): endpoint = self.LEAGUE_ENDPOINT + extend r = requests.get(endpoint, params=params, headers=headers, cookies=self.cookies) - alternate_endpoint, alternate_response = checkRequestStatus(r.status_code, cookies=self.cookies, league_id=self.league_id, league_endpoint=self.LEAGUE_ENDPOINT, year=self.year, extend=extend, params=params, headers=headers) + alternate_response = self.checkRequestStatus(r.status_code, extend=extend, params=params, headers=headers) - if alternate_endpoint: - self.LEAGUE_ENDPOINT = alternate_endpoint - endpoint = alternate_endpoint + extend - response = alternate_response - else: - response = r.json() + + response = alternate_response if alternate_response else r.json() if self.logger: - self.logger.log_request(endpoint=endpoint, params=params, headers=headers, response=response) + self.logger.log_request(endpoint=self.LEAGUE_ENDPOINT + extend, params=params, headers=headers, response=response) return response[0] if isinstance(response, list) else response def get(self, params: dict = None, headers: dict = None, extend: str = ''): endpoint = self.ENDPOINT + extend r = requests.get(endpoint, params=params, headers=headers, cookies=self.cookies) - checkRequestStatus(r.status_code) + self.checkRequestStatus(r.status_code) if self.logger: self.logger.log_request(endpoint=endpoint, params=params, headers=headers, response=r.json()) From d64b1cdccc8ffdee206bc1269b42c791eb507af7 Mon Sep 17 00:00:00 2001 From: Robert Litts Date: Mon, 6 Jan 2025 20:41:43 -0500 Subject: [PATCH 12/14] modified test_private_league to verify league switched to fallback URL --- tests/football/integration/test_league.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/football/integration/test_league.py b/tests/football/integration/test_league.py index d9a89316..d9f79b13 100644 --- a/tests/football/integration/test_league.py +++ b/tests/football/integration/test_league.py @@ -15,8 +15,10 @@ def test_past_league(self): self.assertEqual(league.nfl_week, 18) def test_private_league(self): - with self.assertRaises(Exception): - League(368876, 2018) + ''' Test for switching to fallback API endpoint for private leagues, rando, incorrect cookies used''' + league = League(368876, 2018, 'AEF1234567890ABCDE1234567890ABCD', '{D0C25A4C-2A0D-4E56-8E7F-20A10B663272}') + + self.assertEqual(league.espn_request.LEAGUE_ENDPOINT, "https://lm-api-reads.fantasy.espn.com/apis/v3/games/ffl/leagueHistory/36887?seasonId=2018") def test_unknown_league(self): with self.assertRaises(Exception): From 1376c7881ab0becf2c8442246dbda294b7e56d58 Mon Sep 17 00:00:00 2001 From: Robert Litts Date: Mon, 6 Jan 2025 20:42:08 -0500 Subject: [PATCH 13/14] modified test_private_league to verify league switched to fallback URL -- fixed typo in comment --- tests/football/integration/test_league.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/football/integration/test_league.py b/tests/football/integration/test_league.py index d9f79b13..2359c99b 100644 --- a/tests/football/integration/test_league.py +++ b/tests/football/integration/test_league.py @@ -15,7 +15,7 @@ def test_past_league(self): self.assertEqual(league.nfl_week, 18) def test_private_league(self): - ''' Test for switching to fallback API endpoint for private leagues, rando, incorrect cookies used''' + ''' Test for switching to fallback API endpoint for private leagues. Random, incorrect cookies used to force fallback. ''' league = League(368876, 2018, 'AEF1234567890ABCDE1234567890ABCD', '{D0C25A4C-2A0D-4E56-8E7F-20A10B663272}') self.assertEqual(league.espn_request.LEAGUE_ENDPOINT, "https://lm-api-reads.fantasy.espn.com/apis/v3/games/ffl/leagueHistory/36887?seasonId=2018") From a7178486f22db6723563767350acf0c9e5e66e02 Mon Sep 17 00:00:00 2001 From: Robert Litts Date: Mon, 6 Jan 2025 20:47:06 -0500 Subject: [PATCH 14/14] fixed typo in LEAGUE_ENDPOINT URL in assert equal test --- tests/football/integration/test_league.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/football/integration/test_league.py b/tests/football/integration/test_league.py index 2359c99b..bf4113b4 100644 --- a/tests/football/integration/test_league.py +++ b/tests/football/integration/test_league.py @@ -18,7 +18,7 @@ def test_private_league(self): ''' Test for switching to fallback API endpoint for private leagues. Random, incorrect cookies used to force fallback. ''' league = League(368876, 2018, 'AEF1234567890ABCDE1234567890ABCD', '{D0C25A4C-2A0D-4E56-8E7F-20A10B663272}') - self.assertEqual(league.espn_request.LEAGUE_ENDPOINT, "https://lm-api-reads.fantasy.espn.com/apis/v3/games/ffl/leagueHistory/36887?seasonId=2018") + self.assertEqual(league.espn_request.LEAGUE_ENDPOINT, "https://lm-api-reads.fantasy.espn.com/apis/v3/games/ffl/leagueHistory/368876?seasonId=2018") def test_unknown_league(self): with self.assertRaises(Exception):