Source code for cassiopeia.core.league

from typing import List, Union, Optional, Generator, Set, Type

from merakicommons.cache import lazy_property, lazy
from merakicommons.container import searchable, SearchableList

from .. import configuration
from ..data import Region, Platform, Tier, Division, Queue, Role
from .common import (
    CoreData,
    CoreDataList,
    CassiopeiaObject,
    CassiopeiaGhost,
    CassiopeiaLazyList,
    ghost_load_on,
)
from ..dto.league import (
    LeagueDto,
    LeagueEntryDto,
    LeagueEntriesDto,
    LeagueSummonerEntriesDto,
    MiniSeriesDto,
    GrandmasterLeagueListDto,
    ChallengerLeagueListDto,
    MasterLeagueListDto,
)
from .summoner import Summoner


##############
# Data Types #
##############


class MiniSeriesData(CoreData):
    _dto_type = MiniSeriesDto
    _renamed = {}


class LeagueEntryData(CoreData):
    """Contains the data for one entry (summoner) in a League."""

    _dto_type = LeagueEntryDto
    _renamed = {
        "miniSeries": "promos",
        "playerOrTeamId": "summonerId",
        "playerOrTeamName": "summonerName",
        "leagueName": "name",
        "queueType": "queue",
        "rank": "division",
    }

    def __call__(self, **kwargs):
        if "miniSeries" in kwargs:
            self.promos = MiniSeriesData(**(kwargs.pop("miniSeries") or {}))
        if "summonerId" in kwargs:
            self.summonerId = kwargs.pop("summonerId")
        super().__call__(**kwargs)
        return self


class LeagueData(CoreData):
    """Contains the data for one League which has many entries."""

    _dto_type = LeagueDto
    _renamed = {"leagueId": "id"}

    def __call__(self, **kwargs):
        if "entries" in kwargs:
            self.entries = [
                LeagueEntryData(**entry) for entry in (kwargs.pop("entries") or [])
            ]
        super().__call__(**kwargs)
        return self


class LeagueSummonerEntriesData(CoreDataList):
    """League entries for a single summoner."""

    _dto_type = LeagueSummonerEntriesDto
    _renamed = {"summonerId": "summoner_id"}


class LeagueEntriesData(CoreDataList):
    """For paginated League entries."""

    _dto_type = LeagueEntriesDto
    _renamed = {}


class ChallengerLeagueListData(CoreData):
    _dto_type = ChallengerLeagueListDto
    _renamed = {"leagueId": "id"}

    def __call__(self, **kwargs):
        if "entries" in kwargs:
            self.entries = [
                LeagueEntryData(**entry) for entry in (kwargs.pop("entries") or [])
            ]
        super().__call__(**kwargs)
        return self


class GrandmasterLeagueListData(CoreData):
    _dto_type = GrandmasterLeagueListDto
    _renamed = {"leagueId": "id"}

    def __call__(self, **kwargs):
        if "entries" in kwargs:
            self.entries = [
                LeagueEntryData(**entry) for entry in (kwargs.pop("entries") or [])
            ]
        super().__call__(**kwargs)
        return self


class MasterLeagueListData(CoreData):
    _dto_type = MasterLeagueListDto
    _renamed = {"leagueId": "id"}

    def __call__(self, **kwargs):
        if "entries" in kwargs:
            self.entries = [
                LeagueEntryData(**entry) for entry in (kwargs.pop("entries") or [])
            ]
        super().__call__(**kwargs)
        return self


##############
# Core Types #
##############


