Pagination¶
Many IFPA API endpoints support pagination to handle large result sets. This guide covers pagination patterns, best practices, and known API limitations.
Pagination Overview¶
Pagination allows you to retrieve large datasets in smaller, manageable chunks:
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse
client: IfpaClient = IfpaClient()
# Get first 100 rankings
page1: RankingsResponse = client.rankings.wppr(start_pos=0, count=100)
# Get next 100 rankings
page2: RankingsResponse = client.rankings.wppr(start_pos=100, count=100)
Common Pagination Parameters¶
start_pos(oroffset): Starting position (0-based index)count(orlimit): Number of results to return
Rankings Pagination¶
Rankings endpoints have the most reliable pagination support.
Basic Rankings Pagination¶
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse
client: IfpaClient = IfpaClient()
# Get top 100 players
rankings: RankingsResponse = client.rankings.wppr(start_pos=0, count=100)
print(f"Total rankings: {rankings.total_count}")
print(f"Retrieved: {len(rankings.rankings)} rankings")
for entry in rankings.rankings[:10]:
print(f"{entry.rank}. {entry.player_name}: {entry.wppr_points} WPPR")
Iterating Through Pages¶
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse, RankingEntry
client: IfpaClient = IfpaClient()
def get_top_n_rankings(max_results: int = 500) -> list[RankingEntry]:
"""Get top N rankings using pagination.
Args:
max_results: Maximum number of rankings to retrieve
Returns:
List of ranking entries with full type safety
"""
all_rankings: list[RankingEntry] = []
start_pos: int = 0
page_size: int = 100 # Fetch 100 at a time
while len(all_rankings) < max_results:
# Calculate how many to fetch this iteration
remaining: int = max_results - len(all_rankings)
fetch_count: int = min(page_size, remaining)
# Fetch page
page: RankingsResponse = client.rankings.wppr(
start_pos=start_pos,
count=fetch_count
)
# Add to results
all_rankings.extend(page.rankings)
# Check if we got fewer results than requested (end of data)
if len(page.rankings) < fetch_count:
break
# Move to next page
start_pos += fetch_count
return all_rankings
# Get top 500 players
top_500: list[RankingEntry] = get_top_n_rankings(500)
print(f"Retrieved {len(top_500)} rankings")
Country-Specific Rankings¶
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse
client: IfpaClient = IfpaClient()
# Get top 100 US players
us_rankings: RankingsResponse = client.rankings.wppr(
country="US",
start_pos=0,
count=100
)
# Get next page of US players
us_rankings_page2: RankingsResponse = client.rankings.wppr(
country="US",
start_pos=100,
count=100
)
print(f"US Rankings - Page 1: {len(us_rankings.rankings)} players")
print(f"US Rankings - Page 2: {len(us_rankings_page2.rankings)} players")
Women's and Youth Rankings¶
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse
client: IfpaClient = IfpaClient()
# Women's rankings with pagination
women_page1: RankingsResponse = client.rankings.women(
start_pos=0,
count=50
)
women_page2: RankingsResponse = client.rankings.women(
start_pos=50,
count=50
)
# Youth rankings with pagination
youth_page1: RankingsResponse = client.rankings.youth(
start_pos=0,
count=50
)
youth_page2: RankingsResponse = client.rankings.youth(
start_pos=50,
count=50
)
Query Builder Pagination¶
The Query Builder pattern supports pagination through .offset() and .limit() methods.
Player Query Pagination¶
from ifpa_api import IfpaClient
from ifpa_api.models.player import PlayerSearchResponse
client: IfpaClient = IfpaClient()
# First page - players named "Smith" in the US
page1: PlayerSearchResponse = (client.player.query("Smith")
.country("US")
.offset(0)
.limit(25)
.get())
Director Query Pagination¶
from ifpa_api import IfpaClient
from ifpa_api.models.director import DirectorSearchResponse
client: IfpaClient = IfpaClient()
# First page of US directors
page1: DirectorSearchResponse = (client.director.query()
.country("US")
.offset(0)
.limit(50)
.get())
# Second page
page2: DirectorSearchResponse = (client.director.query()
.country("US")
.offset(50)
.limit(50)
.get())
print(f"Page 1: {len(page1.directors)} directors")
print(f"Page 2: {len(page2.directors)} directors")
Tournament Query Pagination¶
from ifpa_api import IfpaClient
from ifpa_api.models.tournaments import TournamentSearchResponse
client: IfpaClient = IfpaClient()
# First page of 2024 tournaments
page1: TournamentSearchResponse = (client.tournament.query()
.date_range("2024-01-01", "2024-12-31")
.country("US")
.offset(0)
.limit(100)
.get())
# Second page
page2: TournamentSearchResponse = (client.tournament.query()
.date_range("2024-01-01", "2024-12-31")
.country("US")
.offset(100)
.limit(100)
.get())
print(f"Page 1: {len(page1.tournaments)} tournaments")
print(f"Page 2: {len(page2.tournaments)} tournaments")
Paginating with Query Reuse¶
Take advantage of immutable queries for cleaner pagination:
from ifpa_api import IfpaClient
from ifpa_api.models.director import DirectorSearchResponse, DirectorQueryBuilder
client: IfpaClient = IfpaClient()
# Create base query
us_directors_query: DirectorQueryBuilder = client.director.query().country("US").limit(50)
# Fetch multiple pages from same base query
page1: DirectorSearchResponse = us_directors_query.offset(0).get()
page2: DirectorSearchResponse = us_directors_query.offset(50).get()
page3: DirectorSearchResponse = us_directors_query.offset(100).get()
total_results: int = len(page1.directors) + len(page2.directors) + len(page3.directors)
print(f"Total results: {total_results}")
Helper Functions for Pagination¶
Generic Pagination Iterator¶
from typing import TypeVar, Generic, Callable
from ifpa_api import IfpaClient
T = TypeVar('T')
def paginate_all(
fetch_page: Callable[[int, int], list[T]],
page_size: int = 100,
max_results: int | None = None
) -> list[T]:
"""Generic pagination helper with type safety.
Args:
fetch_page: Function that takes (start_pos, count) and returns list of items
page_size: Number of items per page
max_results: Maximum total items to fetch (None for all)
Returns:
All items across all pages
"""
all_items: list[T] = []
start_pos: int = 0
while True:
# Calculate how many to fetch
if max_results is not None:
remaining: int = max_results - len(all_items)
if remaining <= 0:
break
fetch_count: int = min(page_size, remaining)
else:
fetch_count: int = page_size
# Fetch page
items: list[T] = fetch_page(start_pos, fetch_count)
# No more results
if not items:
break
all_items.extend(items)
# Got fewer than requested - end of data
if len(items) < fetch_count:
break
start_pos += fetch_count
return all_items
# Usage example
client: IfpaClient = IfpaClient()
def fetch_wppr_page(start_pos: int, count: int) -> list:
"""Fetch a page of WPPR rankings."""
response = client.rankings.wppr(start_pos=start_pos, count=count)
return response.rankings
# Get all top 1000 players
top_1000 = paginate_all(fetch_wppr_page, page_size=100, max_results=1000)
print(f"Fetched {len(top_1000)} rankings")
Rankings Pagination Helper¶
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse, RankingEntry
client: IfpaClient = IfpaClient()
def get_all_wppr_rankings(
max_rank: int = 1000,
page_size: int = 250,
country: str | None = None
) -> list[RankingEntry]:
"""Fetch WPPR rankings up to a maximum rank.
Args:
max_rank: Maximum rank position to fetch
page_size: Number of rankings per page (max 250)
country: Optional country filter
Returns:
List of ranking entries with type safety
"""
all_rankings: list[RankingEntry] = []
start_pos: int = 0
while start_pos < max_rank:
# Calculate how many to fetch
remaining: int = max_rank - start_pos
fetch_count: int = min(page_size, remaining)
# Fetch page with optional country filter
if country:
page: RankingsResponse = client.rankings.wppr(
country=country,
start_pos=start_pos,
count=fetch_count
)
else:
page: RankingsResponse = client.rankings.wppr(
start_pos=start_pos,
count=fetch_count
)
# Add to results
all_rankings.extend(page.rankings)
# Check if we're done
if len(page.rankings) < fetch_count:
break
start_pos += fetch_count
return all_rankings
# Get top 500 worldwide
top_500_world: list[RankingEntry] = get_all_wppr_rankings(max_rank=500)
# Get top 200 US players
top_200_us: list[RankingEntry] = get_all_wppr_rankings(max_rank=200, country="US")
print(f"Worldwide: {len(top_500_world)} players")
print(f"US: {len(top_200_us)} players")
Known Limitations¶
Player Results Pagination (Ignored)¶
The player results endpoint accepts start_pos and count parameters but completely ignores them:
from ifpa_api import IfpaClient
from ifpa_api.models.player import PlayerResultsResponse
from ifpa_api.models.common import RankingSystem, ResultType
client: IfpaClient = IfpaClient()
# These pagination parameters are IGNORED by the API
results: PlayerResultsResponse = client.player(25584).results(
ranking_system=RankingSystem.MAIN,
result_type=ResultType.ACTIVE
# Note: API ignores start_pos and count parameters completely
# Always returns ALL results regardless of pagination parameters
)
# API always returns ALL results regardless of pagination parameters
print(f"Total results: {results.total_results}")
print(f"Actual results returned: {len(results.results)}")
# These will be the same number
Workaround: Handle pagination client-side:
from ifpa_api import IfpaClient
from ifpa_api.models.player import PlayerResultsResponse, PlayerResult
from ifpa_api.models.common import RankingSystem, ResultType
client: IfpaClient = IfpaClient()
def get_player_results_page(
player_id: int,
ranking_system: RankingSystem,
result_type: ResultType,
page: int = 0,
page_size: int = 50
) -> list[PlayerResult]:
"""Get paginated player results (client-side pagination).
Args:
player_id: Player identifier
ranking_system: Ranking system filter
result_type: Result type filter
page: Page number (0-based)
page_size: Results per page
Returns:
List of results for the requested page
"""
# Fetch all results
all_results: PlayerResultsResponse = client.player(player_id).results(
ranking_system=ranking_system,
result_type=result_type
)
# Calculate slice indices
start_idx: int = page * page_size
end_idx: int = start_idx + page_size
# Return page slice
page_results: list[PlayerResult] = all_results.results[start_idx:end_idx]
return page_results
# Get first page (results 0-49)
page1: list[PlayerResult] = get_player_results_page(
player_id=25584,
ranking_system=RankingSystem.MAIN,
result_type=ResultType.ACTIVE,
page=0,
page_size=50
)
# Get second page (results 50-99)
page2: list[PlayerResult] = get_player_results_page(
player_id=25584,
ranking_system=RankingSystem.MAIN,
result_type=ResultType.ACTIVE,
page=1,
page_size=50
)
Series Standings Pagination (Ignored)¶
Series standings endpoints also ignore pagination parameters:
from ifpa_api import IfpaClient
from ifpa_api.models.series import SeriesStandingsResponse
client: IfpaClient = IfpaClient()
# These parameters do nothing
standings: SeriesStandingsResponse = client.series("NACS").standings(
start_pos=0,
count=50
)
# API returns all standings regardless
print(f"Results: {len(standings.overall_results)}")
Workaround: Use client-side slicing if needed:
from ifpa_api import IfpaClient
from ifpa_api.models.series import SeriesStandingsResponse
client: IfpaClient = IfpaClient()
# Fetch all results
all_standings: SeriesStandingsResponse = client.series("NACS").standings()
# Slice client-side
page_size: int = 10
page_1 = all_standings.overall_results[0:page_size]
page_2 = all_standings.overall_results[page_size:page_size*2]
Best Practices¶
1. Always Use Type Hints¶
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse
client: IfpaClient = IfpaClient()
# Good - type hints on all variables
page_size: int = 100
start_pos: int = 0
rankings: RankingsResponse = client.rankings.wppr(
start_pos=start_pos,
count=page_size
)
# Bad - no type hints
page_size = 100
start_pos = 0
rankings = client.rankings.wppr(start_pos=start_pos, count=page_size)
2. Set Reasonable Page Sizes¶
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse
client: IfpaClient = IfpaClient()
# Good - reasonable page size (100-250)
rankings: RankingsResponse = client.rankings.wppr(start_pos=0, count=100)
# Bad - too small (many API calls)
rankings: RankingsResponse = client.rankings.wppr(start_pos=0, count=10)
# Bad - too large (may timeout or fail)
rankings: RankingsResponse = client.rankings.wppr(start_pos=0, count=10000)
3. Check for End of Data¶
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse
client: IfpaClient = IfpaClient()
page_size: int = 100
start_pos: int = 0
while True:
page: RankingsResponse = client.rankings.wppr(
start_pos=start_pos,
count=page_size
)
# Process results
print(f"Fetched {len(page.rankings)} rankings starting at {start_pos}")
# Check if we're done
if len(page.rankings) < page_size:
print("Reached end of data")
break
start_pos += page_size
4. Handle Rate Limits¶
import time
from ifpa_api import IfpaClient
from ifpa_api.core.exceptions import IfpaApiError
from ifpa_api.models.rankings import RankingsResponse
client: IfpaClient = IfpaClient()
def fetch_with_rate_limiting(
start_pos: int,
count: int,
delay_seconds: float = 0.5
) -> RankingsResponse:
"""Fetch rankings with rate limiting.
Args:
start_pos: Starting position
count: Number of results
delay_seconds: Delay between requests
Returns:
Rankings response
"""
try:
rankings: RankingsResponse = client.rankings.wppr(
start_pos=start_pos,
count=count
)
time.sleep(delay_seconds) # Be kind to the API
return rankings
except IfpaApiError as e:
if e.status_code == 429: # Rate limit exceeded
print(f"Rate limited, waiting 5 seconds...")
time.sleep(5)
return fetch_with_rate_limiting(start_pos, count, delay_seconds)
raise
5. Implement Progress Tracking¶
from ifpa_api import IfpaClient
from ifpa_api.models.rankings import RankingsResponse, RankingEntry
client: IfpaClient = IfpaClient()
def fetch_rankings_with_progress(
max_results: int = 1000,
page_size: int = 100
) -> list[RankingEntry]:
"""Fetch rankings with progress tracking.
Args:
max_results: Maximum results to fetch
page_size: Results per page
Returns:
List of ranking entries
"""
all_rankings: list[RankingEntry] = []
start_pos: int = 0
while start_pos < max_results:
# Calculate fetch count
remaining: int = max_results - start_pos
fetch_count: int = min(page_size, remaining)
# Fetch page
page: RankingsResponse = client.rankings.wppr(
start_pos=start_pos,
count=fetch_count
)
all_rankings.extend(page.rankings)
# Progress update
progress: float = (len(all_rankings) / max_results) * 100
print(f"Progress: {len(all_rankings)}/{max_results} ({progress:.1f}%)")
# Check if done
if len(page.rankings) < fetch_count:
break
start_pos += fetch_count
return all_rankings
rankings: list[RankingEntry] = fetch_rankings_with_progress(max_results=500)
Summary of Pagination Support¶
| Endpoint | Pagination Support | Notes |
|---|---|---|
| Rankings (WPPR, Women, Youth, etc.) | ✅ Full support | start_pos and count work correctly |
| Director Search (Query Builder) | ✅ Full support | .offset() and .limit() work correctly |
| Tournament Search (Query Builder) | ✅ Full support | .offset() and .limit() work correctly |
| Player Search (Query Builder) | ✅ Full support | .offset() and .limit() work correctly |
| Player Results | ❌ Ignored | API ignores pagination params - returns all results |
| Series Standings | ❌ Ignored | API ignores pagination params - returns all results |
| Series Region Standings | ✅ Partial support | start_pos and count accepted but may not be reliable |
Related Resources¶
- Searching Guide - Query Builder pattern
- Callable Pattern Guide - Individual resource operations
- Rankings Resource - Complete rankings API reference
- Player Resource - Complete player API reference
- Error Handling - Handle API errors