Files
mtgonline/backend/app/services/card_database.py
T

226 lines
6.5 KiB
Python

"""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)