Error Handling
Learn how to handle errors gracefully when working with the SwimRankings library.
Exception Hierarchy
The SwimRankings library provides a clear hierarchy of exceptions:
SwimRankingsError (base exception)
├── NetworkError
├── AthleteNotFoundError
├── InvalidGenderError
└── ParseError
Common Exceptions
AthleteNotFoundError
Raised when no athletes are found for the given search criteria.
from swimrankings import Athletes, AthleteNotFoundError
try:
athletes = Athletes(name="NonexistentName")
except AthleteNotFoundError as e:
print(f"No athletes found: {e}")
Common causes:
- Misspelled athlete name
- Very uncommon name
- Restrictive gender filter
NetworkError
Raised when there's a network-related issue.
from swimrankings import Athletes, NetworkError
try:
athletes = Athletes(name="Smith")
except NetworkError as e:
print(f"Network error: {e}")
print("Please check your internet connection")
Common causes:
- No internet connection
- Server is temporarily down
- Request timeout
- DNS resolution failure
- Firewall blocking requests
InvalidGenderError
Raised when an invalid gender value is provided.
from swimrankings import Athletes, InvalidGenderError
try:
athletes = Athletes(name="Smith", gender="invalid")
except InvalidGenderError as e:
print(f"Invalid gender: {e}")
Valid gender values: "all"
, "male"
, "female"
ParseError
Raised when the HTML response cannot be parsed properly.
from swimrankings import Athletes, ParseError
try:
athletes = Athletes(name="Smith")
except ParseError as e:
print(f"Parse error: {e}")
print("The website might have changed its format")
Common causes:
- Website structure changes
- Unexpected HTML format
- Corrupted response data
Best Practices
Comprehensive Error Handling
Always handle the most specific exceptions first:
from swimrankings import (
Athletes,
AthleteNotFoundError,
NetworkError,
InvalidGenderError,
ParseError,
SwimRankingsError
)
def search_athletes_safely(name, gender="all"):
try:
athletes = Athletes(name=name, gender=gender)
return list(athletes)
except InvalidGenderError as e:
print(f"❌ Invalid gender parameter: {e}")
return []
except AthleteNotFoundError:
print(f"🔍 No athletes found for '{name}' with gender '{gender}'")
return []
except NetworkError as e:
print(f"🌐 Network error: {e}")
print("Please check your internet connection and try again.")
return []
except ParseError as e:
print(f"⚠️ Parse error: {e}")
print("The website format might have changed.")
return []
except SwimRankingsError as e:
print(f"❌ SwimRankings error: {e}")
return []
except Exception as e:
print(f"💥 Unexpected error: {e}")
return []
# Usage
athletes = search_athletes_safely("Smith", "male")
if athletes:
print(f"✅ Found {len(athletes)} athletes")
Retry Logic for Network Errors
Implement retry logic for transient network issues:
import time
from swimrankings import Athletes, NetworkError
def search_with_retry(name, max_retries=3, base_delay=1):
"""Search with exponential backoff retry."""
last_error = None
for attempt in range(max_retries):
try:
athletes = Athletes(name=name)
return list(athletes)
except NetworkError as e:
last_error = e
if attempt == max_retries - 1:
break
delay = base_delay * (2 ** attempt) # Exponential backoff
print(f"Network error (attempt {attempt + 1}/{max_retries}): {e}")
print(f"Retrying in {delay} seconds...")
time.sleep(delay)
raise NetworkError(f"Failed after {max_retries} attempts. Last error: {last_error}")
# Usage
try:
athletes = search_with_retry("Smith")
print(f"Success! Found {len(athletes)} athletes")
except NetworkError as e:
print(f"Failed to search: {e}")
Timeout Configuration
Configure timeouts based on your needs:
from swimrankings import Athletes, NetworkError
# Short timeout for quick checks
try:
athletes = Athletes(name="Smith", timeout=5)
except NetworkError:
print("Quick search failed, trying with longer timeout...")
# Longer timeout for slower connections
try:
athletes = Athletes(name="Smith", timeout=30)
except NetworkError:
print("Search failed even with extended timeout")
Graceful Degradation
Handle partial failures gracefully:
from swimrankings import Athletes, AthleteNotFoundError, NetworkError
def search_multiple_names(names):
"""Search for multiple names, handling failures gracefully."""
results = {}
for name in names:
try:
athletes = Athletes(name=name)
results[name] = list(athletes)
print(f"✅ {name}: {len(athletes)} athletes")
except AthleteNotFoundError:
results[name] = []
print(f"🔍 {name}: No athletes found")
except NetworkError as e:
results[name] = None
print(f"🌐 {name}: Network error - {e}")
except Exception as e:
results[name] = None
print(f"❌ {name}: Unexpected error - {e}")
return results
# Usage
names = ["Smith", "Johnson", "Williams"]
results = search_multiple_names(names)
# Process results
successful_searches = [name for name, athletes in results.items() if athletes is not None]
print(f"Successful searches: {len(successful_searches)}")
Logging Errors
Use Python's logging module for better error tracking:
import logging
from swimrankings import Athletes, SwimRankingsError
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('swimrankings.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def search_with_logging(name, gender="all"):
"""Search with comprehensive logging."""
logger.info(f"Starting search for '{name}' with gender '{gender}'")
try:
athletes = Athletes(name=name, gender=gender)
athlete_list = list(athletes)
logger.info(f"Search successful: found {len(athlete_list)} athletes")
return athlete_list
except SwimRankingsError as e:
logger.error(f"SwimRankings error during search: {e}")
return []
except Exception as e:
logger.exception(f"Unexpected error during search: {e}")
return []
# Usage
athletes = search_with_logging("Smith", "male")
Testing Error Conditions
When writing tests, you can simulate error conditions:
import pytest
from unittest.mock import patch
from swimrankings import Athletes, NetworkError
def test_network_error_handling():
"""Test that network errors are handled properly."""
with patch('swimrankings.athletes.requests.get') as mock_get:
mock_get.side_effect = Exception("Network error")
with pytest.raises(NetworkError):
Athletes(name="Test")
def test_invalid_gender():
"""Test invalid gender parameter."""
with pytest.raises(InvalidGenderError):
Athletes(name="Test", gender="invalid")
Debugging Tips
Enable Verbose Error Messages
import requests
from swimrankings import Athletes
# Enable requests logging for debugging
import logging
logging.basicConfig(level=logging.DEBUG)
try:
athletes = Athletes(name="Test")
except Exception as e:
print(f"Error details: {e}")
print(f"Error type: {type(e)}")
Check Response Content
For debugging parse errors:
import requests
from bs4 import BeautifulSoup
# Manual request to debug
url = "https://www.swimrankings.net/index.php"
params = {
'internalRequest': 'athleteFind',
'athlete_clubId': -1,
'athlete_gender': -1,
'athlete_lastname': 'TestName',
'athlete_firstname': ''
}
response = requests.get(url, params=params)
print(f"Status code: {response.status_code}")
print(f"Response length: {len(response.text)}")
print(f"First 500 chars: {response.text[:500]}")
# Check if table exists
soup = BeautifulSoup(response.text, 'lxml')
table = soup.find('table', class_='athleteSearch')
print(f"Table found: {table is not None}")
Recovery Strategies
Fallback to Different Parameters
from swimrankings import Athletes, AthleteNotFoundError
def flexible_search(name):
"""Try different search strategies."""
strategies = [
{"gender": "all"},
{"gender": "male"},
{"gender": "female"},
]
for strategy in strategies:
try:
athletes = Athletes(name=name, **strategy)
print(f"Found {len(athletes)} athletes with strategy: {strategy}")
return list(athletes)
except AthleteNotFoundError:
continue
print(f"No athletes found for '{name}' with any strategy")
return []
Partial Name Matching
from swimrankings import Athletes, AthleteNotFoundError
def smart_search(full_name):
"""Try searching with different parts of the name."""
name_parts = full_name.split()
for part in name_parts:
if len(part) >= 3: # Only try meaningful parts
try:
athletes = Athletes(name=part)
# Filter results to match the original search intent
matching = [
athlete for athlete in athletes
if any(name_part.lower() in athlete.full_name.lower()
for name_part in name_parts)
]
if matching:
return matching
except AthleteNotFoundError:
continue
return []
# Usage
athletes = smart_search("John Smith")