PK!#xpyartifact/.DS_StoreBud1%typesfdscboolsfdscbool  @ @ @ @ E%DSDB` @ @ @PK!rn!!pyartifact/__init__.py__version__ = '0.1.0' __all__ = ['Cards', 'CardFilter', 'Hero', 'Ability', 'PassiveAbility', 'Spell', 'Creep', 'Improvement', 'Item'] from .api_sync import Cards from .filtering import CardFilter from .sets_and_cards import Hero, Ability, PassiveAbility, Spell, Creep, Improvement, Item PK!k)&Wpyartifact/api_sync.pyfrom typing import List, Optional from .sets_and_cards import CardSet, CardTypesInstanced from .filtering import CardFilter from .context import ctx class Cards: """ Synchronous API around Artifact API sets of cards """ def __init__( self, limit_sets: Optional[List[str]] = None, localize: Optional[str] = None, ) -> None: """ :param limit_sets: Whether to only fetch some sets, by default all ar used ('00', and '01') :param localize: Which language to fetch strings for, at this time only english is available :param raw_data: Whether you want to use the raw json data from the api, or the object mapping provided by this library """ self._set_numbers = limit_sets or ['00', '01'] self.sets: List[CardSet] = [CardSet(set_number) for set_number in self._set_numbers] if localize is not None: ctx.language = localize def load_all_sets(self): ctx.cards_by_id = {} ctx.cards_by_name = {} for set in self.sets: set.load() @property def filter(self) -> 'CardFilter': return CardFilter(sets=self.sets) def get(self, name: str) -> 'CardTypesInstanced': return ctx.cards_by_name[name.lower()] def find(self, name_approx: str) -> 'CardTypesInstanced': raise NotImplementedError('Card lookup with approx. names is not yet implemented.') PK!kpyartifact/context.pyfrom typing import Any, Dict class Context: def __init__(self, language: str = 'english') -> None: self.language: str = language self.cards_by_id: Dict[int, Any] = {} self.cards_by_name: Dict[str, Any] = {} ctx = Context() PK!cpyartifact/exceptions.pyclass PyArtifactException(Exception): pass class FilterException(PyArtifactException): pass class UnknownFilterArgument(FilterException): pass PK!,nFM!M!pyartifact/filtering.pyfrom typing import Optional, Iterable, Set, Union, List, Tuple, cast from .exceptions import UnknownFilterArgument from .sets_and_cards import CardSet, CardTypes, CardTypesInstanced, AVAILABLE_TYPES, STR_TO_CARD_TYPE, Improvement, \ Creep, Spell, Item, Hero def convert_card_type(type_: Union[str, CardTypes]) -> CardTypes: if isinstance(type_, AVAILABLE_TYPES): return type_ else: type_ = cast(str, type_) try: return STR_TO_CARD_TYPE[type_] except KeyError: raise UnknownFilterArgument(f'Unrecognized card type: {type_}') def convert_card_types(card_types: Iterable[Union[str, CardTypes]]) -> Tuple[CardTypes, ...]: return tuple([convert_card_type(ct) for ct in card_types]) class CardFilter: def __init__( self, sets: Optional[Iterable[CardSet]] = None, cards: Optional[Iterable[CardTypesInstanced]] = None ) -> None: self.cards: Set[CardTypesInstanced] = set(cards) if cards else set() if sets: for card_set in sets: for card in card_set.data.card_list: # type: ignore self.cards.add(card) self._filtered: List[CardTypesInstanced] = [] def __len__(self) -> int: return len(self.cards) def __str__(self) -> str: return f'' def __getitem__(self, item: int) -> 'CardTypesInstanced': return list(self.cards)[item] def type(self, type_: Union[str, CardTypes], filter_out=False) -> 'CardFilter': type_ = convert_card_type(type_) for card in self.cards: if filter_out: if not isinstance(card, type_): self._filtered.append(card) else: if isinstance(card, type_): self._filtered.append(card) return CardFilter(cards=self._filtered) def types_in(self, card_types: Iterable[Union[str, CardTypes]]) -> 'CardFilter': """ Filters out cards that were not passed to this filter :param card_types: Either strings of card types, or this library's classes of card types """ card_types = convert_card_types(card_types) for card in self.cards: if isinstance(card, card_types): self._filtered.append(card) return CardFilter(cards=self._filtered) def types_not_in(self, card_types: Iterable[Union[str, CardTypes]]) -> 'CardFilter': """ Filters out cards that were passed into this filter :param card_types: Either strings of card types, or this library's classes of card types """ card_types = convert_card_types(card_types) for card in self.cards: if not isinstance(card, card_types): self._filtered.append(card) return CardFilter(cards=self._filtered) def rarity(self, rarity: str) -> 'CardFilter': for card in self.cards: if hasattr(card, 'rarity'): card = cast(Union[Hero, Creep, Improvement, Improvement], card) if card.rarity == rarity: self._filtered.append(card) return CardFilter(cards=self._filtered) def rarity_in(self, rarities: List[str]) -> 'CardFilter': for card in self.cards: if hasattr(card, 'rarity'): card = cast(Union[Hero, Creep, Improvement, Improvement], card) if card.rarity in rarities: self._filtered.append(card) return CardFilter(cards=self._filtered) def rarity_not_in(self, rarities: List[str]) -> 'CardFilter': for card in self.cards: if hasattr(card, 'rarity'): card = cast(Union[Hero, Creep, Improvement, Improvement], card) if card.rarity not in rarities: self._filtered.append(card) return CardFilter(cards=self._filtered) def color(self, color: str) -> 'CardFilter': for card in self.cards: if hasattr(card, 'color'): card = cast(Union[Hero, Spell, Creep, Improvement], card) if card.color == color.lower(): self._filtered.append(card) return CardFilter(cards=self._filtered) def color_in(self, colors: Iterable[str]) -> 'CardFilter': colors = [color.lower() for color in colors] for card in self.cards: if hasattr(card, 'color'): card = cast(Union[Hero, Spell, Creep, Improvement], card) if card.color in colors: self._filtered.append(card) return CardFilter(cards=self._filtered) def color_not_in(self, colors: Iterable[str]) -> 'CardFilter': colors = [color.lower() for color in colors] for card in self.cards: if hasattr(card, 'color'): card = cast(Union[Hero, Spell, Creep, Improvement], card) if card.color not in colors: self._filtered.append(card) return CardFilter(cards=self._filtered) def mana_cost( self, gt: Optional[int] = None, gte: Optional[int] = None, lt: Optional[int] = None, lte: Optional[int] = None, eq: Optional[int] = None ) -> 'CardFilter': """ Filters out cards by their mana cost, if they have one. This will always filter out cards without mana cost. If multiple arguments are passed, every card that fits at least one will pass the filter. :param gt: Filters out cards that have higher mana cost than the number provided :param gte: Filters out cards that have higher or equal mana cost than the number provided :param lt: Filters out cards that have lower mana cost than the number provided :param lte: Filters out cards that have lower or equal mana cost than the number provided :param eq: Filters out cards that have mana cost equal to the number provided """ for card in self.cards: if hasattr(card, 'mana_cost'): card = cast(Union[Spell, Creep, Improvement], card) if gt is not None and card.mana_cost > gt: self._filtered.append(card) elif gte is not None and card.mana_cost >= gte: self._filtered.append(card) elif lt is not None and card.mana_cost < lt: self._filtered.append(card) elif lte is not None and card.mana_cost <= lte: self._filtered.append(card) elif eq is not None and card.mana_cost == eq: self._filtered.append(card) return CardFilter(cards=self._filtered) def gold_cost( self, gt: Optional[int] = None, gte: Optional[int] = None, lt: Optional[int] = None, lte: Optional[int] = None, eq: Optional[int] = None ) -> 'CardFilter': """ Filters out cards by their gold cost, if they have one. This will always filter out cards without gold cost. If multiple arguments are passed, every card that fits at least one will pass the filter. :param gt: Filters out cards that have higher gold cost than the number provided :param gte: Filters out cards that have higher or equal gold cost than the number provided :param lt: Filters out cards that have lower gold cost than the number provided :param lte: Filters out cards that have lower or equal gold cost than the number provided :param eq: Filters out cards that have gold cost equal to the number provided """ for card in self.cards: if hasattr(card, 'gold_cost'): card = cast(Item, card) if gt is not None and card.gold_cost > gt: self._filtered.append(card) elif gte is not None and card.gold_cost >= gte: self._filtered.append(card) elif lt is not None and card.gold_cost < lt: self._filtered.append(card) elif lte is not None and card.gold_cost <= lte: self._filtered.append(card) elif eq is not None and card.gold_cost == eq: self._filtered.append(card) return CardFilter(cards=self._filtered) PK!`-pyartifact/sets_and_cards.pyimport logging import time from typing import Optional, List, Union, Type, Dict import requests from .context import ctx from .types.json_types import CardSetType, SetInfoType, SetDataType, ReferenceType class CardBase: """Each card has this.""" def __init__(self, **kwargs) -> None: self.id: int = kwargs['card_id'] self.base_id: int = kwargs['base_card_id'] self.name: str = kwargs['card_name'][ctx.language] self.type: str = kwargs['card_type'] self.text: str = kwargs['card_text'].get(ctx.language, '') self.mini_image: str = kwargs['mini_image'].get('default') self.large_image: str = kwargs['large_image'].get('default') self.ingame_image: str = kwargs['ingame_image'].get('default') self.__references: List[ReferenceType] = kwargs['references'] def __str__(self) -> str: return self.name def __repr__(self) -> str: return f'' @property def includes(self) -> List['CardTypesInstanced']: includes = [] for ref in self.__references: if ref['ref_type'] == 'includes': included = ctx.cards_by_id[ref['card_id']] for _ in range(ref['count']): includes.append(included) return includes @property def passive_abilities(self) -> List['PassiveAbility']: passive_abilities = [] for ref in self.__references: if ref['ref_type'] == 'passive_ability': ability = ctx.cards_by_id[ref['card_id']] passive_abilities.append(ability) return passive_abilities @property def active_abilities(self) -> List['Ability']: abilities = [] for ref in self.__references: if ref['ref_type'] == 'active_ability': ability = ctx.cards_by_id[ref['card_id']] abilities.append(ability) return abilities @property def references(self) -> List['CardTypesInstanced']: references = [] for ref in self.__references: if ref['ref_type'] == 'references': reference = ctx.cards_by_id[ref['card_id']] references.append(reference) return references class ColoredCard: def __init__(self, **kwargs) -> None: if kwargs.get('is_blue', False): self.color: str = 'blue' elif kwargs.get('is_black', False): self.color = 'black' elif kwargs.get('is_red', False): self.color = 'red' elif kwargs.get('is_green', False): self.color = 'green' else: # in case future sets introduce new colors, this way it won't break this library self.color = 'unknown' class Unit: def __init__(self, **kwargs) -> None: self.attack: int = kwargs.get('attack', 0) self.armor: int = kwargs.get('armor', 0) self.hit_points: int = kwargs.get('hit_points', 0) class Collectible: """Doesn't mean that EVERY card of that type is Collectible, all have exceptions""" def __init__(self, **kwargs) -> None: self.rarity: Optional[str] = kwargs.get('rarity') self.item_def: Optional[int] = kwargs.get('item_def') class Castable: def __init__(self, **kwargs) -> None: self.mana_cost: int = kwargs['mana_cost'] class Hero(CardBase, ColoredCard, Unit, Collectible): def __init__(self, **kwargs) -> None: CardBase.__init__(self, **kwargs) ColoredCard.__init__(self, **kwargs) Unit.__init__(self, **kwargs) Collectible.__init__(self, **kwargs) self.illustrator: str = kwargs['illustrator'] class PassiveAbility(CardBase): def __init__(self, **kwargs) -> None: CardBase.__init__(self, **kwargs) class Spell(CardBase, ColoredCard, Collectible, Castable): def __init__(self, **kwargs) -> None: CardBase.__init__(self, **kwargs) ColoredCard.__init__(self, **kwargs) Collectible.__init__(self, **kwargs) Castable.__init__(self, **kwargs) self.illustrator: str = kwargs['illustrator'] class Creep(CardBase, ColoredCard, Unit, Collectible, Castable): def __init__(self, **kwargs): CardBase.__init__(self, **kwargs) ColoredCard.__init__(self, **kwargs) Unit.__init__(self, **kwargs) Collectible.__init__(self, **kwargs) Castable.__init__(self, **kwargs) self.illustrator: Optional[str] = kwargs.get('illustrator') class Ability(CardBase): def __init__(self, **kwargs) -> None: CardBase.__init__(self, **kwargs) class Item(CardBase, Collectible): def __init__(self, **kwargs) -> None: CardBase.__init__(self, **kwargs) Collectible.__init__(self, **kwargs) self.illustrator: str = kwargs['illustrator'] self.gold_cost: int = kwargs['gold_cost'] self.sub_type: str = kwargs['sub_type'] class Improvement(CardBase, ColoredCard, Collectible, Castable): def __init__(self, **kwargs) -> None: CardBase.__init__(self, **kwargs) ColoredCard.__init__(self, **kwargs) Collectible.__init__(self, **kwargs) Castable.__init__(self, **kwargs) self.illustrator: str = kwargs['illustrator'] class SetInfo: def __init__(self, set_info: SetInfoType) -> None: self.set_id: int = set_info['set_id'] self.pack_item_def: int = set_info['pack_item_def'] self.name: str = set_info['name'][ctx.language] CardTypesInstanced = Union[Item, Hero, Ability, PassiveAbility, Improvement, Creep, Spell] CardTypes = Union[ Type[Item], Type[Hero], Type[Ability], Type[PassiveAbility], Type[Improvement], Type[Creep], Type[Spell] ] STR_TO_CARD_TYPE = { 'Hero': Hero, 'Passive Ability': PassiveAbility, 'Spell': Spell, 'Creep': Creep, 'Ability': Ability, 'Item': Item, 'Improvement': Improvement } # type: Dict[str, CardTypes] AVAILABLE_TYPES = (Item, Hero, Ability, PassiveAbility, Improvement, Creep, Spell) class CardSetData: # Stronghold and Pathing are core game mechanics, there's no need to be indexing them not_indexed = ['Stronghold', 'Pathing'] def __init__(self, data: CardSetType) -> None: self.version: int = data['version'] self.set_info = SetInfo(data['set_info']) self.card_list: List[CardTypesInstanced] = [] for card in data['card_list']: if card['card_type'] not in self.not_indexed: type_of_card = STR_TO_CARD_TYPE[card['card_type']] # type: CardTypes card_instance = type_of_card(**card) self.card_list.append(card_instance) # For fast lookups ctx.cards_by_id[card['base_card_id']] = card_instance ctx.cards_by_name[card['card_name'][ctx.language].lower()] = card_instance class CardSet: base_url = 'https://playartifact.com/cardset/' def __init__(self, set_number: str) -> None: self.url = f'{self.base_url}{set_number}' self.expire_time = None self.data: Optional[CardSetData] = None def load(self) -> None: cdn_info = requests.get(self.url).json() self.expire_time = cdn_info['expire_time'] data = requests.get(f"{cdn_info['cdn_root']}{cdn_info['url']}").json() # type: SetDataType self.data = CardSetData(data['card_set']) PK!pyartifact/types/__init__.pyPK!t))pyartifact/types/json_types.pyfrom typing import List, Dict from mypy_extensions import TypedDict # Key is language (atm only english), value is text, which includes html LocalizedText = Dict[str, str] # Key is so far only default, value is the url ImageUrl = Dict[str, str] class ReferenceTypeBase(TypedDict): card_id: int ref_type: str # includes, passive_ability, active_ability, references class ReferenceType(ReferenceTypeBase, total=False): count: int class SetInfoType(TypedDict): set_id: int pack_item_def: int name: LocalizedText class CardListDataType(TypedDict, total=False): card_id: int base_card_id: int card_type: str # Hero, Passive Ability, Spell, Creep, Ability, Item, Improvement sub_type: str gold_cost: int mana_cost: int card_name: LocalizedText card_text: LocalizedText mini_image: ImageUrl large_image: ImageUrl ingame_image: ImageUrl illustrator: str rarity: str # Common, Uncommon, Rare is_blue: bool is_red: bool is_black: bool is_green: bool item_def: int attack: int armor: int hit_points: int references: List[ReferenceType] class CardSetType(TypedDict): version: int set_info: SetInfoType card_list: List[CardListDataType] class SetDataType(TypedDict): card_set: CardSetType PK!H WX pyartifact-0.1.0.dist-info/WHEEL A н#Z."jm)Afb~ڠO68oF04UhoAf f4=4h0k::wXPK!H@w#pyartifact-0.1.0.dist-info/METADATAOO0 Ƥh *v*㈬+AMZtP>=) z~9R|GKm: Y$=ZvXx s:޿4Fnmڦ3%lӑi~ͮMQU~nS֪ ~Cv!JX^pCje0H%xQT^|S;Eۦ2T]Cbc:юhhǂ[,h~-܃3g<,sq򟷡%'bPK!HmgH!pyartifact-0.1.0.dist-info/RECORD}ˎ@}? 8PRYҨE MXP@O2`fһCǸclc5]@ag%wynYҭK@^Vvkp()o/1 xF?\$Ԓ˝vz,/V}6pA@ "(T)Xͬ>M=o)4 jXz^S߃C=:Jި%%.mjlm\J!j1#HX϶Gl5[@7J b攑'ި0)(qZhG/9UiA? ȩˮ'rayGb](HEwk]sӼމt S xspwٴq7Zx=ՏD7X<P*%T"hڂ[H(ibexTֺx<.󛗣 nã9?˾qb.]% s&ku02,n@0g,#=I}j~kðb)d,<,x*{U"GARLpk{m_oocVžu|/vС9춷 N[V PK!#xpyartifact/.DS_StorePK!rn!!6pyartifact/__init__.pyPK!k)&Wpyartifact/api_sync.pyPK!kpyartifact/context.pyPK!c pyartifact/exceptions.pyPK!,nFM!M!!pyartifact/filtering.pyPK!`-"Cpyartifact/sets_and_cards.pyPK!]`pyartifact/types/__init__.pyPK!t))`pyartifact/types/json_types.pyPK!H WX epyartifact-0.1.0.dist-info/WHEELPK!H@w#fpyartifact-0.1.0.dist-info/METADATAPK!HmgH!gpyartifact-0.1.0.dist-info/RECORDPK faj