Source code for psnawp_api.models.search.games_search

"""Implements the endpoints to search games."""

from __future__ import annotations

import json
from collections.abc import Generator
from typing import TYPE_CHECKING, Any

from typing_extensions import Self, override

from psnawp_api.models.listing import PaginationIterator
from psnawp_api.models.search.games_search_datatypes import GameSearchResultItem, SearchDomain, default_game_root_response
from psnawp_api.utils import BASE_PATH

if TYPE_CHECKING:
    from collections.abc import Generator

    from psnawp_api.core import Authenticator
    from psnawp_api.models.listing import PaginationArguments
    from psnawp_api.models.search.games_search_datatypes import (
        GameContextContainer,
        GameDomainContainer,
        GameRootResponse,
        GameUniversalContextSearchResponse,
    )

SEARCH_COMMON_HEADER: dict[str, str] = {
    "accept": "application/json",
    "content-type": "application/json",
    "apollographql-client-name": "PlayStationApp-Android",
    "apollographql-client-version": "25.4.0",
}


[docs] class UniversalDomainSearchIterator(PaginationIterator[GameSearchResultItem]): """Iterator for paginating over universal search results within a specific domain. This iterator handles the pagination logic for querying the PlayStation Network's universal search API, which can be used to search across different domains (e.g., games, add-ons, users). It allows for iterating over search results based on the provided search query and domain. :var str search_query: The search query string used to query the universal search endpoint. :var SearchDomain search_domain: The specific domain within which the search is performed (e.g., games, add-ons, users). :var str next_cursor: The cursor used for paginating to the next set of results. .. note:: This class is intended to be used via UniversalSearch. See :py:class:`~UniversalSearch`. """
[docs] def __init__( self, authenticator: Authenticator, url: str, pagination_args: PaginationArguments, search_query: str, search_domain: SearchDomain, next_cursor: str, ) -> None: """Initializes the UniversalDomainSearchIterator with the provided parameters. This iterator fetches search results from the PlayStation universal search API and manages pagination. :param authenticator: The Authenticator instance used for making authenticated requests to the API. :param url: The URL of the universal search endpoint. :param pagination_args: Pagination-specific arguments, such as page size and limit, passed to the endpoint. :param search_query: The search query string to search for specific content. :param search_domain: The domain to search within (e.g., games, add-ons, users). :param next_cursor: The cursor for fetching the next page of results. """ super().__init__( authenticator=authenticator, url=url, pagination_args=pagination_args, ) self.search_query = search_query self.search_domain = search_domain self.next_cursor = next_cursor
[docs] @classmethod def fetch_results( cls, authenticator: Authenticator, pagination_args: PaginationArguments, search_query: str, search_domain: SearchDomain, ) -> Generator[GameSearchResultItem, None, None]: """Initiates a game search and yields results based on the specified search domain. This method uses two endpoints: - The first retrieves a mix of full games and add-ons. - The second iterates over paginated results in the selected domain. :param authenticator: Instance of :class:`Authenticator` used for authenticated API requests. :param pagination_args: Pagination control including current offset and limit. :param search_query: The query string to search for content. :param search_domain: The content domain to search within (e.g., full games or add-ons). :yield: Yields individual :class:`GameSearchResultItem` objects until the limit is reached. If more results are available, continues yielding from the appropriate paginated endpoint. """ variables: dict[str, str | int] = { "searchTerm": search_query, "searchContext": "MobileUniversalSearchGame", "displayTitleLocale": "en-US", } extensions = { "persistedQuery": { "version": 1, "sha256Hash": "a2fbc15433b37ca7bfcd7112f741735e13268f5e9ebd5ffce51b85acc126f41d", }, } params = { "operationName": "metGetContextSearchResults", "variables": json.dumps(variables), "extensions": json.dumps(extensions), } response: dict[str, Any] = authenticator.get( url=BASE_PATH["graph_ql"], headers=SEARCH_COMMON_HEADER, params=params, ).json() default_value: GameRootResponse = default_game_root_response() data: GameContextContainer = response.get("data", default_value["data"]) universal_search: GameUniversalContextSearchResponse = data.get( "universalContextSearch", default_value["data"]["universalContextSearch"], ) search_results_container = universal_search.get( "results", default_value["data"]["universalContextSearch"]["results"], )[search_domain] search_results = search_results_container.get( "searchResults", default_value["data"]["universalContextSearch"]["results"][search_domain]["searchResults"], ) for search_result in search_results: if pagination_args.is_limit_reached(): return pagination_args.increment_offset() yield search_result search_iter = UniversalDomainSearchIterator.from_endpoint( authenticator=authenticator, pagination_args=pagination_args, search_query=search_query, search_domain=search_domain, next_cursor=search_results_container["next"], ) for search_result in search_iter: yield search_result
[docs] @classmethod def from_endpoint( cls, authenticator: Authenticator, pagination_args: PaginationArguments, search_query: str, search_domain: SearchDomain, next_cursor: str, ) -> Self: """Creates an instance of :py:class:`UniversalDomainSearchIterator` from api endpoint.""" return cls( authenticator=authenticator, url=BASE_PATH["graph_ql"], pagination_args=pagination_args, search_query=search_query, search_domain=search_domain, next_cursor=next_cursor, )
[docs] @override def fetch_next_page(self) -> Generator[GameSearchResultItem, None, None]: """Fetches the next page of Search Result objects from the API. :yield: A generator yielding Result objects. """ variables: dict[str, str | int] = { "searchTerm": self.search_query, "searchDomain": ("MobileGames" if self.search_domain == SearchDomain.FULL_GAMES else "MobileAddOns"), "pageSize": self._pagination_args.adjusted_page_size, "pageOffset": self._pagination_args.offset, "nextCursor": self.next_cursor, } extensions = { "persistedQuery": { "version": 1, "sha256Hash": "b51624299bd17b3799f77c9f097cc8887a04d3873f0329095976a841595bc902", }, } params = { "operationName": "metGetDomainSearchResults", "variables": json.dumps(variables), "extensions": json.dumps(extensions), } response: dict[str, Any] = self.authenticator.get( url=BASE_PATH["graph_ql"], headers=SEARCH_COMMON_HEADER, params=params, ).json() default_value: GameRootResponse = default_game_root_response() default_game_domain_container: GameDomainContainer = { "universalDomainSearch": default_value["data"]["universalContextSearch"]["results"][self.search_domain], } game_domain_container: GameDomainContainer = response.get("data", default_game_domain_container) game_universal_search_domain = game_domain_container.get( "universalDomainSearch", default_value["data"]["universalContextSearch"]["results"][self.search_domain] ) search_results = game_universal_search_domain.get( "searchResults", default_value["data"]["universalContextSearch"]["results"][self.search_domain]["searchResults"], ) self._total_item_count = game_universal_search_domain.get("totalResultCount", 0) self.next_cursor = game_universal_search_domain.get("next", "") for search_result in search_results: if self._pagination_args.is_limit_reached(): return self._pagination_args.increment_offset() yield search_result if self.next_cursor: self._has_next = True else: self._has_next = False