"""Card database service for importing and querying card data.""" import httpx from typing import List, Dict, Optional from dataclasses import dataclass from enum import Enum class CardType(str, Enum): """Card types from Magic: The Gathering.""" CREATURES = "Creature" INSTANT = "Instant" SORCERY = "Sorcery" ENCHANTMENT = "Enchantment" ARTIFACT = "Artifact" PLANE = "Plane" PLANESWALKER = "Planeswalker" LAND = "Land" BATTLE = "Battle" class CardColor(str, Enum): """Card colors.""" WHITE = "W" BLUE = "U" BLACK = "B" RED = "R" GREEN = "G" COLORLESS = "C" MULTICOLOR = "M" SHARD = "S" WIDGET = "X" class CardRarity(str, Enum): """Card rarities.""" COMMON = "common" UNCOMMON = "uncommon" RARE = "rare" MYTHIC = "mythic" SPECIAL = "special" @dataclass class CardData: """Card information from card database.""" id: int name: str types: List[CardType] colors: List[CardColor] rarity: CardRarity set_code: str collector_number: str flavor_text: Optional[str] = None rules_text: Optional[str] = None power: Optional[str] = None toughness: Optional[str] = None artist: Optional[str] = None image_url: Optional[str] = None provider_id: Optional[str] = None def __str__(self) -> str: return f"{self.name} ({self.set_code}-{self.collector_number})" class CardDatabase: """Card database service for importing and querying card data.""" def __init__(self): self.cards: Dict[int, CardData] = {} self._next_id = 1 async def import_from_mtjson(self, url: str = "https://mtjson.xyz/api/5.0.0/") -> List[CardData]: """Import card data from MTJSON API.""" async with httpx.AsyncClient() as client: response = await client.get(url) response.raise_for_status() data = response.json() imported_cards = [] for card_data in data: card = self._parse_mtjson_card(card_data) self.cards[self._next_id] = card imported_cards.append(card) self._next_id += 1 return imported_cards def _parse_mtjson_card(self, data: dict) -> CardData: """Parse MTJSON card data into CardData.""" card_id = self._next_id # Extract types types = [] if "types" in data: for type_str in data["types"]: try: types.append(CardType(type_str)) except ValueError: pass # Extract colors colors = [] if "colors" in data: for color_str in data["colors"]: try: colors.append(CardColor(color_str)) except ValueError: pass # Extract rarity rarity = CardRarity.COMMON if "rarity" in data: try: rarity = CardRarity(data["rarity"].lower()) except ValueError: pass # Extract set and collector number set_code = "" collector_number = "" if "set" in data: set_code = data["set"] if "collectorNumber" in data: collector_number = data["collectorNumber"] # Extract image URL image_url = None if "imageUris" in data and "normal" in data["imageUris"]: image_url = data["imageUris"]["normal"] return CardData( id=card_id, name=data.get("name", ""), types=types, colors=colors, rarity=rarity, set_code=set_code, collector_number=collector_number, flavor_text=data.get("flavorText"), rules_text=data.get("rulesText"), power=data.get("power"), toughness=data.get("toughness"), artist=data.get("artist"), image_url=image_url, provider_id=data.get("multiverseId"), ) def get_card_by_id(self, card_id: int) -> Optional[CardData]: """Get card by ID.""" return self.cards.get(card_id) def get_card_by_name(self, name: str) -> List[CardData]: """Get cards by name (case-insensitive).""" name_lower = name.lower() return [card for card in self.cards.values() if card.name.lower() == name_lower] def search_cards( self, query: str = "", card_type: Optional[CardType] = None, color: Optional[CardColor] = None, rarity: Optional[CardRarity] = None, set_code: Optional[str] = None, limit: int = 50, ) -> List[CardData]: """Search cards with filters.""" results = list(self.cards.values()) # Filter by query if query: query_lower = query.lower() results = [card for card in results if query_lower in card.name.lower()] # Filter by type if card_type: results = [card for card in results if card_type in card.types] # Filter by color if color: results = [card for card in results if color in card.colors] # Filter by rarity if rarity: results = [card for card in results if card.rarity == rarity] # Filter by set if set_code: results = [card for card in results if card.set_code == set_code.upper()] return results[:limit] def get_random_card(self) -> Optional[CardData]: """Get a random card from the database.""" import random if not self.cards: return None return random.choice(list(self.cards.values())) def get_card_count(self) -> int: """Get total number of cards in database.""" return len(self.cards) # Singleton instance card_database = CardDatabase() async def import_cards() -> List[CardData]: """Import cards from MTJSON.""" return await card_database.import_from_mtjson() def search_cards(**kwargs) -> List[CardData]: """Search cards with filters.""" return card_database.search_cards(**kwargs) def get_card_by_name(name: str) -> List[CardData]: """Get cards by name.""" return card_database.get_card_by_name(name) def get_card_by_id(card_id: int) -> Optional[CardData]: """Get card by ID.""" return card_database.get_card_by_id(card_id)