Source code for psnawp_api.models.trophies.trophy

"""Defines the Trophy class and methods to access individual trophies within a trophy group, such as those from the main game or DLCs."""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, TypedDict

from typing_extensions import Self, override

from psnawp_api.models.listing import PaginationIterator
from psnawp_api.models.trophies.trophy_utils import (
    trophy_rarity_to_enum,
    trophy_type_str_to_enum,
)
from psnawp_api.utils import iso_format_to_datetime
from psnawp_api.utils.endpoints import API_PATH, BASE_PATH

if TYPE_CHECKING:
    from collections.abc import Generator
    from datetime import datetime

    from psnawp_api.core import Authenticator
    from psnawp_api.models.listing import PaginationArguments
    from psnawp_api.models.trophies.trophy_constants import (
        PlatformType,
        TrophyRarity,
        TrophyType,
    )


[docs] @dataclass(frozen=True) class Trophy: """A class that represents a PlayStation Video Game Trophy. :var str | None trophy_set_version: The current version of the trophy set. :var bool | None has_trophy_groups: True if this title has additional trophy groups. :var int | None trophy_id: Unique ID for this trophy. :var bool | None trophy_hidden: True if this is a secret trophy (Only for client). :var TrophyType | None trophy_type: Type of the trophy. :var str | None trophy_name: Name of trophy. :var str | None trophy_detail: Description of the trophy. :var str | None trophy_icon_url: URL for the graphic associated with the trophy. :var str | None trophy_group_id: ID of the trophy group this trophy belongs to. :var int | None trophy_progress_target_value: Trophy progress towards it being unlocked (PS5 Only). :var str | None trophy_reward_name: Name of the reward earning the trophy grants (PS5 Only). :var str | None trophy_reward_img_url: URL for the graphic associated with the reward (PS5 Only). To initialize this class, you need the :py:meth:`Trophy.from_trophy_dict`. """ # Trophy Group Metadata trophy_set_version: str | None has_trophy_groups: bool | None # Trophy Meta trophy_id: int | None trophy_hidden: bool | None trophy_type: TrophyType | None trophy_name: str | None trophy_detail: str | None trophy_icon_url: str | None trophy_group_id: str | None trophy_progress_target_value: int | None trophy_reward_name: str | None trophy_reward_img_url: str | None
[docs] @classmethod def from_trophy_dict(cls, trophy_dict: dict[str, Any]) -> Self: """Creates an instance of :py:class:`Trophy` from a dictionary.""" return cls( trophy_set_version=trophy_dict.get("trophySetVersion"), has_trophy_groups=trophy_dict.get("hasTrophyGroups"), trophy_id=trophy_dict.get("trophyId"), trophy_hidden=trophy_dict.get("trophyHidden"), trophy_type=trophy_type_str_to_enum(trophy_dict.get("trophyType")), trophy_name=trophy_dict.get("trophyName"), trophy_detail=trophy_dict.get("trophyDetail"), trophy_icon_url=trophy_dict.get("trophyIconUrl"), trophy_group_id=trophy_dict.get("trophyGroupId"), trophy_progress_target_value=trophy_dict.get("trophyProgressTargetValue"), trophy_reward_name=trophy_dict.get("trophyRewardName"), trophy_reward_img_url=trophy_dict.get("trophyRewardImageUrl"), )
[docs] @dataclass(frozen=True) class TrophyWithProgress(Trophy): """Earned Trophy Info. :var bool | None earned: True if this trophy has been earned. :var int | None progress: If the trophy tracks progress towards unlock this is number of steps currently completed (ie. 73/300) (PS5 titles only). :var int | None progress_rate: If the trophy tracks progress towards unlock this is the current percentage complete (PS5 titles only). :var datetime.datetime | None progressed_date_time: If the trophy tracks progress towards unlock, and some progress has been made, then this returns the date progress was last updated. (PS5 titles only). :var datetime.datetime | None earned_date_time: Date trophy was earned. :var TrophyRarity | None trophy_rarity: Rarity of the trophy. :var float | None trophy_earn_rate: Percentage of all users who have earned the trophy. To initialize this class, you need the :py:meth:`TrophyWithProgress.from_trophy_dict`. """ earned: bool | None progress: int | None progress_rate: int | None progressed_date_time: datetime | None earned_date_time: datetime | None trophy_rarity: TrophyRarity | None trophy_earn_rate: float | None
[docs] @classmethod def from_trophy_dict(cls, trophy_dict: dict[str, Any]) -> Self: """Creates an instance of :py:class:`TrophyWithProgress` from a dictionary.""" return cls( trophy_set_version=trophy_dict.get("trophySetVersion"), has_trophy_groups=trophy_dict.get("hasTrophyGroups"), trophy_id=trophy_dict.get("trophyId"), trophy_hidden=trophy_dict.get("trophyHidden"), trophy_type=trophy_type_str_to_enum(trophy_dict.get("trophyType")), trophy_name=trophy_dict.get("trophyName"), trophy_detail=trophy_dict.get("trophyDetail"), trophy_icon_url=trophy_dict.get("trophyIconUrl"), trophy_group_id=trophy_dict.get("trophyGroupId"), trophy_progress_target_value=trophy_dict.get("trophyProgressTargetValue"), trophy_reward_name=trophy_dict.get("trophyRewardName"), trophy_reward_img_url=trophy_dict.get("trophyRewardImageUrl"), earned=trophy_dict.get("earned"), progress=trophy_dict.get("progress"), progress_rate=trophy_dict.get("progressRate"), progressed_date_time=iso_format_to_datetime( trophy_dict.get("progressedDateTime"), ), earned_date_time=iso_format_to_datetime(trophy_dict.get("earnedDateTime")), trophy_rarity=trophy_rarity_to_enum(trophy_dict.get("trophyRare")), trophy_earn_rate=trophy_dict.get("trophyEarnedRate"), )
[docs] @classmethod def from_trophies_list( cls, trophies_dict: list[dict[str, Any]] | None, ) -> list[Trophy]: """Creates an list of :py:class:`TrophyWithProgress` from list of dictionaries.""" trophy_list: list[Trophy] = [] if trophies_dict is None: return trophy_list for trophy_dict in trophies_dict: trophy_instance = cls.from_trophy_dict(trophy_dict) trophy_list.append(trophy_instance) return trophy_list
[docs] class TrophyIterator(PaginationIterator[Trophy]): """Class for Iterating over all trophies for a specified group within a game title. To initialize this class, you need the :py:meth:`TrophyIterator.from_endpoint` .. note:: This class is intended to be used via Client or User class. See :py:meth:`psnawp_api.models.client.Client.trophies` or :py:meth:`psnawp_api.models.user.User.trophies`. """
[docs] def __init__( self, authenticator: Authenticator, url: str, pagination_args: PaginationArguments, platform: PlatformType, ) -> None: """Init for TrophyIterator.""" super().__init__( authenticator=authenticator, url=url, pagination_args=pagination_args, ) self.platform = platform
[docs] @classmethod def from_endpoint( cls, authenticator: Authenticator, pagination_args: PaginationArguments, np_communication_id: str, platform: PlatformType, trophy_group_id: str, ) -> Self: """Creates an instance of :py:class:`TrophyIterator` from api endpoint.""" url = f"{BASE_PATH['trophies']}{API_PATH['trophies_for_title'].format(np_communication_id=np_communication_id, trophy_group_id=trophy_group_id)}" return cls(authenticator, url, pagination_args, platform)
[docs] @override def fetch_next_page(self) -> Generator[Trophy, None, None]: """Fetches the next page in endpoint with pagination.""" service_name = self.platform.get_trophy_service_name() params = { "npServiceName": service_name, } | self._pagination_args.get_params_dict() response = self.authenticator.get(url=self._url, params=params).json() self._total_item_count = response.get("totalItemCount", 0) trophies: list[dict[str, Any]] = response.get("trophies", []) for trophy in trophies: trophy_instance = Trophy.from_trophy_dict( { **trophy, "trophySetVersion": response.get("trophySetVersion"), "hasTrophyGroups": response.get("hasTrophyGroups"), }, ) self._pagination_args.increment_offset() yield trophy_instance offset = response.get("nextOffset") or 0 if offset > 0: self._has_next = True else: self._has_next = False
[docs] class RarestTrophies(TypedDict): """Represents the rarest trophies in a game title.""" trophyId: int trophyHidden: bool earned: bool earnedDateTime: str trophyType: str trophyRare: int trophyEarnedRate: str
[docs] class TrophyWithProgressIterator(PaginationIterator[TrophyWithProgress]): """Class for Iterating over all trophies for a specified group within a game title, this class includes user progress for each trophy. To initialize this class, you need the :meth:`TrophyWithProgressIterator.from_endpoint`. .. warning:: Retrieving the progress of Trophies will require double the number of request because the progress has to be fetched via separate endpoint. .. note:: This class is intended to be used via Client or User class. See :py:meth:`psnawp_api.models.client.Client.trophies` or :py:meth:`psnawp_api.models.user.User.trophies`. """
[docs] def __init__( self, authenticator: Authenticator, url: str, pagination_args: PaginationArguments, platform: PlatformType, progress_url: str, ) -> None: """Init for TrophyWithProgressIterator.""" super().__init__( authenticator=authenticator, url=url, pagination_args=pagination_args, ) self.platform = platform self._progress_url = progress_url self.rarest_trophies: list[RarestTrophies] | None = None
[docs] @classmethod def from_endpoint( cls, authenticator: Authenticator, pagination_args: PaginationArguments, np_communication_id: str, platform: PlatformType, trophy_group_id: str, account_id: str, ) -> Self: """Fetches the next page in endpoint with pagination.""" url = f"{BASE_PATH['trophies']}{API_PATH['trophies_for_title'].format(np_communication_id=np_communication_id, trophy_group_id=trophy_group_id)}" progress_url = ( f"{BASE_PATH['trophies']}" f"{API_PATH['trophies_earned_for_title'].format(account_id=account_id, np_communication_id=np_communication_id, trophy_group_id=trophy_group_id)}" ) return cls(authenticator, url, pagination_args, platform, progress_url)
[docs] @override def fetch_next_page(self) -> Generator[TrophyWithProgress, None, None]: """Fetches the next page in endpoint with pagination.""" service_name = self.platform.get_trophy_service_name() params = { "npServiceName": service_name, } | self._pagination_args.get_params_dict() response = self.authenticator.get(url=self._url, params=params).json() self._total_item_count = response.get("totalItemCount", 0) trophies: list[dict[str, Any]] = response.get("trophies") response_progress = self.authenticator.get( url=self._progress_url, params=params, ).json() self.rarest_trophies = response_progress.get("rarestTrophies") trophies_progress: list[dict[str, Any]] = response_progress.get("trophies") for trophy, progress in zip(trophies, trophies_progress, strict=False): trophy_instance = TrophyWithProgress.from_trophy_dict( { **trophy, **progress, "trophySetVersion": response.get("trophySetVersion"), "hasTrophyGroups": response.get("hasTrophyGroups"), }, ) self._pagination_args.increment_offset() yield trophy_instance offset = response.get("nextOffset") or 0 if offset > 0: self._has_next = True else: self._has_next = False