[docs]class MiniSeries(CassiopeiaObject): _data_types = {MiniSeriesData} # Technically wins, loses, and wins_required can all be calculated from progress, so we don't technically need to store those data. def __str__(self): return f"Promos{self.progress + [None for _ in range(self.not_played)]}" @property def wins(self) -> int: return self._data[ MiniSeriesData ].wins # sum(self.progress) # This will work too @property def losses(self) -> int: return self._data[ MiniSeriesData ].losses # len(self._data[MiniSeriesData].progress[0]) - sum(self.progress) # This will work too @property def wins_required(self) -> int: """2 or 3 wins will be required for promotion.""" return self._data[ MiniSeriesData ].target # {3: 2, 5: 3}[len(self._data[MiniSeriesData].progress[0])] @property def not_played(self) -> int: """The number of games in the player's promos that they haven't played yet.""" return len(self._data[MiniSeriesData].progress) - len(self.progress) @lazy_property def progress(self) -> List[bool]: """A list of True/False for the number of games the played in the mini series indicating if the player won or lost.""" return [ True if p == "W" else False for p in self._data[MiniSeriesData].progress if p != "N" ]
[docs]@searchable( { str: ["division", "name", "summoner"], bool: ["hot_streak", "veteran", "fresh_blood"], Division: ["division"], Summoner: ["summoner"], Queue: ["queue"], } ) class LeagueEntry(CassiopeiaGhost): _data_types = {LeagueEntryData} def __init__(self, *, region: Union[Region, str] = None): kwargs = {"region": region} super().__init__(**kwargs) def __get_query__(self): return {"region": self.region, "platform": self.platform} def __eq__(self, other: "LeagueEntry"): if not isinstance(other, LeagueEntry) or self.region != other.region: return False return ( self.summoner == other.summoner and self.queue == other.queue and self.name == other.name ) __hash__ = CassiopeiaGhost.__hash__
[docs] @classmethod def from_data( cls, data: LeagueEntryData, loaded_groups: Optional[Set[Type[CoreData]]] = None, league: "League" = None, ): self = super().from_data(data=data, loaded_groups=loaded_groups) self.__league = league return self
@lazy_property def region(self) -> Region: """The region for this champion.""" return Region(self._data[LeagueEntryData].region) @lazy_property def platform(self) -> Platform: """The platform for this champion.""" return self.region.platform @lazy_property def queue(self) -> Queue: try: return Queue(self._data[LeagueEntryData].queue) except AttributeError: return self.league.queue @lazy_property def tier(self) -> Tier: return Tier(self._data[LeagueEntryData].tier) @lazy_property def division(self) -> Division: return Division(self._data[LeagueEntryData].division) @property def hot_streak(self) -> bool: return self._data[LeagueEntryData].hotStreak @lazy_property def promos(self) -> Optional[MiniSeries]: if hasattr(self._data[LeagueEntryData], "promos"): return MiniSeries.from_data(self._data[LeagueEntryData].promos) else: # Return None if the summoner isn't in their promos if hasattr(self._data[LeagueEntryData], "leaguePoints"): return None # Reraise the original error return MiniSeries.from_data(self._data[LeagueEntryData].promos) @property def wins(self) -> int: return self._data[LeagueEntryData].wins @property def veteran(self) -> bool: return self._data[LeagueEntryData].veteran @property def losses(self) -> int: return self._data[LeagueEntryData].losses @lazy_property def summoner(self) -> Summoner: return Summoner( id=self._data[LeagueEntryData].summonerId, name=self._data[LeagueEntryData].summonerName, region=self.region, ) @property def fresh_blood(self) -> bool: return self._data[LeagueEntryData].freshBlood @lazy_property def league(self) -> "League": return self.__league or League( id=self._data[LeagueEntryData].leagueId, region=self.region ) @property def league_points(self) -> int: return self._data[LeagueEntryData].leaguePoints @property def inactive(self) -> bool: return self._data[LeagueEntryData].inactive @property def role(self) -> Role: return Role.from_league_naming_scheme(self._data[LeagueEntryData].role)
[docs]class LeagueEntries(CassiopeiaLazyList): # type List[LeagueEntry] _data_types = {LeagueEntriesData} def __init__( self, *, region: Union[Region, str] = None, queue: Queue = None, tier: Tier = None, division: Division = None, ): kwargs = {"region": region, "queue": queue, "tier": tier, "division": division} CassiopeiaObject.__init__(self, **kwargs) @classmethod def __get_query_from_kwargs__( cls, *, region: Union[Region, str] = None, queue: Queue = None, tier: Tier = None, division: Division = None, ): query = {"region": region, "queue": queue, "tier": tier, "division": division} return query
[docs] @classmethod def from_generator( cls, generator: Generator, region: Union[Region, str] = None, queue: Queue = None, tier: Tier = None, division: Division = None, **kwargs, ): self = cls.__new__(cls) kwargs["region"] = region kwargs["queue"] = queue kwargs["tier"] = tier kwargs["division"] = division CassiopeiaLazyList.__init__(self, generator=generator, **kwargs) return self
@lazy_property def region(self) -> Region: return Region(self._data[LeagueEntriesData].region) @lazy_property def platform(self) -> Platform: return self.region.platform @lazy_property def queue(self) -> Queue: return Queue(self._data[LeagueEntriesData].queue) @lazy_property def tier(self) -> Tier: return Tier(self._data[LeagueEntriesData].tier) @lazy_property def division(self) -> Division: return Division(self._data[LeagueEntriesData].division)
[docs]class LeagueSummonerEntries(CassiopeiaLazyList): _data_types = {LeagueSummonerEntriesData} def __init__(self, *, summoner: Summoner): self.__summoner = summoner kwargs = {"region": summoner.region} CassiopeiaObject.__init__(self, **kwargs) @classmethod def __get_query_from_kwargs__(cls, *, summoner: Union[Summoner, str]) -> dict: query = {"region": summoner.region} if isinstance(summoner, Summoner): query["summoner.id"] = summoner.id elif isinstance(summoner, str): if len(summoner) < 35: query["summoner.id"] = Summoner( name=summoner, region=summoner.region ).id else: query["summoner.id"] = summoner assert "summoner.id" in query return query @lazy_property def region(self) -> Region: return Region(self._data[LeagueSummonerEntriesData].region) @lazy_property def platform(self) -> Platform: return self.region.platform @lazy_property def fives(self): for entry in self: if entry.queue is Queue.ranked_solo_fives: return entry raise ValueError("Queue does not exist for this summoner.") @lazy_property def flex(self): for entry in self: if entry.queue is Queue.ranked_flex_fives: return entry raise ValueError("Queue does not exist for this summoner.") @lazy_property def threes(self): for entry in self: if entry.queue is Queue.ranked_flex_threes: return entry raise ValueError("Queue does not exist for this summoner.")
[docs]@searchable({str: ["tier", "queue", "name"], Queue: ["queue"], Tier: ["tier"]}) class League(CassiopeiaGhost): _data_types = {LeagueData} def __init__( self, id: str = None, queue: Queue = None, region: Union[Region, str] = None ): if region is not None and not isinstance(region, Region): region = Region(region) kwargs = {"id": id, "region": region, "queue": queue} super().__init__(**kwargs) def __get_query__(self): return {"id": self.id, "region": self.region, "platform": self.platform} def __eq__(self, other: "League"): if not isinstance(other, League) or self.region != other.region: return False return self.id == other.id def __str__(self): region = self.region id_ = self.id return "League(id={id_}, region='{region}')".format( id_=id_, region=region.value ) __hash__ = CassiopeiaGhost.__hash__ def __getitem__(self, item): return self.entries[item] def __len__(self): return len(self.entries) def __iter__(self): return iter(self.entries) @lazy_property def region(self) -> Region: return Region(self._data[LeagueData].region) @lazy_property def platform(self) -> Platform: return self.region.platform @property def id(self) -> str: return self._data[LeagueData].id @CassiopeiaGhost.property(LeagueData) @ghost_load_on @lazy def tier(self) -> Tier: return Tier(self._data[LeagueData].tier) @CassiopeiaGhost.property(LeagueData) @ghost_load_on @lazy def queue(self) -> Queue: return Queue(self._data[LeagueData].queue) @CassiopeiaGhost.property(LeagueData) @ghost_load_on def name(self) -> str: return self._data[LeagueData].name @CassiopeiaGhost.property(LeagueData) @ghost_load_on @lazy def entries(self) -> List[LeagueEntry]: entries = [] for entry in self._data[LeagueData].entries: entry.leagueId = self.id entry = LeagueEntry.from_data(data=entry, loaded_groups={LeagueEntriesData}) entries.append(entry) return SearchableList(entries)
[docs]class ChallengerLeague(League): _data_types = {ChallengerLeagueListData} def __init__( self, *, queue: Union[Queue, str, int] = None, region: Union[Region, str] = None ): kwargs = {"region": region} if isinstance(queue, int): kwargs["queue"] = Queue.from_id(queue) elif isinstance(queue, str): kwargs["queue"] = Queue(queue) elif isinstance(queue, Queue): kwargs["queue"] = queue super().__init__(**kwargs) def __get_query__(self): return {"region": self.region, "platform": self.platform, "queue": self.queue} def __eq__(self, other: "ChallengerLeague"): if not isinstance(other, ChallengerLeague) or self.region != other.region: return False return self.queue == other.queue __hash__ = CassiopeiaGhost.__hash__ @lazy_property def region(self) -> Region: return Region(self._data[ChallengerLeagueListData].region) @lazy_property def platform(self) -> Platform: return self.region.platform @CassiopeiaGhost.property(ChallengerLeagueListData) @ghost_load_on def id(self) -> str: return self._data[ChallengerLeagueListData].id @lazy_property def tier(self) -> Tier: return Tier.challenger @lazy_property def queue(self) -> Queue: return Queue(self._data[ChallengerLeagueListData].queue) @CassiopeiaGhost.property(ChallengerLeagueListData) @ghost_load_on def name(self) -> str: return self._data[ChallengerLeagueListData].name @CassiopeiaGhost.property(ChallengerLeagueListData) @ghost_load_on @lazy def entries(self) -> List[LeagueEntry]: return SearchableList( [ LeagueEntry.from_data(entry) for entry in self._data[ChallengerLeagueListData].entries ] )
class GrandmasterLeague(CassiopeiaGhost): _data_types = {GrandmasterLeagueListData} def __init__( self, *, queue: Union[Queue, str, int] = None, region: Union[Region, str] = None ): kwargs = {"region": region} if isinstance(queue, int): kwargs["queue"] = Queue.from_id(queue) elif isinstance(queue, str): kwargs["queue"] = Queue(queue) elif isinstance(queue, Queue): kwargs["queue"] = queue super().__init__(**kwargs) def __get_query__(self): return {"region": self.region, "platform": self.platform, "queue": self.queue} def __eq__(self, other: "GrandmasterLeague"): if not isinstance(other, GrandmasterLeague) or self.region != other.region: return False return self.queue == other.queue __hash__ = CassiopeiaGhost.__hash__ def __getitem__(self, item): return self.entries[item] def __len__(self): return len(self.entries) @lazy_property def region(self) -> Region: return Region(self._data[GrandmasterLeagueListData].region) @lazy_property def platform(self) -> Platform: return self.region.platform @CassiopeiaGhost.property(GrandmasterLeagueListData) @ghost_load_on def id(self) -> str: return self._data[GrandmasterLeagueListData].id @lazy_property def tier(self) -> Tier: return Tier.grandmaster @lazy_property def queue(self) -> Queue: return Queue(self._data[GrandmasterLeagueListData].queue) @CassiopeiaGhost.property(GrandmasterLeagueListData) @ghost_load_on def name(self) -> str: return self._data[GrandmasterLeagueListData].name @CassiopeiaGhost.property(GrandmasterLeagueListData) @ghost_load_on @lazy def entries(self) -> List[LeagueEntry]: return SearchableList( [ LeagueEntry.from_data(entry) for entry in self._data[GrandmasterLeagueListData].entries ] )
[docs]class MasterLeague(CassiopeiaGhost): _data_types = {MasterLeagueListData} def __init__( self, *, queue: Union[Queue, str, int] = None, region: Union[Region, str] = None ): kwargs = {"region": region} if isinstance(queue, int): kwargs["queue"] = Queue.from_id(queue) elif isinstance(queue, str): kwargs["queue"] = Queue(queue) elif isinstance(queue, Queue): kwargs["queue"] = queue super().__init__(**kwargs) def __get_query__(self): return {"region": self.region, "platform": self.platform, "queue": self.queue} def __eq__(self, other: "MasterLeague"): if not isinstance(other, MasterLeague) or self.region != other.region: return False return self.queue == other.queue __hash__ = CassiopeiaGhost.__hash__ def __getitem__(self, item): return self.entries[item] def __len__(self): return len(self.entries) @lazy_property def region(self) -> Region: return Region(self._data[MasterLeagueListData].region) @lazy_property def platform(self) -> Platform: return self.region.platform @CassiopeiaGhost.property(MasterLeagueListData) @ghost_load_on def id(self) -> str: return self._data[MasterLeagueListData].id @lazy_property def tier(self) -> Tier: return Tier.master @lazy_property def queue(self) -> Queue: return Queue(self._data[MasterLeagueListData].queue) @CassiopeiaGhost.property(MasterLeagueListData) @ghost_load_on def name(self) -> str: return self._data[MasterLeagueListData].name @CassiopeiaGhost.property(MasterLeagueListData) @ghost_load_on @lazy def entries(self) -> List[LeagueEntry]: return SearchableList( [ LeagueEntry.from_data(entry) for entry in self._data[MasterLeagueListData].entries ] )