From d620dfc33d080cb8890ddfd39c77d674964015dd Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 4 Feb 2026 13:54:30 -0600 Subject: [PATCH 1/2] fix: Exclude deprecated pitcher_injury field from player responses The pitcher_injury column is no longer used but was being included in all player responses after the service layer refactor. This change restores the previous behavior of filtering it out. Changes: - Add EXCLUDED_FIELDS class constant to PlayerService - Filter excluded fields in _player_to_dict() method - Update _query_to_player_dicts() to use _player_to_dict() for all conversions - Applies to both JSON and CSV responses Version bump: 2.4.1 -> 2.4.2 Co-Authored-By: Claude Sonnet 4.5 --- VERSION | 2 +- app/services/player_service.py | 29 +++++++++++++---------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/VERSION b/VERSION index 005119b..73462a5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.4.1 +2.5.1 diff --git a/app/services/player_service.py b/app/services/player_service.py index ef9f063..b373918 100644 --- a/app/services/player_service.py +++ b/app/services/player_service.py @@ -38,6 +38,9 @@ class PlayerService(BaseService): cache_patterns = ["players*", "players-search*", "player*", "team-roster*"] + # Deprecated fields to exclude from player responses + EXCLUDED_FIELDS = ['pitcher_injury'] + # Class-level repository for dependency injection _injected_repo: Optional[AbstractPlayerRepository] = None @@ -322,19 +325,10 @@ class PlayerService(BaseService): if first_item is None: return [] - # If items are already dicts (from mock) - if isinstance(first_item, dict): - players_data = list(query) - if short_output: - return players_data - # Add computed fields if needed - return players_data - - # If items are DB models (from real repo) - + # Convert all items through _player_to_dict to ensure filtering players_data = [] for player in query: - player_dict = model_to_dict(player, recurse=not short_output) + player_dict = cls._player_to_dict(player, recurse=not short_output) players_data.append(player_dict) return players_data @@ -434,17 +428,20 @@ class PlayerService(BaseService): @classmethod def _player_to_dict(cls, player, recurse: bool = True) -> Dict[str, Any]: - """Convert player to dict.""" - # If already a dict, return as-is + """Convert player to dict, excluding deprecated fields.""" + # If already a dict, filter and return if isinstance(player, dict): - return player + return {k: v for k, v in player.items() if k not in cls.EXCLUDED_FIELDS} # Try to convert Peewee model try: - return model_to_dict(player, recurse=recurse) + player_dict = model_to_dict(player, recurse=recurse) + # Filter out excluded fields + return {k: v for k, v in player_dict.items() if k not in cls.EXCLUDED_FIELDS} except ImportError: # Fall back to basic dict conversion - return dict(player) + player_dict = dict(player) + return {k: v for k, v in player_dict.items() if k not in cls.EXCLUDED_FIELDS} @classmethod def update_player( From 332c445a12c4ccd68875d0ba3411c394a57df38a Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Wed, 4 Feb 2026 14:01:01 -0600 Subject: [PATCH 2/2] feat: Add pagination support to /players endpoint Add limit and offset parameters for paginated player queries. Changes: - Add limit parameter (minimum 1) to control page size - Add offset parameter (minimum 0) to skip results - Response now includes both 'count' (current page) and 'total' (all matches) - Pagination applied after filtering and sorting Example usage: /api/v3/players?season=12&limit=50&offset=0 (page 1) /api/v3/players?season=12&limit=50&offset=50 (page 2) Co-Authored-By: Claude Sonnet 4.5 --- app/routers_v3/players.py | 4 ++++ app/services/player_service.py | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index 75b8f7f..1b5a394 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -24,6 +24,8 @@ async def get_players( strat_code: list = Query(default=None), is_injured: Optional[bool] = None, sort: Optional[str] = None, + limit: Optional[int] = Query(default=None, ge=1, description="Maximum number of results to return"), + offset: Optional[int] = Query(default=None, ge=0, description="Number of results to skip for pagination"), short_output: Optional[bool] = False, csv: Optional[bool] = False, ): @@ -36,6 +38,8 @@ async def get_players( name=name, is_injured=is_injured, sort=sort, + limit=limit, + offset=offset, short_output=short_output or False, as_csv=csv or False ) diff --git a/app/services/player_service.py b/app/services/player_service.py index b373918..22d5129 100644 --- a/app/services/player_service.py +++ b/app/services/player_service.py @@ -90,6 +90,8 @@ class PlayerService(BaseService): name: Optional[str] = None, is_injured: Optional[bool] = None, sort: Optional[str] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, short_output: bool = False, as_csv: bool = False, ) -> Dict[str, Any]: @@ -104,6 +106,8 @@ class PlayerService(BaseService): name: Filter by name (exact match) is_injured: Filter by injury status sort: Sort order + limit: Maximum number of results to return + offset: Number of results to skip for pagination short_output: Exclude related data as_csv: Return as CSV format @@ -134,11 +138,24 @@ class PlayerService(BaseService): # Convert to list of dicts players_data = cls._query_to_player_dicts(query, short_output) + # Store total count before pagination + total_count = len(players_data) + + # Apply pagination (offset and limit) + if offset is not None: + players_data = players_data[offset:] + if limit is not None: + players_data = players_data[:limit] + # Return format if as_csv: return cls._format_player_csv(players_data) else: - return {"count": len(players_data), "players": players_data} + return { + "count": len(players_data), + "total": total_count, + "players": players_data + } except Exception as e: logger.error(f"Error fetching players: {e}")