from datetime import date from typing import Any, Literal, TYPE_CHECKING from uuid import UUID from pydantic import BaseModel, HttpUrl, field_validator from scryfall.models.base import BaseAPIModel from scryfall.models.enums import Color if TYPE_CHECKING: from scryfall.models.rulings import Ruling from scryfall.models.sets import Set class RelatedCard(BaseModel): """A card that's related to the current card""" id: UUID """The UUID of the related card""" object: Literal["related_card"] """The object type, literally `related_card`""" component: Literal["token", "meld_part", "meld_result", "combo_piece"] """How the card is related""" name: str """The name of the related card""" type_line: str """The type line of the related card""" uri: HttpUrl """The URL of the related card""" class CardFace(BaseModel): """A card face, for multi-faced cards""" artist: str | None = None """The face artist""" artist_id: str | None = None """The ID of the face artist""" cmc: float | None = None """Card Mana Cost""" color_indicator: list[Color] | None = None """The color indicator(s) of the face""" colors: list[Color] | None = None """The color(s) of the face""" defense: str | None = None """The toughness of the face, if it exists""" flavor_text: str | None = None """The face's flavor text""" illustration_id: UUID | None = None """The face's illustration UUID""" image_uris: dict[Literal["small", "normal", "large", "png", "art_crop", "border_crop"], HttpUrl] | None = None """URLs for the image face for different formats.""" layout: str | None = None """Layout of the face.""" loyalty: str | None = None """Face loyalty""" mana_cost: str """Face mana cost""" name: str """Name of the face""" object: Literal["card_face"] """Object type, literally `card_face`""" oracle_id: UUID | None = None """Oracle ID of the face""" oracle_text: str | None = None """Oracle text of the face""" power: str | None = None """The face's power""" printed_name: str | None = None """The printed name of the face""" printed_text: str | None = None """The printed text of the face""" printed_type_line: str | None = None """The printed type line of the face""" toughness: str | None = None """The toughness of the face""" type_line: str | None = None """The type line of the face""" watermark: str | None = None """The watermark of the face""" class Preview(BaseModel): """Object for defining when the card was previewed.""" previewed_at: date | None = None """When it was previewed""" source_uri: HttpUrl | None = None """The URL of where it was previewed""" source: str | None = None """The source of the preview""" @field_validator("source_uri", mode="before") @classmethod def validate_source_uri(cls, value: Any) -> Any: if isinstance(value, str): if len(value) > 0: return HttpUrl(value) return None class Card(BaseAPIModel): """Main Card model.""" # Core fields arena_id: int | None = None """MTG Arena ID""" id: UUID """Scryfall card UUID""" lang: str """Card language""" mtgo_id: int | None = None """MTG Online ID""" mtgo_foil_id: int | None = None """MTG Online Foil ID""" multiverse_ids: list[int] | None = None """MTG Multiverse IDs""" tcgplayer_id: int | None = None """TCGPlayer ID""" tcgplayer_etched_id: int | None = None """TCGPlayer Etched ID""" cardmarket_id: int | None = None """Cardmarket ID""" object: Literal["card"] """Object type, literally `card`""" layout: str """Card layout""" oracle_id: UUID | None = None """Oracle UUID""" prints_search_uri: HttpUrl """Print search URL for other printings""" rulings_uri: HttpUrl """Rulings URL""" scryfall_uri: HttpUrl """Scryfall URL""" uri: HttpUrl """Card URL""" # Gameplay fields all_parts: list[RelatedCard] | None = None """If this card is closely related to other cards, this property will be an array with Related Card objects.""" card_faces: list[CardFace] | None = None """All faces of the card""" cmc: float """Card mana value""" color_identity: list[Color] """Card color identity""" color_indicator: list[Color] | None = None """Card color indicator, if any""" colors: list[Color] | None = None """Card colors, if any. Colors may be on the card's faces instead""" defense: str | None = None """Toughness""" edhrec_rank: int | None = None """EDHRec card rank/popularity""" game_changer: bool | None = None """If this car is on the Commander Game Changer list""" hand_modifier: str | None = None """Vanguard card hand modifier""" keywords: list[str] """List of keywords, such as `Flying` or `Cumulative upkeep`""" legalities: dict[str, Literal["legal", "not_legal", "restricted", "banned"]] """List of legalitites across different formats""" life_modifier: str | None = None """The card's life modifier, if it is a Vanguard card. This value will contain a delta, such as `+2`.""" loyalty: str | None = None """This loyalty if any. Note that some cards have loyalties that are not numeric, such as X.""" mana_cost: str | None = None """The mana cost for this card. This value will be any empty string `""` if the cost is absent. Remember that per the game rules, a missing mana cost and a mana cost of `{0}` are different values. Multi-faced cards will report this value in card faces.""" name: str """The name of this card. If this card has multiple faces, this field will contain both names separated by `␣//␣`.""" oracle_text: str | None = None """The Oracle text for this card, if any.""" penny_rank: int | None = None """This card's rank/popularity on Penny Dreadful. Not all cards are ranked.""" power: str | None = None """This card's power, if any. Note that some cards have powers that are not numeric, such as `*`.""" produced_mana: list[Color] | None = None """Colors of mana that this card could produce.""" reserved: bool """True if this card is on the Reserved List.""" toughness: str | None = None """This card's toughness, if any. Note that some cards have toughnesses that are not numeric, such as `*`.""" type_line: str """The type line of this card.""" # Print fields artist: str | None = None artist_ids: list[UUID] | None = None attraction_lights: list[int] | None = None booster: bool border_color: Literal["black", "white", "borderless", "yellow", "silver", "gold"] card_back_id: UUID | None = None collector_number: str content_warning: bool | None = None digital: bool finishes: list[Literal["foil", "nonfoil", "etched"]] flavor_name: str | None = None flavor_text: str | None = None frame_effects: list[str] | None = None frame: str full_art: bool games: list[Literal["paper", "arena", "mtgo"]] highres_image: bool illustration_id: UUID | None = None image_status: Literal["missing", "placeholder", "lowres", "highres_scan"] image_uris: dict[Literal["small", "normal", "large", "png", "art_crop", "border_crop"], HttpUrl] | None = None oversized: bool prices: dict[str, str | None] printed_name: str | None = None printed_text: str | None = None printed_type_line: str | None = None promo: bool promo_types: list[str] | None = None purchase_uris: dict[str, HttpUrl] | None = None rarity: Literal["common", "uncommon", "rare", "special", "mythic", "bonus"] related_uris: dict[str, HttpUrl] released_at: date reprint: bool scryfall_set_uri: HttpUrl set_name: str set_search_uri: HttpUrl set_type: str set_uri: HttpUrl set: str set_id: UUID story_spotlight: bool textless: bool variation: bool variation_of: UUID | None = None security_stamp: Literal["oval", "triangle", "acorn", "circle", "arena", "heart"] | None = None watermark: str | None = None preview: Preview | None = None async def get_set(self) -> "Set": """Get set card is a part of.""" return await self._client.get_set_by_id(self.set_id) async def get_rulings(self) -> list["Ruling"]: """Get rulings for card.""" return await self._client.get_rulings_by_card_id(self.id)