Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
[0.3.0] - 2025-11-18¶
Breaking Changes - Field Name Alignment¶
SDK Field Names Now Match Exact API Field Names
To improve transparency and reduce confusion when comparing SDK responses with API documentation, all response model field names now exactly match the IFPA API field names. This is a breaking change that affects how you access certain fields in player and tournament results.
Player Model Changes (TournamentResult in player results/history):
| Old Field Name (v0.2.x) | New Field Name (v0.3.0) | Description |
|---|---|---|
wppr_points |
current_points |
WPPR points from tournament result |
# Before (v0.2.x)
results = client.player(12345).results(RankingSystem.MAIN, ResultType.ACTIVE)
for result in results.results:
print(f"WPPR: {result.wppr_points}")
# After (v0.3.0)
results = client.player(12345).results(RankingSystem.MAIN, ResultType.ACTIVE)
for result in results.results:
print(f"WPPR: {result.current_points}")
Tournament Model Changes (TournamentResult in tournament results):
| Old Field Name (v0.2.x) | New Field Name (v0.3.0) | Description |
|---|---|---|
wppr_points |
points |
WPPR points earned in tournament |
rating_points |
ratings_value |
Rating value earned in tournament |
total_events |
player_tournament_count |
Total tournaments player has participated in |
# Before (v0.2.x)
results = client.tournament(12345).results()
for result in results.results:
print(f"Position: {result.position}")
print(f"WPPR: {result.wppr_points}")
print(f"Rating: {result.rating_points}")
print(f"Total Events: {result.total_events}")
# After (v0.3.0)
results = client.tournament(12345).results()
for result in results.results:
print(f"Position: {result.position}")
print(f"WPPR: {result.points}")
print(f"Rating: {result.ratings_value}")
print(f"Total Events: {result.player_tournament_count}")
Why This Change?
- Transparency: Field names now exactly match what the API returns
- Debugging: Easier to compare SDK responses with API documentation
- Consistency: Aligns with the API specification
- Clarity: No more confusion about SDK field name mappings
Migration Checklist:
- Search your codebase for
result.wppr_pointsin player results → Replace withresult.current_points - Search your codebase for
result.wppr_pointsin tournament results → Replace withresult.points - Search your codebase for
result.rating_pointsin tournament results → Replace withresult.ratings_value - Search your codebase for
result.total_eventsin tournament results → Replace withresult.player_tournament_count - Run your tests to identify any remaining references
- Update any documentation or comments referencing the old field names
Added¶
Quality of Life Improvements
-
Enhanced Error Messages - All API errors now include request context:
-
Pagination Helpers - Automatic pagination for large result sets:
-
Semantic Exceptions - Clear, specific errors for common scenarios:
from ifpa_api.exceptions import SeriesPlayerNotFoundError, TournamentNotLeagueError try: card = client.series("PAPA").player_card(12345, "OH") except SeriesPlayerNotFoundError as e: print(f"Player {e.player_id} has no results in {e.series_code}") try: league = client.tournament(12345).league() except TournamentNotLeagueError as e: print(f"Tournament {e.tournament_id} is not a league") -
Better Validation Messages - Helpful hints for common validation errors:
Query Builder Pattern (NEW)
All resources with search/query capabilities now support a fluent Query Builder pattern for composable, type-safe filtering:
# Player queries
results = client.player.query("John").country("US").state("WA").limit(25).get()
# Director queries
results = client.director.query("Josh").city("Seattle").state("WA").get()
# Tournament queries
results = client.tournament.query("PAPA").country("US").date_range("2024-01-01", "2024-12-31").get()
# Query reuse (immutable pattern)
us_query = client.player.query().country("US")
wa_players = us_query.state("WA").get()
or_players = us_query.state("OR").get() # base query unchanged!
Base Classes & Infrastructure
BaseResourceContext[T]- Generic base class for resource contextsBaseResourceClient- Base class for all resource client classesLocationFiltersMixin- Adds.country(),.state(),.city()filteringPaginationMixin- Adds.limit()and.offset()pagination- Eliminated ~550 lines of duplicated code through DRY principles
Package Restructuring
Resources now organized into focused packages:
- player/ - client.py, context.py, query_builder.py
- director/ - client.py, context.py, query_builder.py
- tournament/ - client.py, context.py, query_builder.py
- series/ - client.py, context.py
Reference Resource (from 0.2.1):
- ReferenceClient.countries() - Get list of all countries in IFPA system (62 countries)
- ReferenceClient.state_provs() - Get states/provinces by country (67 regions across AU, CA, US)
Rankings Resource Additions (from 0.2.1):
- RankingsClient.country_list() - List all countries with player counts (51 countries)
- RankingsClient.custom_list() - List all custom ranking systems (84 custom rankings)
Tournaments Resource Additions (from 0.2.1):
- TournamentClient.list_formats() - List all tournament format types (25 formats)
Exceptions (from 0.2.1):
- PlayersNeverMetError - Custom exception for when PVP data unavailable between two players who have never competed together
Changed¶
Tournament Resource Refactoring (BREAKING CHANGES)
The Tournament resource has been refactored to match the callable pattern established by Player and Director resources, providing a unified interface for collection and resource-level operations.
Breaking Changes:
-
Property renamed:
client.tournaments→client.tournament(singular) -
Method renamed:
.get()→.details()(consistent with Player and Director) -
Class renamed:
TournamentsClient→TournamentClient(singular) -
Class removed:
TournamentHandleis now private (_TournamentContext) - This internal class should not be directly imported by users
- Access tournament operations via the callable pattern:
client.tournament(id).details()
Series Resource Refactoring (BREAKING CHANGES)
The Series resource has been migrated to use the callable pattern, completing the unification effort across all resources.
Breaking Changes:
-
Method removed:
client.series_handle()method removed -
Class renamed:
SeriesHandleis now private (_SeriesContext) - This internal class should not be directly imported by users
- Access series operations via the callable pattern:
client.series("CODE").standings()
Unified Pattern Across All Resources:
All resources now follow the same callable pattern:
# Player
client.player(123).details()
client.player.query("John").country("US").get()
# Director
client.director(456).details()
client.director.query("Josh").city("Seattle").get()
# Tournament
client.tournament(789).details()
client.tournament.query("PAPA").country("US").get()
# Series
client.series("NACS").standings()
Fixed¶
From 0.3.0 Development:
- Fixed tournament results validation error when API returns "Not Rated" strings for unrated players (now properly converts to None)
- Consolidated integration tests from 3 files into 1 organized file (test_tournament_integration.py)
- Updated all documentation to reflect new callable pattern and query builder
- Fixed helper function get_test_tournament_id() to use correct callable pattern
From 0.2.2:
- Players Resource:
- Fixed wppr_points field in player results/history always returning None due to incorrect API field mapping (API uses current_points, not wppr_points)
- Added missing optional fields to player tournament results: all_time_points, active_points, inactive_points
- Tournaments Resource:
- Fixed
wppr_pointsfield mapping in tournament results (API usespoints, notwppr_points) - Fixed
rating_pointsfield mapping in tournament results (API usesratings_value, notrating_points) - Fixed
total_eventsfield mapping in tournament results (API usesplayer_tournament_count, nottotal_events) - Added missing
wppr_rankfield to tournament results
From 0.2.1:
- Rankings Resource:
- Fixed age field validation to handle empty string values returned by API
- Fixed by_country() response model to correctly return player rankings (was incorrectly expecting country-level statistics)
- Directors Resource:
-
Fixed
country_directors()to handle nestedplayer_profilestructure in API response -
Tournaments Resource:
- Fixed search response to correctly map API's
searchkey to model'stournamentsfield -
Added validation requiring both
start_dateandend_dateparameters together (API returns 400 if only one provided) -
Series Resource:
- Fixed
standings()to call correct/series/{code}/overall_standingsendpoint (was calling wrong endpoint) -
Added required
region_codeparameter toregions(),stats(), andtournaments()methods -
Players Resource:
- Improved
pvp()error handling with clearer exception for players who have never competed together
Removed¶
From 0.2.1:
- Series Resource:
- Removed overview() method (endpoint does not exist in API)
- Removed rules() method (endpoint does not exist in API)
- Removed schedule() method (endpoint does not exist in API)
Testing¶
- 473 comprehensive tests across unit and integration suites
- Enhanced integration tests to validate actual field values instead of just field existence
- Updated unit test mocks to reflect actual API response structure
- All tests passing with 2 skipped due to known API issues
- Maintained 99% code coverage
Migration Guide¶
Simple find-replace for your codebase:
- client.tournaments → client.tournament
- client.series_handle( → client.series(
- .search( → .query().get() (or use fluent filters)
Query Builder Migration:
# Before (v0.2.x) - positional search
results = client.player.search(name="John")
# After (v0.3.0) - fluent query builder
results = client.player.query("John").get()
# With additional filters
results = client.player.query("John").country("US").state("WA").limit(25).get()
0.2.2 - 2025-11-18¶
Fixed¶
- Players Resource:
- Fixed
wppr_pointsfield in player results/history always returning None due to incorrect API field mapping (API usescurrent_points, notwppr_points) -
Added missing optional fields to player tournament results:
all_time_points,active_points,inactive_points -
Tournaments Resource:
- Fixed
wppr_pointsfield mapping in tournament results (API usespoints, notwppr_points) - Fixed
rating_pointsfield mapping in tournament results (API usesratings_value, notrating_points) - Fixed
total_eventsfield mapping in tournament results (API usesplayer_tournament_count, nottotal_events) - Added missing
wppr_rankfield to tournament results
Testing¶
- Enhanced integration tests to validate actual field values instead of just field existence
- Updated unit test mocks to reflect actual API response structure
- Skipped unreliable
stateprovfilter tests for both Player and Director search (API returns incorrect states) - All 212 integration tests passing (with 2 skipped due to known API issues)
0.2.1 - 2025-11-16¶
Added¶
Phase 2 - New Features & Enhanced API Coverage
- Reference Resource (NEW):
ReferenceClient.countries()- Get list of all countries in IFPA system (62 countries)-
ReferenceClient.state_provs()- Get states/provinces by country (67 regions across AU, CA, US) -
Rankings Resource:
RankingsClient.country_list()- List all countries with player counts (51 countries)-
RankingsClient.custom_list()- List all custom ranking systems (84 custom rankings) -
Tournaments Resource:
TournamentHandle.related()- Get related tournaments (same venue or series)-
TournamentsClient.list_formats()- List all tournament format types (25 formats) -
Exceptions:
PlayersNeverMetError- Custom exception for when PVP data unavailable between two players who have never competed together
Fixed¶
- Rankings Resource:
- Fixed age field validation to handle empty string values returned by API
-
Fixed
by_country()response model to correctly return player rankings (was incorrectly expecting country-level statistics) -
Directors Resource:
-
Fixed
country_directors()to handle nestedplayer_profilestructure in API response -
Tournaments Resource:
- Fixed search response to correctly map API's
searchkey to model'stournamentsfield -
Added validation requiring both
start_dateandend_dateparameters together (API returns 400 if only one provided) -
Series Resource:
- Fixed
standings()to call correct/series/{code}/overall_standingsendpoint (was calling wrong endpoint) -
Added required
region_codeparameter toregions(),stats(), andtournaments()methods -
Players Resource:
- Improved
pvp()error handling with clearer exception for players who have never competed together
Removed¶
- Series Resource:
- Removed
overview()method (endpoint does not exist in API) - Removed
rules()method (endpoint does not exist in API) - Removed
schedule()method (endpoint does not exist in API)
Testing¶
- Added 154 comprehensive integration tests across all 6 resources
- Increased code coverage from 95% to 97%
- All fixes verified against live IFPA API
- Test pass rate improved from 52% to 100% (unit tests)
- Integration test pass rate: 88% (180/209 tests passing)
0.2.0 - 2025-11-14¶
Added¶
PlayersClient.get_multiple()method to fetch up to 50 players in a single requestPlayerHandle.pvp_all()method to get PVP competitor summarytournamentparameter toPlayersClient.search()for filtering by tournament nametourposparameter toPlayersClient.search()for filtering by tournament position
Changed¶
- BREAKING:
PlayerHandle.results()now requires bothranking_systemandresult_typeparameters (were optional) - BREAKING:
PlayerHandle.history()response structure now has separaterank_historyandrating_historyarrays (was singlehistoryarray) - BREAKING:
RankingHistorymodel field renamed:ranking_system→system - BREAKING:
RankingHistorymodel now hasactive_flagfield - Fixed critical bug:
PlayersClient.search()parameter mapping (name now correctly maps to "name" query parameter)
Removed¶
- BREAKING:
PlayerHandle.rankings()method (endpoint returns 404 - not in API spec) - BREAKING:
PlayerHandle.cards()method (endpoint returns 404 - not in API spec)
Migration Guide¶
results() Method¶
# Before (v0.1.x)
results = client.player(123).results()
# After (v0.2.0+) - Both parameters required
from ifpa_api.models.common import RankingSystem, ResultType
results = client.player(123).results(
ranking_system=RankingSystem.MAIN,
result_type=ResultType.ACTIVE
)
history() Method¶
# Before (v0.1.x)
history = client.player(123).history()
for entry in history.history:
print(entry.rank, entry.rating)
# After (v0.2.0+) - Separate arrays
history = client.player(123).history()
for entry in history.rank_history:
print(entry.rank_position, entry.wppr_points)
for entry in history.rating_history:
print(entry.rating)
Removed Methods¶
# Before (v0.1.x)
rankings = client.player(123).rankings() # Will raise AttributeError in v0.2.0
cards = client.player(123).cards() # Will raise AttributeError in v0.2.0
# After (v0.2.0+) - No replacement
# Player rankings data is available in the player profile
profile = client.player(123).details()
# Player cards are only available via series: client.series("CODE").player_card(123)
0.1.0 - 2025-11-14¶
Added¶
- Initial release of IFPA SDK
- Complete implementation of IFPA API v2.1 with all 46 endpoints
IfpaClientmain client facade with context manager support- HTTP client layer with session management, authentication, and error handling
- Comprehensive Pydantic models for all API resources:
- Directors models and enums
- Players models and enums
- Rankings models and enums
- Tournaments models and enums
- Series models and enums
- Statistics models and enums
- Calendar models and enums
- Common models and enums (TimePeriod, RankingSystem, ResultType, TournamentType)
- Resource clients for all IFPA API resources:
DirectorClient- Search directors, get country directors, director details, tournaments (callable pattern)PlayersClient- Search players with multiple filtersPlayerHandle- Get player profile, rankings, results, PvP, history, cardsRankingsClient- WPPR, women, youth, virtual, pro, country, age-based, custom, group rankingsTournamentsClient- Search tournaments with filtersTournamentHandle- Get tournament details, results, formats, league infoSeriesClient- List all series or filter to active onlySeriesHandle- Get standings, player cards, overview, regions, rules, stats, scheduleStatsClient- Global stats, top countries, machine popularity- Handle pattern for resource-specific operations with fluent API
- Configuration system with environment variable support
- Custom exception hierarchy:
IfpaError- Base exceptionMissingApiKeyError- No API key provided or foundIfpaApiError- API returned error responseIfpaClientValidationError- Request validation failed- Request validation using Pydantic models (configurable)
- Pagination support for all paginated endpoints
- Comprehensive test suite:
- 180+ unit tests using requests-mock
- 40+ integration tests with real API calls
- 98% code coverage
- Complete type hints throughout the codebase
- Development tools configuration:
- Black formatter (100 char line length)
- Ruff linter with strict rules
- mypy strict type checking
- pre-commit hooks for automated checks
- pytest with coverage reporting
- Project documentation:
- Comprehensive README with examples for all resources
- CONTRIBUTING guide with development workflow
- Complete docstrings on all public APIs
- MkDocs configuration for documentation site
- Poetry-based dependency management
- MIT License
Technical Details¶
- Python Version: 3.11+
- Dependencies: requests, pydantic 2.0+
- API Version: IFPA API v2.1
- Base URL: https://api.ifpapinball.com
- Authentication: X-API-Key header
Supported Endpoints¶
Directors (4 endpoints):
- GET /director/search - Search for tournament directors
- GET /director/{director_id} - Get director details
- GET /director/{director_id}/tournaments/past - Get past tournaments
- GET /director/{director_id}/tournaments/upcoming - Get upcoming tournaments
Players (5 endpoints in v0.1.0):
- GET /player/search - Search for players
- GET /player/{player_id} - Get player profile
- GET /player/{player_id}/pvp/{other_player_id} - Head-to-head comparison
- GET /player/{player_id}/results - Tournament results history
- GET /player/{player_id}/rank_history - WPPR ranking history
Note: v0.1.0 incorrectly included rankings() and cards() methods that mapped to non-existent API endpoints. These were removed in v0.2.0+.
Rankings (9 endpoints):
- GET /rankings/wppr - Main WPPR rankings
- GET /rankings/women - Women's rankings
- GET /rankings/youth - Youth rankings
- GET /rankings/virtual - Virtual tournament rankings
- GET /rankings/pro - Professional circuit rankings
- GET /rankings/country - Country rankings
- GET /rankings/age_based/{age_group} - Age-based rankings
- GET /rankings/custom/{ranking_id} - Custom ranking systems
- GET /rankings/group/{group_id} - Group rankings
Tournaments (6 endpoints):
- GET /tournament/search - Search tournaments
- GET /tournament/{tournament_id} - Get tournament details
- GET /tournament/{tournament_id}/results - Tournament results
- GET /tournament/{tournament_id}/formats - Tournament formats
- GET /tournament/{tournament_id}/league - League information
- GET /tournament/{tournament_id}/submissions - Tournament submissions
Series (8 endpoints):
- GET /series/list - List all series
- GET /series/{series_code}/standings - Series standings
- GET /series/{series_code}/player_card/{player_id} - Player's series card
- GET /series/{series_code}/overview - Series overview
- GET /series/{series_code}/regions - Series regions
- GET /series/{series_code}/rules - Series rules
- GET /series/{series_code}/stats - Series statistics
- GET /series/{series_code}/schedule - Series schedule
Stats (10 endpoints):
- GET /stats/global - Global statistics
- GET /stats/player_counts - Player count statistics
- GET /stats/tournament_counts - Tournament count statistics
- GET /stats/top_countries - Top countries by player count
- GET /stats/top_tournaments - Top tournaments
- GET /stats/recent_tournaments - Recent tournament statistics
- GET /stats/machine_popularity - Machine popularity statistics
- GET /stats/trends - Various trend statistics
- GET /stats/historical - Historical statistics
- GET /stats/participation - Participation statistics
Reference (2 endpoints):
- GET /reference/countries - List of countries
- GET /reference/states - List of states/provinces