# New Python Class We are working on this new Python class: ```python import json import os import logging from datetime import datetime, timedelta from typing import Optional, Dict, Any from clients.beatleader import client as beatleader_client from clients.beatleader.api.player_scores import player_scores_get_compact_scores from clients.beatleader.models.compact_score_response_response_with_metadata import CompactScoreResponseResponseWithMetadata logging.basicConfig( format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=logging.DEBUG ) class BeatLeaderAPI: BASE_URL = "https://api.beatleader.xyz" def __init__(self, cache_expiry_days: int = 1, cache_dir: Optional[str] = None): self.client = beatleader_client.Client(base_url=self.BASE_URL) self.cache_expiry_days = cache_expiry_days self.CACHE_DIR = cache_dir or self._determine_cache_dir() if not os.path.exists(self.CACHE_DIR): os.makedirs(self.CACHE_DIR) logging.info(f"Created cache directory: {self.CACHE_DIR}") def _determine_cache_dir(self) -> str: home_cache = os.path.expanduser("~/.cache") beatleader_cache = os.path.join(home_cache, "beatleader") if os.path.exists(home_cache): if not os.path.exists(beatleader_cache): try: os.makedirs(beatleader_cache) logging.info(f"Created cache directory: {beatleader_cache}") except OSError as e: logging.warning(f"Failed to create {beatleader_cache}: {e}") return os.path.join(os.getcwd(), ".cache") return beatleader_cache else: logging.info("~/.cache doesn't exist, using local .cache directory") return os.path.join(os.getcwd(), ".cache") def _get_cache_filename(self, player_id: str) -> str: return os.path.join(self.CACHE_DIR, f"player_{player_id}_scores.json") def _is_cache_valid(self, cache_file: str) -> bool: if not os.path.exists(cache_file): return False file_modified_time = datetime.fromtimestamp(os.path.getmtime(cache_file)) return datetime.now() - file_modified_time < timedelta(days=self.cache_expiry_days) def get_player_scores( self, player_id: str, use_cache: bool = True, count: int = 100, sort: str = "recent", max_pages: Optional[int] = None ) -> Dict[str, Any]: """ Fetches all player scores for a given player ID, handling pagination and caching. :param player_id: The ScoreSaber player ID. :param use_cache: Whether to use cached data if available. :param limit: Number of scores per page. :param sort: Sorting criteria. :param max_pages: Maximum number of pages to fetch. Fetch all if None. :return: A dictionary containing metadata and a list of player scores. """ cache_file = self._get_cache_filename(player_id) if use_cache and self._is_cache_valid(cache_file): logging.debug(f"Using cached data for player {player_id}") with open(cache_file, 'r') as f: return json.load(f) logging.debug(f"Fetching fresh data for player {player_id}") all_scores = [] page = 1 total_items = None while max_pages is None or page <= max_pages: try: response: CompactScoreResponseResponseWithMetadata = player_scores_get_compact_scores.sync( client=self.client, id=player_id, page=page, count=count, sort=sort ) except Exception as e: logging.error(f"Error fetching page {page} for player {player_id}: {e}") return {"metadata": {}, "playerScores": []} all_scores.extend(response.data) if total_items is None: total_items = response.metadata.total logging.debug(f"Total scores to fetch: {total_items}") logging.debug(f"Fetched page {page}: {len(response.data)} scores") if len(all_scores) >= total_items: break page += 1 result = { 'metadata': { 'itemsPerPage': response.metadata.items_per_page, 'page': response.metadata.page, 'total': response.metadata.total }, 'playerScores': all_scores } with open(cache_file, 'w') as f: json.dump(result, f, default=str) # default=str to handle datetime serialization logging.info(f"Cached scores for player {player_id} at {cache_file}") return result ``` Here is `src/clients/beatleader/api/player_scores/player_scores_get_compact_scores.py`: ```python from http import HTTPStatus from typing import Any, Dict, Optional, Union, cast import httpx from ... import errors from ...client import AuthenticatedClient, Client from ...models.compact_score_response_response_with_metadata import CompactScoreResponseResponseWithMetadata from ...models.difficulty_status import DifficultyStatus from ...models.leaderboard_contexts import LeaderboardContexts from ...models.order import Order from ...models.requirements import Requirements from ...models.score_filter_status import ScoreFilterStatus from ...models.scores_sort_by import ScoresSortBy from ...types import UNSET, Response, Unset def _get_kwargs( id: str, *, sort_by: Union[Unset, ScoresSortBy] = UNSET, order: Union[Unset, Order] = UNSET, page: Union[Unset, int] = 1, count: Union[Unset, int] = 8, search: Union[Unset, str] = UNSET, diff: Union[Unset, str] = UNSET, mode: Union[Unset, str] = UNSET, requirements: Union[Unset, Requirements] = UNSET, score_status: Union[Unset, ScoreFilterStatus] = UNSET, leaderboard_context: Union[Unset, LeaderboardContexts] = UNSET, type: Union[Unset, DifficultyStatus] = UNSET, modifiers: Union[Unset, str] = UNSET, stars_from: Union[Unset, float] = UNSET, stars_to: Union[Unset, float] = UNSET, time_from: Union[Unset, int] = UNSET, time_to: Union[Unset, int] = UNSET, event_id: Union[Unset, int] = UNSET, ) -> Dict[str, Any]: params: Dict[str, Any] = {} json_sort_by: Union[Unset, str] = UNSET if not isinstance(sort_by, Unset): json_sort_by = sort_by.value params["sortBy"] = json_sort_by json_order: Union[Unset, str] = UNSET if not isinstance(order, Unset): json_order = order.value params["order"] = json_order params["page"] = page params["count"] = count params["search"] = search params["diff"] = diff params["mode"] = mode json_requirements: Union[Unset, str] = UNSET if not isinstance(requirements, Unset): json_requirements = requirements.value params["requirements"] = json_requirements json_score_status: Union[Unset, str] = UNSET if not isinstance(score_status, Unset): json_score_status = score_status.value params["scoreStatus"] = json_score_status json_leaderboard_context: Union[Unset, str] = UNSET if not isinstance(leaderboard_context, Unset): json_leaderboard_context = leaderboard_context.value params["leaderboardContext"] = json_leaderboard_context json_type: Union[Unset, str] = UNSET if not isinstance(type, Unset): json_type = type.value params["type"] = json_type params["modifiers"] = modifiers params["stars_from"] = stars_from params["stars_to"] = stars_to params["time_from"] = time_from params["time_to"] = time_to params["eventId"] = event_id params = {k: v for k, v in params.items() if v is not UNSET and v is not None} _kwargs: Dict[str, Any] = { "method": "get", "url": f"/player/{id}/scores/compact", "params": params, } return _kwargs def _parse_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response ) -> Optional[Union[Any, CompactScoreResponseResponseWithMetadata]]: if response.status_code == HTTPStatus.OK: response_200 = CompactScoreResponseResponseWithMetadata.from_dict(response.json()) return response_200 if response.status_code == HTTPStatus.BAD_REQUEST: response_400 = cast(Any, None) return response_400 if response.status_code == HTTPStatus.NOT_FOUND: response_404 = cast(Any, None) return response_404 if client.raise_on_unexpected_status: raise errors.UnexpectedStatus(response.status_code, response.content) else: return None def _build_response( *, client: Union[AuthenticatedClient, Client], response: httpx.Response ) -> Response[Union[Any, CompactScoreResponseResponseWithMetadata]]: return Response( status_code=HTTPStatus(response.status_code), content=response.content, headers=response.headers, parsed=_parse_response(client=client, response=response), ) def sync_detailed( id: str, *, client: Union[AuthenticatedClient, Client], sort_by: Union[Unset, ScoresSortBy] = UNSET, order: Union[Unset, Order] = UNSET, page: Union[Unset, int] = 1, count: Union[Unset, int] = 8, search: Union[Unset, str] = UNSET, diff: Union[Unset, str] = UNSET, mode: Union[Unset, str] = UNSET, requirements: Union[Unset, Requirements] = UNSET, score_status: Union[Unset, ScoreFilterStatus] = UNSET, leaderboard_context: Union[Unset, LeaderboardContexts] = UNSET, type: Union[Unset, DifficultyStatus] = UNSET, modifiers: Union[Unset, str] = UNSET, stars_from: Union[Unset, float] = UNSET, stars_to: Union[Unset, float] = UNSET, time_from: Union[Unset, int] = UNSET, time_to: Union[Unset, int] = UNSET, event_id: Union[Unset, int] = UNSET, ) -> Response[Union[Any, CompactScoreResponseResponseWithMetadata]]: """Retrieve player's scores in a compact form Fetches a paginated list of scores for a specified player ID. Returns less info to save bandwith or processing time Args: id (str): sort_by (Union[Unset, ScoresSortBy]): order (Union[Unset, Order]): Represents the order in which values will be sorted. page (Union[Unset, int]): Default: 1. count (Union[Unset, int]): Default: 8. search (Union[Unset, str]): diff (Union[Unset, str]): mode (Union[Unset, str]): requirements (Union[Unset, Requirements]): score_status (Union[Unset, ScoreFilterStatus]): leaderboard_context (Union[Unset, LeaderboardContexts]): type (Union[Unset, DifficultyStatus]): Represents the difficulty status of a map. modifiers (Union[Unset, str]): stars_from (Union[Unset, float]): stars_to (Union[Unset, float]): time_from (Union[Unset, int]): time_to (Union[Unset, int]): event_id (Union[Unset, int]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: Response[Union[Any, CompactScoreResponseResponseWithMetadata]] """ kwargs = _get_kwargs( id=id, sort_by=sort_by, order=order, page=page, count=count, search=search, diff=diff, mode=mode, requirements=requirements, score_status=score_status, leaderboard_context=leaderboard_context, type=type, modifiers=modifiers, stars_from=stars_from, stars_to=stars_to, time_from=time_from, time_to=time_to, event_id=event_id, ) response = client.get_httpx_client().request( **kwargs, ) return _build_response(client=client, response=response) def sync( id: str, *, client: Union[AuthenticatedClient, Client], sort_by: Union[Unset, ScoresSortBy] = UNSET, order: Union[Unset, Order] = UNSET, page: Union[Unset, int] = 1, count: Union[Unset, int] = 8, search: Union[Unset, str] = UNSET, diff: Union[Unset, str] = UNSET, mode: Union[Unset, str] = UNSET, requirements: Union[Unset, Requirements] = UNSET, score_status: Union[Unset, ScoreFilterStatus] = UNSET, leaderboard_context: Union[Unset, LeaderboardContexts] = UNSET, type: Union[Unset, DifficultyStatus] = UNSET, modifiers: Union[Unset, str] = UNSET, stars_from: Union[Unset, float] = UNSET, stars_to: Union[Unset, float] = UNSET, time_from: Union[Unset, int] = UNSET, time_to: Union[Unset, int] = UNSET, event_id: Union[Unset, int] = UNSET, ) -> Optional[Union[Any, CompactScoreResponseResponseWithMetadata]]: """Retrieve player's scores in a compact form Fetches a paginated list of scores for a specified player ID. Returns less info to save bandwith or processing time Args: id (str): sort_by (Union[Unset, ScoresSortBy]): order (Union[Unset, Order]): Represents the order in which values will be sorted. page (Union[Unset, int]): Default: 1. count (Union[Unset, int]): Default: 8. search (Union[Unset, str]): diff (Union[Unset, str]): mode (Union[Unset, str]): requirements (Union[Unset, Requirements]): score_status (Union[Unset, ScoreFilterStatus]): leaderboard_context (Union[Unset, LeaderboardContexts]): type (Union[Unset, DifficultyStatus]): Represents the difficulty status of a map. modifiers (Union[Unset, str]): stars_from (Union[Unset, float]): stars_to (Union[Unset, float]): time_from (Union[Unset, int]): time_to (Union[Unset, int]): event_id (Union[Unset, int]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: Union[Any, CompactScoreResponseResponseWithMetadata] """ return sync_detailed( id=id, client=client, sort_by=sort_by, order=order, page=page, count=count, search=search, diff=diff, mode=mode, requirements=requirements, score_status=score_status, leaderboard_context=leaderboard_context, type=type, modifiers=modifiers, stars_from=stars_from, stars_to=stars_to, time_from=time_from, time_to=time_to, event_id=event_id, ).parsed async def asyncio_detailed( id: str, *, client: Union[AuthenticatedClient, Client], sort_by: Union[Unset, ScoresSortBy] = UNSET, order: Union[Unset, Order] = UNSET, page: Union[Unset, int] = 1, count: Union[Unset, int] = 8, search: Union[Unset, str] = UNSET, diff: Union[Unset, str] = UNSET, mode: Union[Unset, str] = UNSET, requirements: Union[Unset, Requirements] = UNSET, score_status: Union[Unset, ScoreFilterStatus] = UNSET, leaderboard_context: Union[Unset, LeaderboardContexts] = UNSET, type: Union[Unset, DifficultyStatus] = UNSET, modifiers: Union[Unset, str] = UNSET, stars_from: Union[Unset, float] = UNSET, stars_to: Union[Unset, float] = UNSET, time_from: Union[Unset, int] = UNSET, time_to: Union[Unset, int] = UNSET, event_id: Union[Unset, int] = UNSET, ) -> Response[Union[Any, CompactScoreResponseResponseWithMetadata]]: """Retrieve player's scores in a compact form Fetches a paginated list of scores for a specified player ID. Returns less info to save bandwith or processing time Args: id (str): sort_by (Union[Unset, ScoresSortBy]): order (Union[Unset, Order]): Represents the order in which values will be sorted. page (Union[Unset, int]): Default: 1. count (Union[Unset, int]): Default: 8. search (Union[Unset, str]): diff (Union[Unset, str]): mode (Union[Unset, str]): requirements (Union[Unset, Requirements]): score_status (Union[Unset, ScoreFilterStatus]): leaderboard_context (Union[Unset, LeaderboardContexts]): type (Union[Unset, DifficultyStatus]): Represents the difficulty status of a map. modifiers (Union[Unset, str]): stars_from (Union[Unset, float]): stars_to (Union[Unset, float]): time_from (Union[Unset, int]): time_to (Union[Unset, int]): event_id (Union[Unset, int]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: Response[Union[Any, CompactScoreResponseResponseWithMetadata]] """ kwargs = _get_kwargs( id=id, sort_by=sort_by, order=order, page=page, count=count, search=search, diff=diff, mode=mode, requirements=requirements, score_status=score_status, leaderboard_context=leaderboard_context, type=type, modifiers=modifiers, stars_from=stars_from, stars_to=stars_to, time_from=time_from, time_to=time_to, event_id=event_id, ) response = await client.get_async_httpx_client().request(**kwargs) return _build_response(client=client, response=response) async def asyncio( id: str, *, client: Union[AuthenticatedClient, Client], sort_by: Union[Unset, ScoresSortBy] = UNSET, order: Union[Unset, Order] = UNSET, page: Union[Unset, int] = 1, count: Union[Unset, int] = 8, search: Union[Unset, str] = UNSET, diff: Union[Unset, str] = UNSET, mode: Union[Unset, str] = UNSET, requirements: Union[Unset, Requirements] = UNSET, score_status: Union[Unset, ScoreFilterStatus] = UNSET, leaderboard_context: Union[Unset, LeaderboardContexts] = UNSET, type: Union[Unset, DifficultyStatus] = UNSET, modifiers: Union[Unset, str] = UNSET, stars_from: Union[Unset, float] = UNSET, stars_to: Union[Unset, float] = UNSET, time_from: Union[Unset, int] = UNSET, time_to: Union[Unset, int] = UNSET, event_id: Union[Unset, int] = UNSET, ) -> Optional[Union[Any, CompactScoreResponseResponseWithMetadata]]: """Retrieve player's scores in a compact form Fetches a paginated list of scores for a specified player ID. Returns less info to save bandwith or processing time Args: id (str): sort_by (Union[Unset, ScoresSortBy]): order (Union[Unset, Order]): Represents the order in which values will be sorted. page (Union[Unset, int]): Default: 1. count (Union[Unset, int]): Default: 8. search (Union[Unset, str]): diff (Union[Unset, str]): mode (Union[Unset, str]): requirements (Union[Unset, Requirements]): score_status (Union[Unset, ScoreFilterStatus]): leaderboard_context (Union[Unset, LeaderboardContexts]): type (Union[Unset, DifficultyStatus]): Represents the difficulty status of a map. modifiers (Union[Unset, str]): stars_from (Union[Unset, float]): stars_to (Union[Unset, float]): time_from (Union[Unset, int]): time_to (Union[Unset, int]): event_id (Union[Unset, int]): Raises: errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. httpx.TimeoutException: If the request takes longer than Client.timeout. Returns: Union[Any, CompactScoreResponseResponseWithMetadata] """ return ( await asyncio_detailed( id=id, client=client, sort_by=sort_by, order=order, page=page, count=count, search=search, diff=diff, mode=mode, requirements=requirements, score_status=score_status, leaderboard_context=leaderboard_context, type=type, modifiers=modifiers, stars_from=stars_from, stars_to=stars_to, time_from=time_from, time_to=time_to, event_id=event_id, ) ).parsed ``` Please review get_player_scores(), we wonder if the sort option is done correctly.