From 73384827facae8f46e64c6602d81b1c067f214c1 Mon Sep 17 00:00:00 2001 From: Triyan Mukherjee Date: Sat, 31 Oct 2026 00:39:41 +0530 Subject: [PATCH 1/4] feat: Add types to build.py and refactor helper methods --- data/v2/build.py | 1748 ++++++++++++------------------------- data/v2/utils/__init__.py | 0 data/v2/utils/helpers.py | 56 ++ data/v2/utils/media.py | 609 +++++++++++++ pokemon_v2/test_models.py | 14 +- ruff.toml | 1 - 6 files changed, 1254 insertions(+), 1174 deletions(-) create mode 100644 data/v2/utils/__init__.py create mode 100644 data/v2/utils/helpers.py create mode 100644 data/v2/utils/media.py diff --git a/data/v2/build.py b/data/v2/build.py index e959d8118..8e38413ff 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -11,618 +11,190 @@ # wipe it and rewrite each row using the data found in data/v2/csv. -import contextlib -import csv -import gzip -import json -import os -import os.path -import pathlib import re -import urllib.error -import urllib.request -from collections.abc import Callable +from collections.abc import Iterator from copy import deepcopy -from email.utils import parsedate_to_datetime -from typing import Any -from django.db import connection - -from pokemon_v2.models import * - -# why this way? how about use `__file__` -DATA_LOCATION = "data/v2/csv/" -DATA_LOCATION2 = os.path.join(os.path.dirname(__file__), "csv") -GROUP_RGX = r"\[(.*?)\]\{(.*?)\}" -SUB_RGX = r"\[.*?\]\{.*?\}" - -DB_CURSOR = connection.cursor() -DB_VENDOR = connection.vendor - - -MEDIA_DIR = "{prefix}{{file_name}}".format( - prefix=os.environ.get( - "POKEAPI_SPRITES_PREFIX", - "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/", - ) +from data.v2.utils.helpers import build_generic, load_data +from data.v2.utils.media import ( + POKEMON_SPRITE_CONFIG, + TYPE_SPRITE_GAME_MAP, + file_path_or_none, + pokemon_form_sprite_lookup, + pokemon_sprite_lookup, + resolve_sprite_config, ) -SOUND_DIR = "{prefix}{{file_name}}".format( - prefix=os.environ.get( - "POKEAPI_CRIES_PREFIX", - "https://raw.githubusercontent.com/PokeAPI/cries/main/cries/", - ) +from pokemon_v2.models import ( + Ability, + AbilityChange, + AbilityChangeEffectText, + AbilityEffectText, + AbilityFlavorText, + AbilityName, + Berry, + BerryFirmness, + BerryFirmnessName, + BerryFlavor, + BerryFlavorMap, + BerryFlavorName, + Characteristic, + CharacteristicDescription, + ContestCombo, + ContestEffect, + ContestEffectEffectText, + ContestEffectFlavorText, + ContestType, + ContestTypeName, + Currency, + CurrencyName, + EggGroup, + EggGroupName, + Encounter, + EncounterCondition, + EncounterConditionName, + EncounterConditionValue, + EncounterConditionValueMap, + EncounterConditionValueName, + EncounterMethod, + EncounterMethodName, + EncounterPokemonDetail, + EncounterSlot, + EvolutionChain, + EvolutionTrigger, + EvolutionTriggerName, + EvolutionVariable, + EvolutionVariableDescription, + EvolutionVariableName, + Experience, + Gender, + Generation, + GenerationName, + GrowthRate, + GrowthRateDescription, + Item, + ItemAttribute, + ItemAttributeDescription, + ItemAttributeMap, + ItemAttributeName, + ItemCategory, + ItemCategoryName, + ItemEffectText, + ItemFlavorText, + ItemFlingEffect, + ItemFlingEffectEffectText, + ItemGameIndex, + ItemName, + ItemPocket, + ItemPocketName, + ItemPrice, + ItemSprites, + Language, + LanguageName, + Location, + LocationArea, + LocationAreaEncounterRate, + LocationAreaName, + LocationGameIndex, + LocationName, + Machine, + Move, + MoveAttribute, + MoveAttributeDescription, + MoveAttributeMap, + MoveAttributeName, + MoveBattleStyle, + MoveBattleStyleName, + MoveChange, + MoveDamageClass, + MoveDamageClassDescription, + MoveDamageClassName, + MoveEffect, + MoveEffectChange, + MoveEffectChangeEffectText, + MoveEffectEffectText, + MoveFlavorText, + MoveLearnMethod, + MoveLearnMethodDescription, + MoveLearnMethodName, + MoveMeta, + MoveMetaAilment, + MoveMetaAilmentName, + MoveMetaCategory, + MoveMetaCategoryDescription, + MoveMetaStatChange, + MoveName, + MoveTarget, + MoveTargetDescription, + MoveTargetName, + Nature, + NatureBattleStylePreference, + NatureName, + NaturePokeathlonStat, + PalPark, + PalParkArea, + PalParkAreaName, + PokeathlonStat, + PokeathlonStatName, + Pokedex, + PokedexDescription, + PokedexName, + PokedexVersionGroup, + Pokemon, + PokemonAbility, + PokemonAbilityPast, + PokemonColor, + PokemonColorName, + PokemonCries, + PokemonDexNumber, + PokemonEggGroup, + PokemonEvolution, + PokemonForm, + PokemonFormCondition, + PokemonFormFlavorText, + PokemonFormGeneration, + PokemonFormName, + PokemonFormSprites, + PokemonFormTrigger, + PokemonFormType, + PokemonGameIndex, + PokemonHabitat, + PokemonHabitatName, + PokemonItem, + PokemonMove, + PokemonShape, + PokemonShapeName, + PokemonSpecies, + PokemonSpeciesDescription, + PokemonSpeciesFlavorText, + PokemonSpeciesName, + PokemonSprites, + PokemonStat, + PokemonStatPast, + PokemonType, + PokemonTypePast, + Region, + RegionName, + Stat, + StatName, + SuperContestCombo, + SuperContestEffect, + SuperContestEffectFlavorText, + Type, + TypeEfficacy, + TypeEfficacyPast, + TypeGameIndex, + TypeName, + TypeSprites, + Version, + VersionGroup, + VersionGroupMoveLearnMethod, + VersionGroupRegion, + VersionName, ) -IMAGE_DIR = os.getcwd() + "/data/v2/sprites/sprites/" -CRIES_DIR = os.getcwd() + "/data/v2/cries/cries/" - - -def _load_manifest_from_path(path: str) -> set[str] | None: - try: - with gzip.open(path, "rt", encoding="utf-8") as f: - return set(json.load(f)) - except (OSError, json.JSONDecodeError) as e: - print(f"Warning: Failed to read local sprite manifest at {path}: {e}") - return None - - -def _fetch_manifest(url: str, cache_file: str) -> set[str] | None: - try: - # Check freshness if cache_file exists - if os.path.isfile(cache_file): - try: - head_req = urllib.request.Request(url, method="HEAD") - with urllib.request.urlopen(head_req, timeout=10) as head_resp: - last_mod_header = head_resp.headers.get("Last-Modified") - if last_mod_header: - remote_mtime = parsedate_to_datetime(last_mod_header).timestamp() - if os.path.getmtime(cache_file) >= remote_mtime: - return _load_manifest_from_path(cache_file) - except (OSError, urllib.error.URLError, ValueError): - pass - - # Update manifest - with urllib.request.urlopen(url, timeout=30) as resp: - raw_bytes: bytes = resp.read() - data = json.loads(gzip.decompress(raw_bytes).decode("utf-8")) - - with contextlib.suppress(OSError): - pathlib.Path(cache_file).write_bytes(raw_bytes) - last_mod = resp.headers.get("Last-Modified") - if last_mod: - mtime = parsedate_to_datetime(last_mod).timestamp() - os.utime(cache_file, (mtime, mtime)) - - return set(data) - except (OSError, urllib.error.URLError, json.JSONDecodeError, gzip.BadGzipFile) as e: - print(f"Warning: Failed to fetch sprite manifest from {url}: {e}") - return None - - -def _load_local_sprites() -> set[str]: - """Scan the local cloned sprites submodule directory.""" - if not (os.path.isdir(IMAGE_DIR) and os.listdir(IMAGE_DIR)): - return set() - - images: set[str] = set() - for root, _dirs, files in os.walk(IMAGE_DIR): - for file in files: - image_path = os.path.join(root.replace(IMAGE_DIR, ""), file) - images.add(image_path.replace("\\", "/")) - return images - - -def _load_resource_images() -> set[str]: - use_local = os.environ.get("POKEAPI_USE_LOCAL_SPRITES", "").lower() in ("1", "true", "yes") - if use_local and (local_images := _load_local_sprites()): - return local_images - - cache_file = os.path.join(os.path.dirname(__file__), "manifest.json.gz") - manifest_urls: list[str] = list( - filter( - None, - [ - os.environ.get("POKEAPI_SPRITES_MANIFEST_URL", ""), - "https://pokeapi.github.io/sprites/manifest.json.gz", - "https://github.com/PokeAPI/sprites/releases/latest/download/manifest.json.gz", - ], - ) - ) - for url in manifest_urls: - if (data := _fetch_manifest(url, cache_file)) is not None: - return data - - # If offline / all remote requests failed, use cached manifest if available - if os.path.isfile(cache_file) and (cached := _load_manifest_from_path(cache_file)) is not None: - return cached - - # Fallback to local files if manifest is completely unavailable - if local_images := _load_local_sprites(): - return local_images - - print("Warning: Could not load sprite resources from local files or remote manifest. Sprites will be empty.") - return set() - - -RESOURCE_IMAGES: set[str] = _load_resource_images() -RESOURCE_CRIES: set[str] = set() - -for root, _dirs, files in os.walk(CRIES_DIR): - for file in files: - cry_path = os.path.join(root.replace(CRIES_DIR, ""), file) - cry_path = cry_path.replace("\\", "/") # convert Windows-style path to Unix - RESOURCE_CRIES.add(cry_path) - - -POKEMON_SPRITE_CONFIG: dict[str, Any] = { - "front_default": ("pokemon/", "png"), - "front_female": ("pokemon/female/", "png"), - "front_shiny": ("pokemon/shiny/", "png"), - "front_shiny_female": ("pokemon/shiny/female/", "png"), - "back_default": ("pokemon/back/", "png"), - "back_female": ("pokemon/back/female/", "png"), - "back_shiny": ("pokemon/back/shiny/", "png"), - "back_shiny_female": ("pokemon/back/shiny/female/", "png"), - "other": { - "dream_world": { - "front_default": ("pokemon/other/dream-world/", "svg"), - "front_female": ("pokemon/other/dream-world/female/", "svg"), - }, - "home": { - "front_default": ("pokemon/other/home/", "png"), - "front_female": ("pokemon/other/home/female/", "png"), - "front_shiny": ("pokemon/other/home/shiny/", "png"), - "front_shiny_female": ("pokemon/other/home/shiny/female/", "png"), - }, - "official-artwork": { - "front_default": ("pokemon/other/official-artwork/", "png"), - "front_shiny": ("pokemon/other/official-artwork/shiny/", "png"), - "versions": { - "generation-i": { - "red-and-blue": { - "front_default": ("pokemon/other/official-artwork/versions/generation-i/red-and-blue/", "png") - }, - "red-and-green": { - "front_default": ("pokemon/other/official-artwork/versions/generation-i/red-and-green/", "png") - }, - }, - "generation-ii": { - "gold-and-silver": { - "front_default": ( - "pokemon/other/official-artwork/versions/generation-ii/gold-and-silver/", - "png", - ) - } - }, - }, - }, - "showdown": { - "front_default": ("pokemon/other/showdown/", "gif"), - "front_female": ("pokemon/other/showdown/female/", "gif"), - "front_shiny": ("pokemon/other/showdown/shiny/", "gif"), - "front_shiny_female": ("pokemon/other/showdown/shiny/female/", "gif"), - "back_default": ("pokemon/other/showdown/back/", "gif"), - "back_female": ("pokemon/other/showdown/back/female/", "gif"), - "back_shiny": ("pokemon/other/showdown/back/shiny/", "gif"), - "back_shiny_female": ("pokemon/other/showdown/back/shiny/female/", "gif"), - }, - }, - "versions": { - "generation-i": { - "red-blue": { - "front_default": ("pokemon/versions/generation-i/red-blue/", "png"), - "front_gray": ("pokemon/versions/generation-i/red-blue/gray/", "png"), - "front_transparent": ("pokemon/versions/generation-i/red-blue/transparent/", "png"), - "front_transparent_gray": ("pokemon/versions/generation-i/red-blue/transparent/gray/", "png"), - "back_default": ("pokemon/versions/generation-i/red-blue/back/", "png"), - "back_gray": ("pokemon/versions/generation-i/red-blue/back/gray/", "png"), - "back_transparent": ("pokemon/versions/generation-i/red-blue/transparent/back/", "png"), - "back_transparent_gray": ("pokemon/versions/generation-i/red-blue/transparent/back/gray/", "png"), - }, - "red-green-japan": { - "front_default": ("pokemon/versions/generation-i/red-green-japan/", "png"), - "front_gray": ("pokemon/versions/generation-i/red-green-japan/gray/", "png"), - "back_default": ("pokemon/versions/generation-i/red-green-japan/back/", "png"), - "back_gray": ("pokemon/versions/generation-i/red-green-japan/back/gray/", "png"), - }, - "yellow": { - "front_default": ("pokemon/versions/generation-i/yellow/", "png"), - "front_gbc": ("pokemon/versions/generation-i/yellow/gbc/", "png"), - "front_gray": ("pokemon/versions/generation-i/yellow/gray/", "png"), - "front_transparent": ("pokemon/versions/generation-i/yellow/transparent/", "png"), - "front_transparent_gray": ("pokemon/versions/generation-i/yellow/transparent/gray/", "png"), - "back_default": ("pokemon/versions/generation-i/yellow/back/", "png"), - "back_gbc": ("pokemon/versions/generation-i/yellow/back/gbc/", "png"), - "back_gray": ("pokemon/versions/generation-i/yellow/back/gray/", "png"), - "back_transparent": ("pokemon/versions/generation-i/yellow/transparent/back/", "png"), - "back_transparent_gray": ("pokemon/versions/generation-i/yellow/transparent/back/gray/", "png"), - }, - }, - "generation-ii": { - "crystal": { - "front_default": ("pokemon/versions/generation-ii/crystal/", "png"), - "front_shiny": ("pokemon/versions/generation-ii/crystal/shiny/", "png"), - "front_transparent": ("pokemon/versions/generation-ii/crystal/transparent/", "png"), - "front_shiny_transparent": ("pokemon/versions/generation-ii/crystal/transparent/shiny/", "png"), - "back_default": ("pokemon/versions/generation-ii/crystal/back/", "png"), - "back_shiny": ("pokemon/versions/generation-ii/crystal/back/shiny/", "png"), - "back_transparent": ("pokemon/versions/generation-ii/crystal/transparent/back/", "png"), - "back_shiny_transparent": ("pokemon/versions/generation-ii/crystal/transparent/back/shiny/", "png"), - "animated": { - "front_default": ("pokemon/versions/generation-ii/crystal/animated/", "gif"), - "front_shiny": ("pokemon/versions/generation-ii/crystal/animated/shiny/", "gif"), - }, - }, - "gold": { - "front_default": ("pokemon/versions/generation-ii/gold/", "png"), - "front_shiny": ("pokemon/versions/generation-ii/gold/shiny/", "png"), - "front_transparent": ("pokemon/versions/generation-ii/gold/transparent/", "png"), - "front_shiny_transparent": ("pokemon/versions/generation-ii/gold/transparent/shiny/", "png"), - "back_default": ("pokemon/versions/generation-ii/gold/back/", "png"), - "back_shiny": ("pokemon/versions/generation-ii/gold/back/shiny/", "png"), - "back_transparent": ("pokemon/versions/generation-ii/gold/transparent/back/", "png"), - "back_shiny_transparent": ("pokemon/versions/generation-ii/gold/transparent/back/shiny/", "png"), - }, - "silver": { - "front_default": ("pokemon/versions/generation-ii/silver/", "png"), - "front_shiny": ("pokemon/versions/generation-ii/silver/shiny/", "png"), - "front_transparent": ("pokemon/versions/generation-ii/silver/transparent/", "png"), - "front_shiny_transparent": ("pokemon/versions/generation-ii/silver/transparent/shiny/", "png"), - "back_default": ("pokemon/versions/generation-ii/silver/back/", "png"), - "back_shiny": ("pokemon/versions/generation-ii/silver/back/shiny/", "png"), - "back_transparent": ("pokemon/versions/generation-ii/silver/transparent/back/", "png"), - "back_shiny_transparent": ("pokemon/versions/generation-ii/silver/transparent/back/shiny/", "png"), - }, - }, - "generation-iii": { - "emerald": { - "front_default": ("pokemon/versions/generation-iii/emerald/", "png"), - "front_shiny": ("pokemon/versions/generation-iii/emerald/shiny/", "png"), - "back_default": ("pokemon/versions/generation-iii/emerald/back/", "png"), - "back_shiny": ("pokemon/versions/generation-iii/emerald/back/shiny/", "png"), - "animated": { - "front_default": ("pokemon/versions/generation-iii/emerald/animated/", "png"), - "front_shiny": ("pokemon/versions/generation-iii/emerald/animated/shiny/", "png"), - "back_default": ("pokemon/versions/generation-iii/emerald/animated/back/", "png"), - "back_shiny": ("pokemon/versions/generation-iii/emerald/animated/back/shiny/", "png"), - }, - }, - "firered-leafgreen": { - "front_default": ("pokemon/versions/generation-iii/firered-leafgreen/", "png"), - "front_shiny": ("pokemon/versions/generation-iii/firered-leafgreen/shiny/", "png"), - "back_default": ("pokemon/versions/generation-iii/firered-leafgreen/back/", "png"), - "back_shiny": ("pokemon/versions/generation-iii/firered-leafgreen/back/shiny/", "png"), - }, - "icons": { - "front_default": ("pokemon/versions/generation-iii/icons/", "png"), - }, - "ruby-sapphire": { - "front_default": ("pokemon/versions/generation-iii/ruby-sapphire/", "png"), - "front_shiny": ("pokemon/versions/generation-iii/ruby-sapphire/shiny/", "png"), - "back_default": ("pokemon/versions/generation-iii/ruby-sapphire/back/", "png"), - "back_shiny": ("pokemon/versions/generation-iii/ruby-sapphire/back/shiny/", "png"), - }, - }, - "generation-iv": { - "diamond-pearl": { - "front_default": ("pokemon/versions/generation-iv/diamond-pearl/", "png"), - "front_female": ("pokemon/versions/generation-iv/diamond-pearl/female/", "png"), - "front_shiny": ("pokemon/versions/generation-iv/diamond-pearl/shiny/", "png"), - "front_shiny_female": ("pokemon/versions/generation-iv/diamond-pearl/shiny/female/", "png"), - "back_default": ("pokemon/versions/generation-iv/diamond-pearl/back/", "png"), - "back_female": ("pokemon/versions/generation-iv/diamond-pearl/back/female/", "png"), - "back_shiny": ("pokemon/versions/generation-iv/diamond-pearl/back/shiny/", "png"), - "back_shiny_female": ("pokemon/versions/generation-iv/diamond-pearl/back/shiny/female/", "png"), - "animated": { - "front_default": ("pokemon/versions/generation-iv/diamond-pearl/animated/", "png"), - "front_female": ("pokemon/versions/generation-iv/diamond-pearl/animated/female/", "png"), - "front_shiny": ("pokemon/versions/generation-iv/diamond-pearl/animated/shiny/", "png"), - "front_shiny_female": ( - "pokemon/versions/generation-iv/diamond-pearl/animated/shiny/female/", - "png", - ), - }, - }, - "heartgold-soulsilver": { - "front_default": ("pokemon/versions/generation-iv/heartgold-soulsilver/", "png"), - "front_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/female/", "png"), - "front_shiny": ("pokemon/versions/generation-iv/heartgold-soulsilver/shiny/", "png"), - "front_shiny_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/shiny/female/", "png"), - "back_default": ("pokemon/versions/generation-iv/heartgold-soulsilver/back/", "png"), - "back_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/back/female/", "png"), - "back_shiny": ("pokemon/versions/generation-iv/heartgold-soulsilver/back/shiny/", "png"), - "back_shiny_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/back/shiny/female/", "png"), - "animated": { - "front_default": ("pokemon/versions/generation-iv/heartgold-soulsilver/animated/", "png"), - "front_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/animated/female/", "png"), - "front_shiny": ("pokemon/versions/generation-iv/heartgold-soulsilver/animated/shiny/", "png"), - "front_shiny_female": ( - "pokemon/versions/generation-iv/heartgold-soulsilver/animated/shiny/female/", - "png", - ), - }, - }, - "icons": { - "front_default": ("pokemon/versions/generation-iv/icons/", "png"), - }, - "platinum": { - "front_default": ("pokemon/versions/generation-iv/platinum/", "png"), - "front_female": ("pokemon/versions/generation-iv/platinum/female/", "png"), - "front_shiny": ("pokemon/versions/generation-iv/platinum/shiny/", "png"), - "front_shiny_female": ("pokemon/versions/generation-iv/platinum/shiny/female/", "png"), - "back_default": ("pokemon/versions/generation-iv/platinum/back/", "png"), - "back_female": ("pokemon/versions/generation-iv/platinum/back/female/", "png"), - "back_shiny": ("pokemon/versions/generation-iv/platinum/back/shiny/", "png"), - "back_shiny_female": ("pokemon/versions/generation-iv/platinum/back/shiny/female/", "png"), - "animated": { - "front_default": ("pokemon/versions/generation-iv/platinum/animated/", "png"), - "front_female": ("pokemon/versions/generation-iv/platinum/animated/female/", "png"), - "front_shiny": ("pokemon/versions/generation-iv/platinum/animated/shiny/", "png"), - "front_shiny_female": ("pokemon/versions/generation-iv/platinum/animated/shiny/female/", "png"), - }, - }, - }, - "generation-v": { - "black-white": { - "front_default": ("pokemon/versions/generation-v/black-white/", "png"), - "front_female": ("pokemon/versions/generation-v/black-white/female/", "png"), - "front_shiny": ("pokemon/versions/generation-v/black-white/shiny/", "png"), - "front_shiny_female": ("pokemon/versions/generation-v/black-white/shiny/female/", "png"), - "back_default": ("pokemon/versions/generation-v/black-white/back/", "png"), - "back_female": ("pokemon/versions/generation-v/black-white/back/female/", "png"), - "back_shiny": ("pokemon/versions/generation-v/black-white/back/shiny/", "png"), - "back_shiny_female": ("pokemon/versions/generation-v/black-white/back/shiny/female/", "png"), - "animated": { - "front_default": ("pokemon/versions/generation-v/black-white/animated/", "gif"), - "front_female": ("pokemon/versions/generation-v/black-white/animated/female/", "gif"), - "front_shiny": ("pokemon/versions/generation-v/black-white/animated/shiny/", "gif"), - "front_shiny_female": ("pokemon/versions/generation-v/black-white/animated/shiny/female/", "gif"), - "back_default": ("pokemon/versions/generation-v/black-white/animated/back/", "gif"), - "back_female": ("pokemon/versions/generation-v/black-white/animated/back/female/", "gif"), - "back_shiny": ("pokemon/versions/generation-v/black-white/animated/back/shiny/", "gif"), - "back_shiny_female": ( - "pokemon/versions/generation-v/black-white/animated/back/shiny/female/", - "gif", - ), - }, - }, - "icons": { - "front_default": ("pokemon/versions/generation-v/icons/", "png"), - "animated": { - "front_default": ("pokemon/versions/generation-v/icons/animated/", "png"), - }, - }, - }, - "generation-vi": { - "icons": { - "front_default": ("pokemon/versions/generation-vi/icons/", "png"), - "front_female": ("pokemon/versions/generation-vi/icons/female/", "png"), - }, - "omega-ruby-alpha-sapphire": { - "front_default": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/", "png"), - "front_female": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/female/", "png"), - "front_shiny": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/shiny/", "png"), - "front_shiny_female": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/shiny/female/", "png"), - "back_default": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/back/", "png"), - "back_female": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/back/female/", "png"), - "back_shiny": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/back/shiny/", "png"), - "back_shiny_female": ( - "pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/back/shiny/female/", - "png", - ), - }, - "x-y": { - "front_default": ("pokemon/versions/generation-vi/x-y/", "png"), - "front_female": ("pokemon/versions/generation-vi/x-y/female/", "png"), - "front_shiny": ("pokemon/versions/generation-vi/x-y/shiny/", "png"), - "front_shiny_female": ("pokemon/versions/generation-vi/x-y/shiny/female/", "png"), - "back_default": ("pokemon/versions/generation-vi/x-y/back/", "png"), - "back_female": ("pokemon/versions/generation-vi/x-y/back/female/", "png"), - "back_shiny": ("pokemon/versions/generation-vi/x-y/back/shiny/", "png"), - "back_shiny_female": ("pokemon/versions/generation-vi/x-y/back/shiny/female/", "png"), - }, - }, - "generation-vii": { - "icons": { - "front_default": ("pokemon/versions/generation-vii/icons/", "png"), - "front_female": ("pokemon/versions/generation-vii/icons/female/", "png"), - }, - "lets-go-pikachu-lets-go-eevee": { - "front_default": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/", "gif"), - "front_female": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/female/", "gif"), - "front_shiny": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/shiny/", "gif"), - "front_shiny_female": ( - "pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/shiny/female/", - "gif", - ), - "back_default": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/back/", "gif"), - "back_female": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/back/female/", "gif"), - "back_shiny": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/back/shiny/", "gif"), - "back_shiny_female": ( - "pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/back/shiny/female/", - "gif", - ), - "icons": { - "front_default": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/icons/", "png") - }, - }, - "ultra-sun-ultra-moon": { - "front_default": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/", "gif"), - "front_female": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/female/", "gif"), - "front_shiny": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/shiny/", "gif"), - "front_shiny_female": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/shiny/female/", "gif"), - "back_default": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/back/", "gif"), - "back_female": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/back/female/", "gif"), - "back_shiny": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/back/shiny/", "gif"), - "back_shiny_female": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/back/shiny/female/", "gif"), - }, - }, - "generation-viii": { - "brilliant-diamond-shining-pearl": { - "front_default": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/", "webp"), - "front_female": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/female/", "webp"), - "front_shiny": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/shiny/", "webp"), - "front_shiny_female": ( - "pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/shiny/female/", - "webp", - ), - "back_default": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/back/", "webp"), - "back_female": ( - "pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/back/female/", - "webp", - ), - "back_shiny": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/back/shiny/", "webp"), - "back_shiny_female": ( - "pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/back/shiny/female/", - "webp", - ), - "icons": { - "front_default": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/icons/", "png"), - }, - }, - "legends-arceus": { - "front_default": ("pokemon/versions/generation-viii/legends-arceus/", "webp"), - "front_female": ("pokemon/versions/generation-viii/legends-arceus/female/", "webp"), - "front_shiny": ("pokemon/versions/generation-viii/legends-arceus/shiny/", "webp"), - "front_shiny_female": ("pokemon/versions/generation-viii/legends-arceus/shiny/female/", "webp"), - "back_default": ("pokemon/versions/generation-viii/legends-arceus/back/", "webp"), - "back_female": ("pokemon/versions/generation-viii/legends-arceus/back/female/", "webp"), - "back_shiny": ("pokemon/versions/generation-viii/legends-arceus/back/shiny/", "webp"), - "back_shiny_female": ("pokemon/versions/generation-viii/legends-arceus/back/shiny/female/", "webp"), - "icons": { - "front_default": ("pokemon/versions/generation-viii/legends-arceus/icons/", "png"), - "front_female": ("pokemon/versions/generation-viii/legends-arceus/icons/female/", "png"), - "front_shiny": ("pokemon/versions/generation-viii/legends-arceus/icons/shiny/", "png"), - "front_shiny_female": ( - "pokemon/versions/generation-viii/legends-arceus/icons/shiny/female/", - "png", - ), - }, - }, - "sword-shield": { - "front_default": ("pokemon/versions/generation-viii/sword-shield/", "webp"), - "front_female": ("pokemon/versions/generation-viii/sword-shield/female/", "webp"), - "front_shiny": ("pokemon/versions/generation-viii/sword-shield/shiny/", "webp"), - "front_shiny_female": ("pokemon/versions/generation-viii/sword-shield/shiny/female/", "webp"), - "back_default": ("pokemon/versions/generation-viii/sword-shield/back/", "webp"), - "back_female": ("pokemon/versions/generation-viii/sword-shield/back/female/", "webp"), - "back_shiny": ("pokemon/versions/generation-viii/sword-shield/back/shiny/", "webp"), - "back_shiny_female": ("pokemon/versions/generation-viii/sword-shield/back/shiny/female/", "webp"), - "icons": { - "front_default": ("pokemon/versions/generation-viii/sword-shield/icons/", "png"), - "front_female": ("pokemon/versions/generation-viii/sword-shield/icons/female/", "png"), - }, - }, - }, - "generation-ix": { - "champions": { - "front_default": ("pokemon/versions/generation-ix/champions/", "webp"), - "front_female": ("pokemon/versions/generation-ix/champions/female/", "webp"), - "front_shiny": ("pokemon/versions/generation-ix/champions/shiny/", "webp"), - "front_shiny_female": ("pokemon/versions/generation-ix/champions/shiny/female/", "webp"), - "back_default": ("pokemon/versions/generation-ix/champions/back/", "webp"), - "back_female": ("pokemon/versions/generation-ix/champions/back/female/", "webp"), - "back_shiny": ("pokemon/versions/generation-ix/champions/back/shiny/", "webp"), - "back_shiny_female": ("pokemon/versions/generation-ix/champions/back/shiny/female/", "webp"), - "icons": { - "front_default": ("pokemon/versions/generation-ix/champions/icons/", "png"), - "front_shiny": ("pokemon/versions/generation-ix/champions/icons/shiny/", "png"), - }, - }, - "legends-za": { - "front_default": ("pokemon/versions/generation-ix/legends-za/", "webp"), - "front_female": ("pokemon/versions/generation-ix/legends-za/female/", "webp"), - "front_shiny": ("pokemon/versions/generation-ix/legends-za/shiny/", "webp"), - "front_shiny_female": ("pokemon/versions/generation-ix/legends-za/shiny/female/", "webp"), - "back_default": ("pokemon/versions/generation-ix/legends-za/back/", "webp"), - "back_female": ("pokemon/versions/generation-ix/legends-za/back/female/", "webp"), - "back_shiny": ("pokemon/versions/generation-ix/legends-za/back/shiny/", "webp"), - "back_shiny_female": ("pokemon/versions/generation-ix/legends-za/back/shiny/female/", "webp"), - "icons": { - "front_default": ("pokemon/versions/generation-ix/legends-za/icons/", "png"), - "front_female": ("pokemon/versions/generation-ix/legends-za/icons/female/", "png"), - "front_shiny": ("pokemon/versions/generation-ix/legends-za/icons/shiny/", "png"), - "front_shiny_female": ("pokemon/versions/generation-ix/legends-za/icons/shiny/female/", "png"), - }, - }, - "scarlet-violet": { - "front_default": ("pokemon/versions/generation-ix/scarlet-violet/", "webp"), - "front_female": ("pokemon/versions/generation-ix/scarlet-violet/female/", "webp"), - "front_shiny": ("pokemon/versions/generation-ix/scarlet-violet/shiny/", "webp"), - "front_shiny_female": ("pokemon/versions/generation-ix/scarlet-violet/shiny/female/", "webp"), - "back_default": ("pokemon/versions/generation-ix/scarlet-violet/back/", "webp"), - "back_female": ("pokemon/versions/generation-ix/scarlet-violet/back/female/", "webp"), - "back_shiny": ("pokemon/versions/generation-ix/scarlet-violet/back/shiny/", "webp"), - "back_shiny_female": ("pokemon/versions/generation-ix/scarlet-violet/back/shiny/female/", "webp"), - "icons": { - "front_default": ("pokemon/versions/generation-ix/scarlet-violet/icons/", "png"), - "front_female": ("pokemon/versions/generation-ix/scarlet-violet/icons/female/", "png"), - }, - }, - }, - }, -} - - -def file_path_or_none(file_name, image_file=True): - if not image_file: - return SOUND_DIR.format(file_name=file_name) if file_name in RESOURCE_CRIES else None - return MEDIA_DIR.format(file_name=file_name) if file_name in RESOURCE_IMAGES else None - - -def with_iter(context, iterable=None): - if iterable is None: - iterable = context - with context: - yield from iterable - - -def load_data(file_name): - # with_iter closes the file when it has finished - return csv.reader(with_iter(open(DATA_LOCATION + file_name, encoding="utf8")), delimiter=",") - - -def clear_table(model): - table_name = model._meta.db_table - model.objects.all().delete() - print("building " + table_name) - # Reset DB auto increments to start at 1 - if DB_VENDOR == "sqlite": - DB_CURSOR.execute("DELETE FROM sqlite_sequence WHERE name = " + "'" + table_name + "'") - else: - DB_CURSOR.execute("SELECT setval(pg_get_serial_sequence(" + "'" + table_name + "'" + ",'id'), 1, false);") - - -def build_generic(model_classes, file_name, csv_record_to_objects): - batches = {} - for model_class in model_classes: - clear_table(model_class) - batches[model_class] = [] # one batch per model class - - csv_data = load_data(file_name) - next(csv_data, None) # skip header - - for csv_record in csv_data: - for obj in csv_record_to_objects(csv_record): - model_class = type(obj) - batches[model_class].append(obj) - - # Limit the batch size - if len(batches[model_class]) > 200: - model_class.objects.bulk_create(batches[model_class]) - batches[model_class] = [] - - for model_class, batch in batches.items(): - model_class.objects.bulk_create(batch) - - -############## -# LANGUAGE # -############## - - -def _build_languages(): - def csv_record_to_objects(info): + +def _build_languages() -> None: + def languages_from_rows(info: list[str]) -> Iterator[Language]: yield Language( id=int(info[0]), iso639=info[1], @@ -632,55 +204,40 @@ def csv_record_to_objects(info): order=info[5], ) - build_generic((Language,), "languages.csv", csv_record_to_objects) + build_generic((Language,), "languages.csv", languages_from_rows) - def csv_record_to_objects(info): + def language_names_from_rows(info: list[str]) -> Iterator[LanguageName]: yield LanguageName(language_id=int(info[0]), local_language_id=int(info[1]), name=info[2]) - build_generic((LanguageName,), "language_names.csv", csv_record_to_objects) + build_generic((LanguageName,), "language_names.csv", language_names_from_rows) -############ -# REGION # -############ - - -def _build_regions(): - def csv_record_to_objects(info): +def _build_regions() -> None: + def regions_from_rows(info: list[str]) -> Iterator[Region]: yield Region(id=int(info[0]), name=info[1]) - build_generic((Region,), "regions.csv", csv_record_to_objects) + build_generic((Region,), "regions.csv", regions_from_rows) - def csv_record_to_objects(info): + def region_names_from_rows(info: list[str]) -> Iterator[RegionName]: yield RegionName(region_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((RegionName,), "region_names.csv", csv_record_to_objects) - + build_generic((RegionName,), "region_names.csv", region_names_from_rows) -################ -# GENERATION # -################ - -def _build_generations(): - def csv_record_to_objects(info): +def _build_generations() -> None: + def generations_from_rows(info: list[str]) -> Iterator[Generation]: yield Generation(id=int(info[0]), region_id=int(info[1]), name=info[2]) - build_generic((Generation,), "generations.csv", csv_record_to_objects) + build_generic((Generation,), "generations.csv", generations_from_rows) - def csv_record_to_objects(info): + def generation_names_from_rows(info: list[str]) -> Iterator[GenerationName]: yield GenerationName(generation_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((GenerationName,), "generation_names.csv", csv_record_to_objects) - + build_generic((GenerationName,), "generation_names.csv", generation_names_from_rows) -############# -# VERSION # -############# - -def _build_versions(): - def csv_record_to_objects(info): +def _build_versions() -> None: + def version_groups_from_rows(info: list[str]) -> Iterator[VersionGroup]: yield VersionGroup( id=int(info[0]), name=info[1], @@ -688,36 +245,33 @@ def csv_record_to_objects(info): order=int(info[3]), ) - build_generic((VersionGroup,), "version_groups.csv", csv_record_to_objects) + build_generic((VersionGroup,), "version_groups.csv", version_groups_from_rows) - def csv_record_to_objects(info): + def version_group_regions_from_rows(info: list[str]) -> Iterator[VersionGroupRegion]: yield VersionGroupRegion(version_group_id=int(info[0]), region_id=int(info[1])) - build_generic((VersionGroupRegion,), "version_group_regions.csv", csv_record_to_objects) + build_generic((VersionGroupRegion,), "version_group_regions.csv", version_group_regions_from_rows) - def csv_record_to_objects(info): + def versions_from_rows(info: list[str]) -> Iterator[Version]: yield Version(id=int(info[0]), version_group_id=int(info[1]), name=info[2]) - build_generic((Version,), "versions.csv", csv_record_to_objects) + build_generic((Version,), "versions.csv", versions_from_rows) - def csv_record_to_objects(info): + def version_names_from_rows(info: list[str]) -> Iterator[VersionName]: yield VersionName(version_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((VersionName,), "version_names.csv", csv_record_to_objects) - - -################## -# DAMAGE CLASS # -################## + build_generic((VersionName,), "version_names.csv", version_names_from_rows) -def _build_damage_classes(): - def csv_record_to_objects(info): +def _build_damage_classes() -> None: + def move_damage_classes_from_rows(info: list[str]) -> Iterator[MoveDamageClass]: yield MoveDamageClass(id=int(info[0]), name=info[1]) - build_generic((MoveDamageClass,), "move_damage_classes.csv", csv_record_to_objects) + build_generic((MoveDamageClass,), "move_damage_classes.csv", move_damage_classes_from_rows) - def csv_record_to_objects(info): + def move_damage_class_prose_from_rows( + info: list[str], + ) -> Iterator[MoveDamageClassName | MoveDamageClassDescription]: yield MoveDamageClassName(move_damage_class_id=int(info[0]), language_id=int(info[1]), name=info[2]) yield MoveDamageClassDescription( move_damage_class_id=int(info[0]), @@ -728,17 +282,12 @@ def csv_record_to_objects(info): build_generic( (MoveDamageClassName, MoveDamageClassDescription), "move_damage_class_prose.csv", - csv_record_to_objects, + move_damage_class_prose_from_rows, ) -########### -# STATS # -########### - - -def _build_stats(): - def csv_record_to_objects(info): +def _build_stats() -> None: + def stats_from_rows(info: list[str]) -> Iterator[Stat]: yield Stat( id=int(info[0]), move_damage_class_id=int(info[1]) if info[1] != "" else None, @@ -747,31 +296,26 @@ def csv_record_to_objects(info): game_index=int(info[4]) if info[4] else 0, ) - build_generic((Stat,), "stats.csv", csv_record_to_objects) + build_generic((Stat,), "stats.csv", stats_from_rows) - def csv_record_to_objects(info): + def stat_names_from_rows(info: list[str]) -> Iterator[StatName]: yield StatName(stat_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((StatName,), "stat_names.csv", csv_record_to_objects) + build_generic((StatName,), "stat_names.csv", stat_names_from_rows) - def csv_record_to_objects(info): + def pokeathlon_stats_from_rows(info: list[str]) -> Iterator[PokeathlonStat]: yield PokeathlonStat(id=int(info[0]), name=info[1]) - build_generic((PokeathlonStat,), "pokeathlon_stats.csv", csv_record_to_objects) + build_generic((PokeathlonStat,), "pokeathlon_stats.csv", pokeathlon_stats_from_rows) - def csv_record_to_objects(info): + def pokeathlon_stat_names_from_rows(info: list[str]) -> Iterator[PokeathlonStatName]: yield PokeathlonStatName(pokeathlon_stat_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((PokeathlonStatName,), "pokeathlon_stat_names.csv", csv_record_to_objects) + build_generic((PokeathlonStatName,), "pokeathlon_stat_names.csv", pokeathlon_stat_names_from_rows) -# ############### -# # ABILITIES # -# ############### - - -def _build_abilities(): - def csv_record_to_objects(info): +def _build_abilities() -> None: + def abilities_from_rows(info: list[str]) -> Iterator[Ability]: yield Ability( id=int(info[0]), name=info[1], @@ -779,19 +323,19 @@ def csv_record_to_objects(info): is_main_series=bool(int(info[3])), ) - build_generic((Ability,), "abilities.csv", csv_record_to_objects) + build_generic((Ability,), "abilities.csv", abilities_from_rows) - def csv_record_to_objects(info): + def ability_names_from_rows(info: list[str]) -> Iterator[AbilityName]: yield AbilityName(ability_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((AbilityName,), "ability_names.csv", csv_record_to_objects) + build_generic((AbilityName,), "ability_names.csv", ability_names_from_rows) - def csv_record_to_objects(info): + def ability_changes_from_rows(info: list[str]) -> Iterator[AbilityChange]: yield AbilityChange(id=int(info[0]), ability_id=int(info[1]), version_group_id=int(info[2])) - build_generic((AbilityChange,), "ability_changelog.csv", csv_record_to_objects) + build_generic((AbilityChange,), "ability_changelog.csv", ability_changes_from_rows) - def csv_record_to_objects(info): + def ability_effect_texts_from_rows(info: list[str]) -> Iterator[AbilityEffectText]: yield AbilityEffectText( ability_id=int(info[0]), language_id=int(info[1]), @@ -799,18 +343,18 @@ def csv_record_to_objects(info): effect=info[3], ) - build_generic((AbilityEffectText,), "ability_prose.csv", csv_record_to_objects) + build_generic((AbilityEffectText,), "ability_prose.csv", ability_effect_texts_from_rows) - def csv_record_to_objects(info): + def ability_change_effect_texts_from_rows(info: list[str]) -> Iterator[AbilityChangeEffectText]: yield AbilityChangeEffectText( ability_change_id=int(info[0]), language_id=int(info[1]), effect=info[2], ) - build_generic((AbilityChangeEffectText,), "ability_changelog_prose.csv", csv_record_to_objects) + build_generic((AbilityChangeEffectText,), "ability_changelog_prose.csv", ability_change_effect_texts_from_rows) - def csv_record_to_objects(info): + def ability_flavor_texts_from_rows(info: list[str]) -> Iterator[AbilityFlavorText]: yield AbilityFlavorText( ability_id=int(info[0]), version_group_id=int(info[1]), @@ -818,96 +362,76 @@ def csv_record_to_objects(info): flavor_text=info[3], ) - build_generic((AbilityFlavorText,), "ability_flavor_text.csv", csv_record_to_objects) - + build_generic((AbilityFlavorText,), "ability_flavor_text.csv", ability_flavor_texts_from_rows) -#################### -# CHARACTERISTIC # -#################### - -def _build_characteristics(): - def csv_record_to_objects(info): +def _build_characteristics() -> None: + def characteristics_from_rows(info: list[str]) -> Iterator[Characteristic]: yield Characteristic(id=int(info[0]), stat_id=int(info[1]), gene_mod_5=int(info[2])) - build_generic((Characteristic,), "characteristics.csv", csv_record_to_objects) + build_generic((Characteristic,), "characteristics.csv", characteristics_from_rows) - def csv_record_to_objects(info): + def characteristic_descriptions_from_rows(info: list[str]) -> Iterator[CharacteristicDescription]: yield CharacteristicDescription( characteristic_id=int(info[0]), language_id=int(info[1]), description=info[2], ) - build_generic((CharacteristicDescription,), "characteristic_text.csv", csv_record_to_objects) - + build_generic((CharacteristicDescription,), "characteristic_text.csv", characteristic_descriptions_from_rows) -############### -# EGG GROUP # -############### - -def _build_egg_groups(): - def csv_record_to_objects(info): +def _build_egg_groups() -> None: + def egg_groups_from_rows(info: list[str]) -> Iterator[EggGroup]: yield EggGroup(id=int(info[0]), name=info[1]) - build_generic((EggGroup,), "egg_groups.csv", csv_record_to_objects) + build_generic((EggGroup,), "egg_groups.csv", egg_groups_from_rows) - def csv_record_to_objects(info): + def egg_group_names_from_rows(info: list[str]) -> Iterator[EggGroupName]: yield EggGroupName(egg_group_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((EggGroupName,), "egg_group_prose.csv", csv_record_to_objects) - - -################# -# GROWTH RATE # -################# + build_generic((EggGroupName,), "egg_group_prose.csv", egg_group_names_from_rows) -def _build_growth_rates(): - def csv_record_to_objects(info): +def _build_growth_rates() -> None: + def growth_rates_from_rows(info: list[str]) -> Iterator[GrowthRate]: yield GrowthRate(id=int(info[0]), name=info[1], formula=info[2]) - build_generic((GrowthRate,), "growth_rates.csv", csv_record_to_objects) + build_generic((GrowthRate,), "growth_rates.csv", growth_rates_from_rows) - def csv_record_to_objects(info): + def growth_rate_descriptions_from_rows(info: list[str]) -> Iterator[GrowthRateDescription]: yield GrowthRateDescription(growth_rate_id=int(info[0]), language_id=int(info[1]), description=info[2]) - build_generic((GrowthRateDescription,), "growth_rate_prose.csv", csv_record_to_objects) - - -# ########### -# # ITEMS # -# ########### + build_generic((GrowthRateDescription,), "growth_rate_prose.csv", growth_rate_descriptions_from_rows) -def _build_items(): - def csv_record_to_objects(info): +def _build_items() -> None: + def currencies_from_rows(info: list[str]) -> Iterator[Currency]: yield Currency(id=int(info[0]), name=info[1]) - build_generic((Currency,), "currencies.csv", csv_record_to_objects) + build_generic((Currency,), "currencies.csv", currencies_from_rows) - def csv_record_to_objects(info): + def currency_names_from_rows(info: list[str]) -> Iterator[CurrencyName]: yield CurrencyName(currency_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((CurrencyName,), "currency_names.csv", csv_record_to_objects) + build_generic((CurrencyName,), "currency_names.csv", currency_names_from_rows) - def csv_record_to_objects(info): + def item_pockets_from_rows(info: list[str]) -> Iterator[ItemPocket]: yield ItemPocket(id=int(info[0]), name=info[1]) - build_generic((ItemPocket,), "item_pockets.csv", csv_record_to_objects) + build_generic((ItemPocket,), "item_pockets.csv", item_pockets_from_rows) - def csv_record_to_objects(info): + def item_pocket_names_from_rows(info: list[str]) -> Iterator[ItemPocketName]: yield ItemPocketName(item_pocket_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((ItemPocketName,), "item_pocket_names.csv", csv_record_to_objects) + build_generic((ItemPocketName,), "item_pocket_names.csv", item_pocket_names_from_rows) - def csv_record_to_objects(info): + def item_fling_effects_from_rows(info: list[str]) -> Iterator[ItemFlingEffect]: yield ItemFlingEffect(id=int(info[0]), name=info[1]) - build_generic((ItemFlingEffect,), "item_fling_effects.csv", csv_record_to_objects) + build_generic((ItemFlingEffect,), "item_fling_effects.csv", item_fling_effects_from_rows) - def csv_record_to_objects(info): + def item_fling_effect_effect_texts_from_rows(info: list[str]) -> Iterator[ItemFlingEffectEffectText]: yield ItemFlingEffectEffectText( item_fling_effect_id=int(info[0]), language_id=int(info[1]), @@ -917,20 +441,20 @@ def csv_record_to_objects(info): build_generic( (ItemFlingEffectEffectText,), "item_fling_effect_prose.csv", - csv_record_to_objects, + item_fling_effect_effect_texts_from_rows, ) - def csv_record_to_objects(info): + def item_categories_from_rows(info: list[str]) -> Iterator[ItemCategory]: yield ItemCategory(id=int(info[0]), item_pocket_id=int(info[1]), name=info[2]) - build_generic((ItemCategory,), "item_categories.csv", csv_record_to_objects) + build_generic((ItemCategory,), "item_categories.csv", item_categories_from_rows) - def csv_record_to_objects(info): + def item_category_names_from_rows(info: list[str]) -> Iterator[ItemCategoryName]: yield ItemCategoryName(item_category_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((ItemCategoryName,), "item_category_prose.csv", csv_record_to_objects) + build_generic((ItemCategoryName,), "item_category_prose.csv", item_category_names_from_rows) - def csv_record_to_objects(info): + def items_from_rows(info: list[str]) -> Iterator[Item]: yield Item( id=int(info[0]), name=info[1], @@ -939,9 +463,9 @@ def csv_record_to_objects(info): item_fling_effect_id=int(info[5]) if info[5] != "" else None, ) - build_generic((Item,), "items.csv", csv_record_to_objects) + build_generic((Item,), "items.csv", items_from_rows) - def csv_record_to_objects(info): + def item_sprites_from_rows(info: list[str]) -> Iterator[ItemSprites]: if re.search(r"^data-card", info[1]): file_name = "data-card.png" elif re.search(r"^tm[0-9]", info[1]): @@ -955,14 +479,14 @@ def csv_record_to_objects(info): sprites = {"default": file_path_or_none(item_sprites.format(file_name))} yield ItemSprites(id=int(info[0]), item_id=int(info[0]), sprites=sprites) - build_generic((ItemSprites,), "items.csv", csv_record_to_objects) + build_generic((ItemSprites,), "items.csv", item_sprites_from_rows) - def csv_record_to_objects(info): + def item_names_from_rows(info: list[str]) -> Iterator[ItemName]: yield ItemName(item_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((ItemName,), "item_names.csv", csv_record_to_objects) + build_generic((ItemName,), "item_names.csv", item_names_from_rows) - def csv_record_to_objects(info): + def item_effect_texts_from_rows(info: list[str]) -> Iterator[ItemEffectText]: yield ItemEffectText( item_id=int(info[0]), language_id=int(info[1]), @@ -970,14 +494,14 @@ def csv_record_to_objects(info): effect=info[3], ) - build_generic((ItemEffectText,), "item_prose.csv", csv_record_to_objects) + build_generic((ItemEffectText,), "item_prose.csv", item_effect_texts_from_rows) - def csv_record_to_objects(info): + def item_game_indexes_from_rows(info: list[str]) -> Iterator[ItemGameIndex]: yield ItemGameIndex(item_id=int(info[0]), generation_id=int(info[1]), game_index=int(info[2])) - build_generic((ItemGameIndex,), "item_game_indices.csv", csv_record_to_objects) + build_generic((ItemGameIndex,), "item_game_indices.csv", item_game_indexes_from_rows) - def csv_record_to_objects(info): + def item_prices_from_rows(info: list[str]) -> Iterator[ItemPrice]: # Keep backward compatibility with 4-column files (no currency_id). has_currency_id = len(info) >= 5 purchase_price_index = 3 if has_currency_id else 2 @@ -991,9 +515,9 @@ def csv_record_to_objects(info): sell_price=int(info[sell_price_index]) if info[sell_price_index] else None, ) - build_generic((ItemPrice,), "item_prices.csv", csv_record_to_objects) + build_generic((ItemPrice,), "item_prices.csv", item_prices_from_rows) - def csv_record_to_objects(info): + def item_flavor_texts_from_rows(info: list[str]) -> Iterator[ItemFlavorText]: yield ItemFlavorText( item_id=int(info[0]), version_group_id=int(info[1]), @@ -1001,14 +525,14 @@ def csv_record_to_objects(info): flavor_text=info[3], ) - build_generic((ItemFlavorText,), "item_flavor_text.csv", csv_record_to_objects) + build_generic((ItemFlavorText,), "item_flavor_text.csv", item_flavor_texts_from_rows) - def csv_record_to_objects(info): + def item_attributes_from_rows(info: list[str]) -> Iterator[ItemAttribute]: yield ItemAttribute(id=int(info[0]), name=info[1]) - build_generic((ItemAttribute,), "item_flags.csv", csv_record_to_objects) + build_generic((ItemAttribute,), "item_flags.csv", item_attributes_from_rows) - def csv_record_to_objects(info): + def item_flag_prose_from_rows(info: list[str]) -> Iterator[ItemAttributeName | ItemAttributeDescription]: yield ItemAttributeName(item_attribute_id=int(info[0]), language_id=int(info[1]), name=info[2]) yield ItemAttributeDescription( item_attribute_id=int(info[0]), @@ -1019,22 +543,17 @@ def csv_record_to_objects(info): build_generic( (ItemAttributeName, ItemAttributeDescription), "item_flag_prose.csv", - csv_record_to_objects, + item_flag_prose_from_rows, ) - def csv_record_to_objects(info): + def item_attribute_maps_from_rows(info: list[str]) -> Iterator[ItemAttributeMap]: yield ItemAttributeMap(item_id=int(info[0]), item_attribute_id=int(info[1])) - build_generic((ItemAttributeMap,), "item_flag_map.csv", csv_record_to_objects) + build_generic((ItemAttributeMap,), "item_flag_map.csv", item_attribute_maps_from_rows) -########### -# TYPES # -########### - - -def _build_types(): - def csv_record_to_objects(info): +def _build_types() -> None: + def types_from_rows(info: list[str]) -> Iterator[Type]: yield Type( id=int(info[0]), name=info[1], @@ -1042,28 +561,28 @@ def csv_record_to_objects(info): move_damage_class_id=int(info[3]) if info[3] != "" else None, ) - build_generic((Type,), "types.csv", csv_record_to_objects) + build_generic((Type,), "types.csv", types_from_rows) - def csv_record_to_objects(info): + def type_names_from_rows(info: list[str]) -> Iterator[TypeName]: yield TypeName(type_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((TypeName,), "type_names.csv", csv_record_to_objects) + build_generic((TypeName,), "type_names.csv", type_names_from_rows) - def csv_record_to_objects(info): + def type_game_indexes_from_rows(info: list[str]) -> Iterator[TypeGameIndex]: yield TypeGameIndex(type_id=int(info[0]), generation_id=int(info[1]), game_index=int(info[2])) - build_generic((TypeGameIndex,), "type_game_indices.csv", csv_record_to_objects) + build_generic((TypeGameIndex,), "type_game_indices.csv", type_game_indexes_from_rows) - def csv_record_to_objects(info): + def type_efficacies_from_rows(info: list[str]) -> Iterator[TypeEfficacy]: yield TypeEfficacy( damage_type_id=int(info[0]), target_type_id=int(info[1]), damage_factor=int(info[2]), ) - build_generic((TypeEfficacy,), "type_efficacy.csv", csv_record_to_objects) + build_generic((TypeEfficacy,), "type_efficacy.csv", type_efficacies_from_rows) - def csv_record_to_objects(info): + def type_efficacies_past_from_rows(info: list[str]) -> Iterator[TypeEfficacyPast]: yield TypeEfficacyPast( damage_type_id=int(info[0]), target_type_id=int(info[1]), @@ -1071,34 +590,11 @@ def csv_record_to_objects(info): generation_id=int(info[3]), ) - build_generic((TypeEfficacyPast,), "type_efficacy_past.csv", csv_record_to_objects) - - def csv_record_to_objects(info): - game_map = { - "generation-iii": [ - "colosseum", - "emerald", - "firered-leafgreen", - "ruby-sapphire", - "xd", - ], - "generation-iv": ["diamond-pearl", "heartgold-soulsilver", "platinum"], - "generation-v": ["black-2-white-2", "black-white"], - "generation-vi": ["omega-ruby-alpha-sapphire", "x-y"], - "generation-vii": [ - "lets-go-pikachu-lets-go-eevee", - "sun-moon", - "ultra-sun-ultra-moon", - ], - "generation-viii": [ - "brilliant-diamond-shining-pearl", - "legends-arceus", - "sword-shield", - ], - "generation-ix": ["scarlet-violet"], - } + build_generic((TypeEfficacyPast,), "type_efficacy_past.csv", type_efficacies_past_from_rows) + + def type_sprites_from_rows(info: list[str]) -> Iterator[TypeSprites]: sprites = {} - for generation, games in game_map.items(): + for generation, games in TYPE_SPRITE_GAME_MAP.items(): for game in games: if generation not in sprites: sprites[generation] = {} @@ -1109,21 +605,16 @@ def csv_record_to_objects(info): yield TypeSprites(type_id=int(info[0]), sprites=sprites) - build_generic((TypeSprites,), "types.csv", csv_record_to_objects) - + build_generic((TypeSprites,), "types.csv", type_sprites_from_rows) -############# -# CONTEST # -############# - -def _build_contests(): - def csv_record_to_objects(info): +def _build_contests() -> None: + def contest_types_from_rows(info: list[str]) -> Iterator[ContestType]: yield ContestType(id=int(info[0]), name=info[1]) - build_generic((ContestType,), "contest_types.csv", csv_record_to_objects) + build_generic((ContestType,), "contest_types.csv", contest_types_from_rows) - def csv_record_to_objects(info): + def contest_type_names_from_rows(info: list[str]) -> Iterator[ContestTypeName]: yield ContestTypeName( contest_type_id=int(info[0]), language_id=int(info[1]), @@ -1132,14 +623,14 @@ def csv_record_to_objects(info): color=info[4], ) - build_generic((ContestTypeName,), "contest_type_names.csv", csv_record_to_objects) + build_generic((ContestTypeName,), "contest_type_names.csv", contest_type_names_from_rows) - def csv_record_to_objects(info): + def contest_effects_from_rows(info: list[str]) -> Iterator[ContestEffect]: yield ContestEffect(id=int(info[0]), appeal=int(info[1]), jam=int(info[2])) - build_generic((ContestEffect,), "contest_effects.csv", csv_record_to_objects) + build_generic((ContestEffect,), "contest_effects.csv", contest_effects_from_rows) - def csv_record_to_objects(info): + def contest_effect_prose_from_rows(info: list[str]) -> Iterator[ContestEffectEffectText | ContestEffectFlavorText]: yield ContestEffectEffectText(contest_effect_id=int(info[0]), language_id=int(info[1]), effect=info[3]) yield ContestEffectFlavorText( contest_effect_id=int(info[0]), @@ -1150,15 +641,15 @@ def csv_record_to_objects(info): build_generic( (ContestEffectEffectText, ContestEffectFlavorText), "contest_effect_prose.csv", - csv_record_to_objects, + contest_effect_prose_from_rows, ) - def csv_record_to_objects(info): + def super_contest_effects_from_rows(info: list[str]) -> Iterator[SuperContestEffect]: yield SuperContestEffect(id=int(info[0]), appeal=int(info[1])) - build_generic((SuperContestEffect,), "super_contest_effects.csv", csv_record_to_objects) + build_generic((SuperContestEffect,), "super_contest_effects.csv", super_contest_effects_from_rows) - def csv_record_to_objects(info): + def super_contest_effect_flavor_texts_from_rows(info: list[str]) -> Iterator[SuperContestEffectFlavorText]: yield SuperContestEffectFlavorText( super_contest_effect_id=int(info[0]), language_id=int(info[1]), @@ -1168,22 +659,17 @@ def csv_record_to_objects(info): build_generic( (SuperContestEffectFlavorText,), "super_contest_effect_prose.csv", - csv_record_to_objects, + super_contest_effect_flavor_texts_from_rows, ) -########### -# MOVES # -########### - - -def _build_moves(): - def csv_record_to_objects(info): +def _build_moves() -> None: + def move_effects_from_rows(info: list[str]) -> Iterator[MoveEffect]: yield MoveEffect(id=int(info[0])) - build_generic((MoveEffect,), "move_effects.csv", csv_record_to_objects) + build_generic((MoveEffect,), "move_effects.csv", move_effects_from_rows) - def csv_record_to_objects(info): + def move_effect_effect_texts_from_rows(info: list[str]) -> Iterator[MoveEffectEffectText]: yield MoveEffectEffectText( move_effect_id=int(info[0]), language_id=int(info[1]), @@ -1191,14 +677,14 @@ def csv_record_to_objects(info): effect=info[3], ) - build_generic((MoveEffectEffectText,), "move_effect_prose.csv", csv_record_to_objects) + build_generic((MoveEffectEffectText,), "move_effect_prose.csv", move_effect_effect_texts_from_rows) - def csv_record_to_objects(info): + def move_effect_changes_from_rows(info: list[str]) -> Iterator[MoveEffectChange]: yield MoveEffectChange(id=int(info[0]), move_effect_id=int(info[1]), version_group_id=int(info[2])) - build_generic((MoveEffectChange,), "move_effect_changelog.csv", csv_record_to_objects) + build_generic((MoveEffectChange,), "move_effect_changelog.csv", move_effect_changes_from_rows) - def csv_record_to_objects(info): + def move_effect_change_effect_texts_from_rows(info: list[str]) -> Iterator[MoveEffectChangeEffectText]: yield MoveEffectChangeEffectText( move_effect_change_id=int(info[0]), language_id=int(info[1]), @@ -1208,24 +694,26 @@ def csv_record_to_objects(info): build_generic( (MoveEffectChangeEffectText,), "move_effect_changelog_prose.csv", - csv_record_to_objects, + move_effect_change_effect_texts_from_rows, ) - def csv_record_to_objects(info): + def move_learn_methods_from_rows(info: list[str]) -> Iterator[MoveLearnMethod]: yield MoveLearnMethod(id=int(info[0]), name=info[1]) - build_generic((MoveLearnMethod,), "pokemon_move_methods.csv", csv_record_to_objects) + build_generic((MoveLearnMethod,), "pokemon_move_methods.csv", move_learn_methods_from_rows) - def csv_record_to_objects(info): + def version_group_move_learn_methods_from_rows(info: list[str]) -> Iterator[VersionGroupMoveLearnMethod]: yield VersionGroupMoveLearnMethod(version_group_id=int(info[0]), move_learn_method_id=int(info[1])) build_generic( (VersionGroupMoveLearnMethod,), "version_group_pokemon_move_methods.csv", - csv_record_to_objects, + version_group_move_learn_methods_from_rows, ) - def csv_record_to_objects(info): + def pokemon_move_method_prose_from_rows( + info: list[str], + ) -> Iterator[MoveLearnMethodName | MoveLearnMethodDescription]: yield MoveLearnMethodName(move_learn_method_id=int(info[0]), language_id=int(info[1]), name=info[2]) yield MoveLearnMethodDescription( move_learn_method_id=int(info[0]), @@ -1236,25 +724,25 @@ def csv_record_to_objects(info): build_generic( (MoveLearnMethodName, MoveLearnMethodDescription), "pokemon_move_method_prose.csv", - csv_record_to_objects, + pokemon_move_method_prose_from_rows, ) - def csv_record_to_objects(info): + def move_targets_from_rows(info: list[str]) -> Iterator[MoveTarget]: yield MoveTarget(id=int(info[0]), name=info[1]) - build_generic((MoveTarget,), "move_targets.csv", csv_record_to_objects) + build_generic((MoveTarget,), "move_targets.csv", move_targets_from_rows) - def csv_record_to_objects(info): + def move_target_prose_from_rows(info: list[str]) -> Iterator[MoveTargetName | MoveTargetDescription]: yield MoveTargetName(move_target_id=int(info[0]), language_id=int(info[1]), name=info[2]) yield MoveTargetDescription(move_target_id=int(info[0]), language_id=int(info[1]), description=info[3]) build_generic( (MoveTargetName, MoveTargetDescription), "move_target_prose.csv", - csv_record_to_objects, + move_target_prose_from_rows, ) - def csv_record_to_objects(info): + def moves_from_rows(info: list[str]) -> Iterator[Move]: yield Move( id=int(info[0]), name=info[1], @@ -1273,14 +761,14 @@ def csv_record_to_objects(info): super_contest_effect_id=int(info[14]) if info[14] != "" else None, ) - build_generic((Move,), "moves.csv", csv_record_to_objects) + build_generic((Move,), "moves.csv", moves_from_rows) - def csv_record_to_objects(info): + def move_names_from_rows(info: list[str]) -> Iterator[MoveName]: yield MoveName(move_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((MoveName,), "move_names.csv", csv_record_to_objects) + build_generic((MoveName,), "move_names.csv", move_names_from_rows) - def csv_record_to_objects(info): + def move_flavor_texts_from_rows(info: list[str]) -> Iterator[MoveFlavorText]: yield MoveFlavorText( move_id=int(info[0]), version_group_id=int(info[1]), @@ -1288,11 +776,11 @@ def csv_record_to_objects(info): flavor_text=info[3], ) - build_generic((MoveFlavorText,), "move_flavor_text.csv", csv_record_to_objects) + build_generic((MoveFlavorText,), "move_flavor_text.csv", move_flavor_texts_from_rows) existing_effect_ids = set(MoveEffect.objects.values_list("pk", flat=True)) - def csv_record_to_objects(info): + def move_changes_from_rows(info: list[str]) -> Iterator[MoveChange]: effect_id = int(info[8]) if info[8] != "" else None if effect_id not in existing_effect_ids: effect_id = None @@ -1308,29 +796,29 @@ def csv_record_to_objects(info): move_effect_chance=int(info[9]) if info[9] != "" else None, ) - build_generic((MoveChange,), "move_changelog.csv", csv_record_to_objects) + build_generic((MoveChange,), "move_changelog.csv", move_changes_from_rows) - def csv_record_to_objects(info): + def move_battle_styles_from_rows(info: list[str]) -> Iterator[MoveBattleStyle]: yield MoveBattleStyle(id=int(info[0]), name=info[1]) - build_generic((MoveBattleStyle,), "move_battle_styles.csv", csv_record_to_objects) + build_generic((MoveBattleStyle,), "move_battle_styles.csv", move_battle_styles_from_rows) - def csv_record_to_objects(info): + def move_battle_style_names_from_rows(info: list[str]) -> Iterator[MoveBattleStyleName]: yield MoveBattleStyleName(move_battle_style_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((MoveBattleStyleName,), "move_battle_style_prose.csv", csv_record_to_objects) + build_generic((MoveBattleStyleName,), "move_battle_style_prose.csv", move_battle_style_names_from_rows) - def csv_record_to_objects(info): + def move_attributes_from_rows(info: list[str]) -> Iterator[MoveAttribute]: yield MoveAttribute(id=int(info[0]), name=info[1]) - build_generic((MoveAttribute,), "move_flags.csv", csv_record_to_objects) + build_generic((MoveAttribute,), "move_flags.csv", move_attributes_from_rows) - def csv_record_to_objects(info): + def move_attribute_maps_from_rows(info: list[str]) -> Iterator[MoveAttributeMap]: yield MoveAttributeMap(move_id=int(info[0]), move_attribute_id=int(info[1])) - build_generic((MoveAttributeMap,), "move_flag_map.csv", csv_record_to_objects) + build_generic((MoveAttributeMap,), "move_flag_map.csv", move_attribute_maps_from_rows) - def csv_record_to_objects(info): + def move_flag_prose_from_rows(info: list[str]) -> Iterator[MoveAttributeName | MoveAttributeDescription]: yield MoveAttributeName(move_attribute_id=int(info[0]), language_id=int(info[1]), name=info[2]) yield MoveAttributeDescription( move_attribute_id=int(info[0]), @@ -1341,25 +829,25 @@ def csv_record_to_objects(info): build_generic( (MoveAttributeName, MoveAttributeDescription), "move_flag_prose.csv", - csv_record_to_objects, + move_flag_prose_from_rows, ) - def csv_record_to_objects(info): + def move_meta_ailments_from_rows(info: list[str]) -> Iterator[MoveMetaAilment]: yield MoveMetaAilment(id=int(info[0]), name=info[1]) - build_generic((MoveMetaAilment,), "move_meta_ailments.csv", csv_record_to_objects) + build_generic((MoveMetaAilment,), "move_meta_ailments.csv", move_meta_ailments_from_rows) - def csv_record_to_objects(info): + def move_meta_ailment_names_from_rows(info: list[str]) -> Iterator[MoveMetaAilmentName]: yield MoveMetaAilmentName(move_meta_ailment_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((MoveMetaAilmentName,), "move_meta_ailment_names.csv", csv_record_to_objects) + build_generic((MoveMetaAilmentName,), "move_meta_ailment_names.csv", move_meta_ailment_names_from_rows) - def csv_record_to_objects(info): + def move_meta_categories_from_rows(info: list[str]) -> Iterator[MoveMetaCategory]: yield MoveMetaCategory(id=int(info[0]), name=info[1]) - build_generic((MoveMetaCategory,), "move_meta_categories.csv", csv_record_to_objects) + build_generic((MoveMetaCategory,), "move_meta_categories.csv", move_meta_categories_from_rows) - def csv_record_to_objects(info): + def move_meta_category_descriptions_from_rows(info: list[str]) -> Iterator[MoveMetaCategoryDescription]: yield MoveMetaCategoryDescription( move_meta_category_id=int(info[0]), language_id=int(info[1]), @@ -1369,10 +857,10 @@ def csv_record_to_objects(info): build_generic( (MoveMetaCategoryDescription,), "move_meta_category_prose.csv", - csv_record_to_objects, + move_meta_category_descriptions_from_rows, ) - def csv_record_to_objects(info): + def move_metadata_from_rows(info: list[str]) -> Iterator[MoveMeta]: yield MoveMeta( move_id=int(info[0]), move_meta_category_id=int(info[1]), @@ -1389,46 +877,44 @@ def csv_record_to_objects(info): stat_chance=int(info[12]) if info[12] != "" else None, ) - build_generic((MoveMeta,), "move_meta.csv", csv_record_to_objects) + build_generic((MoveMeta,), "move_meta.csv", move_metadata_from_rows) - def csv_record_to_objects(info): + def move_meta_stat_changes_from_rows(info: list[str]) -> Iterator[MoveMetaStatChange]: yield MoveMetaStatChange(move_id=int(info[0]), stat_id=int(info[1]), change=int(info[2])) - build_generic((MoveMetaStatChange,), "move_meta_stat_changes.csv", csv_record_to_objects) + build_generic((MoveMetaStatChange,), "move_meta_stat_changes.csv", move_meta_stat_changes_from_rows) - def csv_record_to_objects(info): + def contest_combos_from_rows(info: list[str]) -> Iterator[ContestCombo]: yield ContestCombo(first_move_id=int(info[0]), second_move_id=int(info[1])) - build_generic((ContestCombo,), "contest_combos.csv", csv_record_to_objects) + build_generic((ContestCombo,), "contest_combos.csv", contest_combos_from_rows) - def csv_record_to_objects(info): + def super_contest_combos_from_rows(info: list[str]) -> Iterator[SuperContestCombo]: yield SuperContestCombo(first_move_id=int(info[0]), second_move_id=int(info[1])) - build_generic((SuperContestCombo,), "super_contest_combos.csv", csv_record_to_objects) - + build_generic((SuperContestCombo,), "super_contest_combos.csv", super_contest_combos_from_rows) -############# -# BERRIES # -############# - -def _build_berries(): - def csv_record_to_objects(info): +def _build_berries() -> None: + def berry_firmnesses_from_rows(info: list[str]) -> Iterator[BerryFirmness]: yield BerryFirmness(id=int(info[0]), name=info[1]) - build_generic((BerryFirmness,), "berry_firmness.csv", csv_record_to_objects) + build_generic((BerryFirmness,), "berry_firmness.csv", berry_firmnesses_from_rows) - def csv_record_to_objects(info): + def berry_firmness_names_from_rows(info: list[str]) -> Iterator[BerryFirmnessName]: yield BerryFirmnessName(berry_firmness_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((BerryFirmnessName,), "berry_firmness_names.csv", csv_record_to_objects) + build_generic((BerryFirmnessName,), "berry_firmness_names.csv", berry_firmness_names_from_rows) + + item_names = dict(Item.objects.values_list("pk", "name")) - def csv_record_to_objects(info): - item = Item.objects.get(pk=int(info[1])) + def berries_from_rows(info: list[str]) -> Iterator[Berry]: + item_id = int(info[1]) + item_name = item_names[item_id] if item_id in item_names else Item.objects.get(pk=item_id).name yield Berry( id=int(info[0]), - item_id=int(info[1]), - name=item.name.split("-berry")[0], + item_id=item_id, + name=item_name.split("-berry")[0], berry_firmness_id=int(info[2]) if info[2] else None, natural_gift_power=int(info[3]) if info[3] else None, natural_gift_type_id=int(info[4]) if info[4] else None, @@ -1439,76 +925,92 @@ def csv_record_to_objects(info): smoothness=int(info[9]) if info[9] else None, ) - build_generic((Berry,), "berries.csv", csv_record_to_objects) + build_generic((Berry,), "berries.csv", berries_from_rows) + + contest_type_ids = set(ContestType.objects.values_list("pk", flat=True)) + contest_flavors: dict[int, list[str]] = {} + english_flavors = ContestTypeName.objects.filter(language_id=9).values_list("contest_type_id", "flavor") + for contest_type_id, flavor in english_flavors: + contest_flavors.setdefault(contest_type_id, []).append(flavor) - def csv_record_to_objects(info): - # Get the english name for this contest type - contest_type_name = ContestTypeName.objects.get(contest_type_id=int(info[0]), language_id=9) + def berry_flavors_from_rows(info: list[str]) -> Iterator[BerryFlavor]: + contest_type_id = int(info[0]) + flavors = contest_flavors.get(contest_type_id, []) + if len(flavors) == 1: + flavor = flavors[0] + else: + # Preserve Django's missing and duplicate reference errors. + flavor = ContestTypeName.objects.get(contest_type_id=contest_type_id, language_id=9).flavor + if contest_type_id not in contest_type_ids: + ContestType.objects.get(pk=contest_type_id) yield BerryFlavor( - id=int(info[0]), - name=contest_type_name.flavor.lower(), - contest_type=ContestType.objects.get(pk=int(info[0])), + id=contest_type_id, + name=flavor.lower(), + contest_type_id=contest_type_id, ) - # This is not an error - build_generic((BerryFlavor,), "contest_types.csv", csv_record_to_objects) + build_generic((BerryFlavor,), "contest_types.csv", berry_flavors_from_rows) - def csv_record_to_objects(info): + def berry_flavor_names_from_rows(info: list[str]) -> Iterator[BerryFlavorName]: yield BerryFlavorName(berry_flavor_id=int(info[0]), language_id=int(info[1]), name=info[3]) - # This is not an error - build_generic((BerryFlavorName,), "contest_type_names.csv", csv_record_to_objects) + build_generic((BerryFlavorName,), "contest_type_names.csv", berry_flavor_names_from_rows) - def csv_record_to_objects(info): + def berry_flavor_maps_from_rows(info: list[str]) -> Iterator[BerryFlavorMap]: yield BerryFlavorMap(berry_id=int(info[0]), berry_flavor_id=int(info[1]), potency=int(info[2])) - # This is not an error - build_generic((BerryFlavorMap,), "berry_flavors.csv", csv_record_to_objects) + build_generic((BerryFlavorMap,), "berry_flavors.csv", berry_flavor_maps_from_rows) -############ -# NATURE # -############ +def _build_natures() -> None: + stat_ids = set(Stat.objects.values_list("pk", flat=True)) + flavor_ids = set(BerryFlavor.objects.values_list("pk", flat=True)) - -def _build_natures(): - def csv_record_to_objects(info): - decreased_stat = None - increased_stat = None - hates_flavor = None - likes_flavor = None + def natures_from_rows(info: list[str]) -> Iterator[Nature]: + decreased_stat_id = None + increased_stat_id = None + hates_flavor_id = None + likes_flavor_id = None if info[2] != info[3]: - decreased_stat = Stat.objects.get(pk=int(info[2])) - increased_stat = Stat.objects.get(pk=int(info[3])) + decreased_stat_id = int(info[2]) + if decreased_stat_id not in stat_ids: + Stat.objects.get(pk=decreased_stat_id) + increased_stat_id = int(info[3]) + if increased_stat_id not in stat_ids: + Stat.objects.get(pk=increased_stat_id) if info[4] != info[5]: - hates_flavor = BerryFlavor.objects.get(pk=int(info[4])) - likes_flavor = BerryFlavor.objects.get(pk=int(info[5])) + hates_flavor_id = int(info[4]) + if hates_flavor_id not in flavor_ids: + BerryFlavor.objects.get(pk=hates_flavor_id) + likes_flavor_id = int(info[5]) + if likes_flavor_id not in flavor_ids: + BerryFlavor.objects.get(pk=likes_flavor_id) yield Nature( id=int(info[0]), name=info[1], - decreased_stat=decreased_stat, - increased_stat=increased_stat, - hates_flavor=hates_flavor, - likes_flavor=likes_flavor, + decreased_stat_id=decreased_stat_id, + increased_stat_id=increased_stat_id, + hates_flavor_id=hates_flavor_id, + likes_flavor_id=likes_flavor_id, game_index=info[6], ) - build_generic((Nature,), "natures.csv", csv_record_to_objects) + build_generic((Nature,), "natures.csv", natures_from_rows) - def csv_record_to_objects(info): + def nature_names_from_rows(info: list[str]) -> Iterator[NatureName]: yield NatureName(nature_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((NatureName,), "nature_names.csv", csv_record_to_objects) + build_generic((NatureName,), "nature_names.csv", nature_names_from_rows) - def csv_record_to_objects(info): + def nature_pokeathlon_stats_from_rows(info: list[str]) -> Iterator[NaturePokeathlonStat]: yield NaturePokeathlonStat(nature_id=(info[0]), pokeathlon_stat_id=(info[1]), max_change=info[2]) - build_generic((NaturePokeathlonStat,), "nature_pokeathlon_stats.csv", csv_record_to_objects) + build_generic((NaturePokeathlonStat,), "nature_pokeathlon_stats.csv", nature_pokeathlon_stats_from_rows) - def csv_record_to_objects(info): + def nature_battle_style_preferences_from_rows(info: list[str]) -> Iterator[NatureBattleStylePreference]: yield NatureBattleStylePreference( nature_id=int(info[0]), move_battle_style_id=int(info[1]), @@ -1519,41 +1021,26 @@ def csv_record_to_objects(info): build_generic( (NatureBattleStylePreference,), "nature_battle_style_preferences.csv", - csv_record_to_objects, + nature_battle_style_preferences_from_rows, ) -########### -# GENDER # -########### - - -def _build_genders(): - def csv_record_to_objects(info): +def _build_genders() -> None: + def genders_from_rows(info: list[str]) -> Iterator[Gender]: yield Gender(id=int(info[0]), name=info[1]) - build_generic((Gender,), "genders.csv", csv_record_to_objects) - - -################ -# EXPERIENCE # -################ + build_generic((Gender,), "genders.csv", genders_from_rows) -def _build_experiences(): - def csv_record_to_objects(info): +def _build_experiences() -> None: + def experiences_from_rows(info: list[str]) -> Iterator[Experience]: yield Experience(growth_rate_id=int(info[0]), level=int(info[1]), experience=int(info[2])) - build_generic((Experience,), "experience.csv", csv_record_to_objects) + build_generic((Experience,), "experience.csv", experiences_from_rows) -############## -# MACHINES # -############## - - -def _build_machines(): - def csv_record_to_objects(info): +def _build_machines() -> None: + def machines_from_rows(info: list[str]) -> Iterator[Machine]: yield Machine( machine_number=int(info[0]), version_group_id=int(info[1]), @@ -1561,34 +1048,29 @@ def csv_record_to_objects(info): move_id=int(info[3]), ) - build_generic((Machine,), "machines.csv", csv_record_to_objects) - + build_generic((Machine,), "machines.csv", machines_from_rows) -############### -# EVOLUTION # -############### - -def _build_evolutions(): - def csv_record_to_objects(info): +def _build_evolutions() -> None: + def evolution_chains_from_rows(info: list[str]) -> Iterator[EvolutionChain]: yield EvolutionChain( id=int(info[0]), baby_trigger_item_id=int(info[1]) if info[1] != "" else None, ) - build_generic((EvolutionChain,), "evolution_chains.csv", csv_record_to_objects) + build_generic((EvolutionChain,), "evolution_chains.csv", evolution_chains_from_rows) - def csv_record_to_objects(info): + def evolution_triggers_from_rows(info: list[str]) -> Iterator[EvolutionTrigger]: yield EvolutionTrigger(id=int(info[0]), name=info[1]) - build_generic((EvolutionTrigger,), "evolution_triggers.csv", csv_record_to_objects) + build_generic((EvolutionTrigger,), "evolution_triggers.csv", evolution_triggers_from_rows) - def csv_record_to_objects(info): + def evolution_trigger_names_from_rows(info: list[str]) -> Iterator[EvolutionTriggerName]: yield EvolutionTriggerName(evolution_trigger_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((EvolutionTriggerName,), "evolution_trigger_prose.csv", csv_record_to_objects) + build_generic((EvolutionTriggerName,), "evolution_trigger_prose.csv", evolution_trigger_names_from_rows) - def csv_record_to_objects(info): + def evolution_variables_from_rows(info: list[str]) -> Iterator[EvolutionVariable]: yield EvolutionVariable( id=int(info[0]), name=info[1], @@ -1598,9 +1080,11 @@ def csv_record_to_objects(info): source=info[5], ) - build_generic((EvolutionVariable,), "evolution_variables.csv", csv_record_to_objects) + build_generic((EvolutionVariable,), "evolution_variables.csv", evolution_variables_from_rows) - def csv_record_to_objects(info): + def evolution_variable_prose_from_rows( + info: list[str], + ) -> Iterator[EvolutionVariableName | EvolutionVariableDescription]: yield EvolutionVariableName( evolution_variable_id=int(info[0]), language_id=int(info[1]), @@ -1615,17 +1099,12 @@ def csv_record_to_objects(info): build_generic( (EvolutionVariableName, EvolutionVariableDescription), "evolution_variable_prose.csv", - csv_record_to_objects, + evolution_variable_prose_from_rows, ) -############# -# POKEDEX # -############# - - -def _build_pokedexes(): - def csv_record_to_objects(info): +def _build_pokedexes() -> None: + def pokedexes_from_rows(info: list[str]) -> Iterator[Pokedex]: yield Pokedex( id=int(info[0]), region_id=int(info[1]) if info[1] != "" else None, @@ -1633,136 +1112,83 @@ def csv_record_to_objects(info): is_main_series=bool(int(info[3])), ) - build_generic((Pokedex,), "pokedexes.csv", csv_record_to_objects) + build_generic((Pokedex,), "pokedexes.csv", pokedexes_from_rows) - def csv_record_to_objects(info): + def pokedex_prose_from_rows(info: list[str]) -> Iterator[PokedexName | PokedexDescription]: yield PokedexName(pokedex_id=int(info[0]), language_id=int(info[1]), name=info[2]) yield PokedexDescription(pokedex_id=int(info[0]), language_id=int(info[1]), description=info[3]) - build_generic((PokedexName, PokedexDescription), "pokedex_prose.csv", csv_record_to_objects) + build_generic((PokedexName, PokedexDescription), "pokedex_prose.csv", pokedex_prose_from_rows) - def csv_record_to_objects(info): + def pokedex_version_groups_from_rows(info: list[str]) -> Iterator[PokedexVersionGroup]: yield PokedexVersionGroup(pokedex_id=int(info[0]), version_group_id=int(info[1])) - build_generic((PokedexVersionGroup,), "pokedex_version_groups.csv", csv_record_to_objects) - + build_generic((PokedexVersionGroup,), "pokedex_version_groups.csv", pokedex_version_groups_from_rows) -############## -# LOCATION # -############## - -def _build_locations(): - def csv_record_to_objects(info): +def _build_locations() -> None: + def locations_from_rows(info: list[str]) -> Iterator[Location]: yield Location( id=int(info[0]), region_id=int(info[1]) if info[1] != "" else None, name=info[2], ) - build_generic((Location,), "locations.csv", csv_record_to_objects) + build_generic((Location,), "locations.csv", locations_from_rows) - def csv_record_to_objects(info): + def location_names_from_rows(info: list[str]) -> Iterator[LocationName]: yield LocationName(location_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((LocationName,), "location_names.csv", csv_record_to_objects) + build_generic((LocationName,), "location_names.csv", location_names_from_rows) - def csv_record_to_objects(info): + def location_game_indexes_from_rows(info: list[str]) -> Iterator[LocationGameIndex]: yield LocationGameIndex( location_id=int(info[0]), generation_id=int(info[1]), game_index=int(info[2]), ) - build_generic((LocationGameIndex,), "location_game_indices.csv", csv_record_to_objects) + build_generic((LocationGameIndex,), "location_game_indices.csv", location_game_indexes_from_rows) + + location_names = dict(Location.objects.values_list("pk", "name")) - def csv_record_to_objects(info): - location = Location.objects.get(pk=int(info[1])) + def location_areas_from_rows(info: list[str]) -> Iterator[LocationArea]: + location_id = int(info[1]) + location_name = ( + location_names[location_id] if location_id in location_names else Location.objects.get(pk=location_id).name + ) yield LocationArea( id=int(info[0]), - location_id=int(info[1]), + location_id=location_id, game_index=int(info[2]), - name=(f"{location.name}-{info[3]}" if info[3] else "{}-{}".format(location.name, "area")), + name=(f"{location_name}-{info[3]}" if info[3] else "{}-{}".format(location_name, "area")), ) - build_generic((LocationArea,), "location_areas.csv", csv_record_to_objects) + build_generic((LocationArea,), "location_areas.csv", location_areas_from_rows) - def csv_record_to_objects(info): + def location_area_names_from_rows(info: list[str]) -> Iterator[LocationAreaName]: yield LocationAreaName(location_area_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((LocationAreaName,), "location_area_prose.csv", csv_record_to_objects) - - -############# -# POKEMON # -############# - - -def _resolve_sprite_config(config: dict[str, Any], lookup: Callable[[str, str], str | None]) -> dict[str, Any]: - resolved: dict[str, Any] = {} - for name, value in config.items(): - if isinstance(value, dict): - resolved[name] = _resolve_sprite_config(value, lookup) - else: - path, extension = value - resolved[name] = lookup(path, extension) - return resolved - - -def _pokemon_sprite_lookup(info: list[str]) -> Callable[[str, str], str | None]: - pokemon_id, identifier, species_id = info[:3] - file_names = [pokemon_id] - if "-" in identifier: - file_names.append(f"{species_id}-{identifier.split('-', 1)[1]}") - - def lookup(path: str, extension: str) -> str | None: - for file_name in file_names: - sprite = file_path_or_none(f"{path}{file_name}.{extension}") - if sprite: - return sprite - return None - - return lookup + build_generic((LocationAreaName,), "location_area_prose.csv", location_area_names_from_rows) -def _pokemon_form_sprite_lookup(info: list[str]) -> Callable[[str, str], str | None]: - form_identifier = info[2] - pokemon_id = int(info[3]) - is_default = info[5] == "1" - - file_names: list[str] = [] - if form_identifier: - file_names.append(f"{pokemon_id}-{form_identifier}") - if is_default or not form_identifier: - file_names.append(str(pokemon_id)) - - def lookup(path: str, extension: str) -> str | None: - for file_name in file_names: - sprite = file_path_or_none(f"{path}{file_name}.{extension}") - if sprite: - return sprite - return None - - return lookup - - -def _build_pokemons(): - def csv_record_to_objects(info): +def _build_pokemons() -> None: + def pokemon_colors_from_rows(info: list[str]) -> Iterator[PokemonColor]: yield PokemonColor(id=int(info[0]), name=info[1]) - build_generic((PokemonColor,), "pokemon_colors.csv", csv_record_to_objects) + build_generic((PokemonColor,), "pokemon_colors.csv", pokemon_colors_from_rows) - def csv_record_to_objects(info): + def pokemon_color_names_from_rows(info: list[str]) -> Iterator[PokemonColorName]: yield PokemonColorName(pokemon_color_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((PokemonColorName,), "pokemon_color_names.csv", csv_record_to_objects) + build_generic((PokemonColorName,), "pokemon_color_names.csv", pokemon_color_names_from_rows) - def csv_record_to_objects(info): + def pokemon_shapes_from_rows(info: list[str]) -> Iterator[PokemonShape]: yield PokemonShape(id=int(info[0]), name=info[1]) - build_generic((PokemonShape,), "pokemon_shapes.csv", csv_record_to_objects) + build_generic((PokemonShape,), "pokemon_shapes.csv", pokemon_shapes_from_rows) - def csv_record_to_objects(info): + def pokemon_shape_names_from_rows(info: list[str]) -> Iterator[PokemonShapeName]: yield PokemonShapeName( pokemon_shape_id=int(info[0]), language_id=int(info[1]), @@ -1770,14 +1196,14 @@ def csv_record_to_objects(info): awesome_name=info[3], ) - build_generic((PokemonShapeName,), "pokemon_shape_prose.csv", csv_record_to_objects) + build_generic((PokemonShapeName,), "pokemon_shape_prose.csv", pokemon_shape_names_from_rows) - def csv_record_to_objects(info): + def pokemon_habitats_from_rows(info: list[str]) -> Iterator[PokemonHabitat]: yield PokemonHabitat(id=int(info[0]), name=info[1]) - build_generic((PokemonHabitat,), "pokemon_habitats.csv", csv_record_to_objects) + build_generic((PokemonHabitat,), "pokemon_habitats.csv", pokemon_habitats_from_rows) - def csv_record_to_objects(info): + def pokemon_species_from_rows(info: list[str]) -> Iterator[PokemonSpecies]: yield PokemonSpecies( id=int(info[0]), name=info[1], @@ -1800,7 +1226,7 @@ def csv_record_to_objects(info): order=int(info[18]) if info[18] != "" else None, ) - build_generic((PokemonSpecies,), "pokemon_species.csv", csv_record_to_objects) + build_generic((PokemonSpecies,), "pokemon_species.csv", pokemon_species_from_rows) # PokemonSpecies.evolves_from_species can't be set until all the species are created data = load_data("pokemon_species.csv") @@ -1812,7 +1238,7 @@ def csv_record_to_objects(info): species.evolves_from_species = evolves species.save() - def csv_record_to_objects(info): + def pokemon_species_names_from_rows(info: list[str]) -> Iterator[PokemonSpeciesName]: yield PokemonSpeciesName( pokemon_species_id=int(info[0]), language_id=int(info[1]), @@ -1820,18 +1246,18 @@ def csv_record_to_objects(info): genus=info[3], ) - build_generic((PokemonSpeciesName,), "pokemon_species_names.csv", csv_record_to_objects) + build_generic((PokemonSpeciesName,), "pokemon_species_names.csv", pokemon_species_names_from_rows) - def csv_record_to_objects(info): + def pokemon_species_descriptions_from_rows(info: list[str]) -> Iterator[PokemonSpeciesDescription]: yield PokemonSpeciesDescription( pokemon_species_id=int(info[0]), language_id=int(info[1]), description=info[2], ) - build_generic((PokemonSpeciesDescription,), "pokemon_species_prose.csv", csv_record_to_objects) + build_generic((PokemonSpeciesDescription,), "pokemon_species_prose.csv", pokemon_species_descriptions_from_rows) - def csv_record_to_objects(info): + def pokemon_species_flavor_texts_from_rows(info: list[str]) -> Iterator[PokemonSpeciesFlavorText]: yield PokemonSpeciesFlavorText( pokemon_species_id=int(info[0]), version_id=int(info[1]), @@ -1842,10 +1268,10 @@ def csv_record_to_objects(info): build_generic( (PokemonSpeciesFlavorText,), "pokemon_species_flavor_text.csv", - csv_record_to_objects, + pokemon_species_flavor_texts_from_rows, ) - def csv_record_to_objects(info): + def pokemon_from_rows(info: list[str]) -> Iterator[Pokemon]: yield Pokemon( id=int(info[0]), name=info[1], @@ -1857,23 +1283,23 @@ def csv_record_to_objects(info): is_default=bool(int(info[7])) if info[7] != "" else None, ) - build_generic((Pokemon,), "pokemon.csv", csv_record_to_objects) + build_generic((Pokemon,), "pokemon.csv", pokemon_from_rows) - def csv_record_to_objects(info): - sprites = _resolve_sprite_config(POKEMON_SPRITE_CONFIG, _pokemon_sprite_lookup(info)) + def pokemon_sprites_from_rows(info: list[str]) -> Iterator[PokemonSprites]: + sprites = resolve_sprite_config(POKEMON_SPRITE_CONFIG, pokemon_sprite_lookup(info)) yield PokemonSprites( id=int(info[0]), pokemon=Pokemon.objects.get(pk=int(info[0])), sprites=sprites, ) - build_generic((PokemonSprites,), "pokemon.csv", csv_record_to_objects) + build_generic((PokemonSprites,), "pokemon.csv", pokemon_sprites_from_rows) - def try_cry_names(path, info, extension): + def try_cry_names(path: str, info: list[str], extension: str) -> str | None: file_name = f"{info[0]}.{extension}" return file_path_or_none(path + file_name, image_file=False) - def csv_record_to_objects(info): + def pokemon_cries_from_rows(info: list[str]) -> Iterator[PokemonCries]: poke_cries = "pokemon" latest = f"{poke_cries}/latest/" legacy = f"{poke_cries}/legacy/" @@ -1887,9 +1313,9 @@ def csv_record_to_objects(info): cries=cries, ) - build_generic((PokemonCries,), "pokemon.csv", csv_record_to_objects) + build_generic((PokemonCries,), "pokemon.csv", pokemon_cries_from_rows) - def csv_record_to_objects(info): + def pokemon_abilities_from_rows(info: list[str]) -> Iterator[PokemonAbility]: yield PokemonAbility( pokemon_id=int(info[0]), ability_id=int(info[1]), @@ -1897,9 +1323,9 @@ def csv_record_to_objects(info): slot=int(info[3]), ) - build_generic((PokemonAbility,), "pokemon_abilities.csv", csv_record_to_objects) + build_generic((PokemonAbility,), "pokemon_abilities.csv", pokemon_abilities_from_rows) - def csv_record_to_objects(info): + def pokemon_abilities_past_from_rows(info: list[str]) -> Iterator[PokemonAbilityPast]: yield PokemonAbilityPast( pokemon_id=int(info[0]), generation_id=int(info[1]), @@ -1908,23 +1334,23 @@ def csv_record_to_objects(info): slot=int(info[4]), ) - build_generic((PokemonAbilityPast,), "pokemon_abilities_past.csv", csv_record_to_objects) + build_generic((PokemonAbilityPast,), "pokemon_abilities_past.csv", pokemon_abilities_past_from_rows) - def csv_record_to_objects(info): + def pokemon_dex_numbers_from_rows(info: list[str]) -> Iterator[PokemonDexNumber]: yield PokemonDexNumber( pokemon_species_id=int(info[0]), pokedex_id=int(info[1]), pokedex_number=int(info[2]), ) - build_generic((PokemonDexNumber,), "pokemon_dex_numbers.csv", csv_record_to_objects) + build_generic((PokemonDexNumber,), "pokemon_dex_numbers.csv", pokemon_dex_numbers_from_rows) - def csv_record_to_objects(info): + def pokemon_egg_groups_from_rows(info: list[str]) -> Iterator[PokemonEggGroup]: yield PokemonEggGroup(pokemon_species_id=int(info[0]), egg_group_id=int(info[1])) - build_generic((PokemonEggGroup,), "pokemon_egg_groups.csv", csv_record_to_objects) + build_generic((PokemonEggGroup,), "pokemon_egg_groups.csv", pokemon_egg_groups_from_rows) - def csv_record_to_objects(info): + def pokemon_forms_from_rows(info: list[str]) -> Iterator[PokemonForm]: yield PokemonForm( id=int(info[0]), name=info[1], @@ -1938,9 +1364,9 @@ def csv_record_to_objects(info): order=int(info[9]), ) - build_generic((PokemonForm,), "pokemon_forms.csv", csv_record_to_objects) + build_generic((PokemonForm,), "pokemon_forms.csv", pokemon_forms_from_rows) - def csv_record_to_objects(info): + def pokemon_evolutions_from_rows(info: list[str]) -> Iterator[PokemonEvolution]: yield PokemonEvolution( id=int(info[0]), evolved_species_id=int(info[1]), @@ -1978,21 +1404,21 @@ def csv_record_to_objects(info): percentage_chance=(float(info[33]) if len(info) > 33 and info[33] != "" else None), ) - build_generic((PokemonEvolution,), "pokemon_evolution.csv", csv_record_to_objects) + build_generic((PokemonEvolution,), "pokemon_evolution.csv", pokemon_evolutions_from_rows) pokemon_sprites_by_id = dict(PokemonSprites.objects.values_list("pokemon_id", "sprites")) - def csv_record_to_objects(info): + def pokemon_form_sprites_from_rows(info: list[str]) -> Iterator[PokemonFormSprites]: if info[2]: - lookup = _pokemon_form_sprite_lookup(info) - sprites = _resolve_sprite_config(POKEMON_SPRITE_CONFIG, lookup) + lookup = pokemon_form_sprite_lookup(info) + sprites = resolve_sprite_config(POKEMON_SPRITE_CONFIG, lookup) else: sprites = deepcopy(pokemon_sprites_by_id.get(int(info[3]), {})) yield PokemonFormSprites(id=int(info[0]), pokemon_form_id=int(info[0]), sprites=sprites) - build_generic((PokemonFormSprites,), "pokemon_forms.csv", csv_record_to_objects) + build_generic((PokemonFormSprites,), "pokemon_forms.csv", pokemon_form_sprites_from_rows) - def csv_record_to_objects(info): + def pokemon_form_names_from_rows(info: list[str]) -> Iterator[PokemonFormName]: yield PokemonFormName( pokemon_form=PokemonForm.objects.get(pk=int(info[0])), language=Language.objects.get(pk=int(info[1])), @@ -2000,28 +1426,28 @@ def csv_record_to_objects(info): pokemon_name=info[3], ) - build_generic((PokemonFormName,), "pokemon_form_names.csv", csv_record_to_objects) + build_generic((PokemonFormName,), "pokemon_form_names.csv", pokemon_form_names_from_rows) - def csv_record_to_objects(info): + def pokemon_form_generations_from_rows(info: list[str]) -> Iterator[PokemonFormGeneration]: yield PokemonFormGeneration( pokemon_form_id=int(info[0]), generation_id=int(info[1]), game_index=int(info[2]), ) - build_generic((PokemonFormGeneration,), "pokemon_form_generations.csv", csv_record_to_objects) + build_generic((PokemonFormGeneration,), "pokemon_form_generations.csv", pokemon_form_generations_from_rows) - def csv_record_to_objects(info): + def pokemon_form_types_from_rows(info: list[str]) -> Iterator[PokemonFormType]: yield PokemonFormType(pokemon_form_id=int(info[0]), type_id=int(info[1]), slot=int(info[2])) - build_generic((PokemonFormType,), "pokemon_form_types.csv", csv_record_to_objects) + build_generic((PokemonFormType,), "pokemon_form_types.csv", pokemon_form_types_from_rows) - def csv_record_to_objects(info): + def pokemon_form_triggers_from_rows(info: list[str]) -> Iterator[PokemonFormTrigger]: yield PokemonFormTrigger(id=int(info[0]), name=info[1]) - build_generic((PokemonFormTrigger,), "pokemon_form_triggers.csv", csv_record_to_objects) + build_generic((PokemonFormTrigger,), "pokemon_form_triggers.csv", pokemon_form_triggers_from_rows) - def csv_record_to_objects(info): + def pokemon_form_conditions_from_rows(info: list[str]) -> Iterator[PokemonFormCondition]: yield PokemonFormCondition( pokemon_form_id=int(info[0]), form_trigger_id=int(info[1]), @@ -2031,9 +1457,9 @@ def csv_record_to_objects(info): base_form_id=int(info[5]) if info[5] != "" else None, ) - build_generic((PokemonFormCondition,), "pokemon_form_conditions.csv", csv_record_to_objects) + build_generic((PokemonFormCondition,), "pokemon_form_conditions.csv", pokemon_form_conditions_from_rows) - def csv_record_to_objects(info): + def pokemon_form_flavor_texts_from_rows(info: list[str]) -> Iterator[PokemonFormFlavorText]: yield PokemonFormFlavorText( pokemon_form_id=int(info[0]), version_id=int(info[1]), @@ -2044,20 +1470,20 @@ def csv_record_to_objects(info): build_generic( (PokemonFormFlavorText,), "pokemon_form_flavor_text.csv", - csv_record_to_objects, + pokemon_form_flavor_texts_from_rows, ) - def csv_record_to_objects(info): + def pokemon_game_indexes_from_rows(info: list[str]) -> Iterator[PokemonGameIndex]: yield PokemonGameIndex(pokemon_id=int(info[0]), version_id=int(info[1]), game_index=int(info[2])) - build_generic((PokemonGameIndex,), "pokemon_game_indices.csv", csv_record_to_objects) + build_generic((PokemonGameIndex,), "pokemon_game_indices.csv", pokemon_game_indexes_from_rows) - def csv_record_to_objects(info): + def pokemon_habitat_names_from_rows(info: list[str]) -> Iterator[PokemonHabitatName]: yield PokemonHabitatName(pokemon_habitat_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((PokemonHabitatName,), "pokemon_habitat_names.csv", csv_record_to_objects) + build_generic((PokemonHabitatName,), "pokemon_habitat_names.csv", pokemon_habitat_names_from_rows) - def csv_record_to_objects(info): + def pokemon_items_from_rows(info: list[str]) -> Iterator[PokemonItem]: yield PokemonItem( pokemon_id=int(info[0]), version_id=int(info[1]), @@ -2065,9 +1491,9 @@ def csv_record_to_objects(info): rarity=int(info[3]), ) - build_generic((PokemonItem,), "pokemon_items.csv", csv_record_to_objects) + build_generic((PokemonItem,), "pokemon_items.csv", pokemon_items_from_rows) - def csv_record_to_objects(info): + def pokemon_moves_from_rows(info: list[str]) -> Iterator[PokemonMove]: yield PokemonMove( pokemon_id=int(info[0]), version_group_id=int(info[1]), @@ -2077,9 +1503,9 @@ def csv_record_to_objects(info): order=int(info[5]) if info[5] != "" else None, ) - build_generic((PokemonMove,), "pokemon_moves.csv", csv_record_to_objects) + build_generic((PokemonMove,), "pokemon_moves.csv", pokemon_moves_from_rows) - def csv_record_to_objects(info): + def pokemon_stats_from_rows(info: list[str]) -> Iterator[PokemonStat]: yield PokemonStat( pokemon_id=int(info[0]), stat_id=int(info[1]), @@ -2087,9 +1513,9 @@ def csv_record_to_objects(info): effort=int(info[3]), ) - build_generic((PokemonStat,), "pokemon_stats.csv", csv_record_to_objects) + build_generic((PokemonStat,), "pokemon_stats.csv", pokemon_stats_from_rows) - def csv_record_to_objects(info): + def pokemon_stats_past_from_rows(info: list[str]) -> Iterator[PokemonStatPast]: yield PokemonStatPast( pokemon_id=int(info[0]), generation_id=int(info[1]), @@ -2098,14 +1524,14 @@ def csv_record_to_objects(info): effort=int(info[4]), ) - build_generic((PokemonStatPast,), "pokemon_stats_past.csv", csv_record_to_objects) + build_generic((PokemonStatPast,), "pokemon_stats_past.csv", pokemon_stats_past_from_rows) - def csv_record_to_objects(info): + def pokemon_types_from_rows(info: list[str]) -> Iterator[PokemonType]: yield PokemonType(pokemon_id=int(info[0]), type_id=int(info[1]), slot=int(info[2])) - build_generic((PokemonType,), "pokemon_types.csv", csv_record_to_objects) + build_generic((PokemonType,), "pokemon_types.csv", pokemon_types_from_rows) - def csv_record_to_objects(info): + def pokemon_types_past_from_rows(info: list[str]) -> Iterator[PokemonTypePast]: yield PokemonTypePast( pokemon_id=int(info[0]), generation_id=int(info[1]), @@ -2113,21 +1539,16 @@ def csv_record_to_objects(info): slot=int(info[3]), ) - build_generic((PokemonTypePast,), "pokemon_types_past.csv", csv_record_to_objects) + build_generic((PokemonTypePast,), "pokemon_types_past.csv", pokemon_types_past_from_rows) -############### -# ENCOUNTER # -############### - - -def _build_encounters(): - def csv_record_to_objects(info): +def _build_encounters() -> None: + def encounter_methods_from_rows(info: list[str]) -> Iterator[EncounterMethod]: yield EncounterMethod(id=int(info[0]), name=info[1], order=int(info[2])) - build_generic((EncounterMethod,), "encounter_methods.csv", csv_record_to_objects) + build_generic((EncounterMethod,), "encounter_methods.csv", encounter_methods_from_rows) - def csv_record_to_objects(info): + def location_area_encounter_rates_from_rows(info: list[str]) -> Iterator[LocationAreaEncounterRate]: yield LocationAreaEncounterRate( location_area_id=int(info[0]), encounter_method_id=int(info[1]), @@ -2138,29 +1559,29 @@ def csv_record_to_objects(info): build_generic( (LocationAreaEncounterRate,), "location_area_encounter_rates.csv", - csv_record_to_objects, + location_area_encounter_rates_from_rows, ) - def csv_record_to_objects(info): + def encounter_method_names_from_rows(info: list[str]) -> Iterator[EncounterMethodName]: yield EncounterMethodName(encounter_method_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((EncounterMethodName,), "encounter_method_prose.csv", csv_record_to_objects) + build_generic((EncounterMethodName,), "encounter_method_prose.csv", encounter_method_names_from_rows) - def csv_record_to_objects(info): + def encounter_conditions_from_rows(info: list[str]) -> Iterator[EncounterCondition]: yield EncounterCondition(id=int(info[0]), name=info[1]) - build_generic((EncounterCondition,), "encounter_conditions.csv", csv_record_to_objects) + build_generic((EncounterCondition,), "encounter_conditions.csv", encounter_conditions_from_rows) - def csv_record_to_objects(info): + def encounter_condition_names_from_rows(info: list[str]) -> Iterator[EncounterConditionName]: yield EncounterConditionName(encounter_condition_id=int(info[0]), language_id=int(info[1]), name=info[2]) build_generic( (EncounterConditionName,), "encounter_condition_prose.csv", - csv_record_to_objects, + encounter_condition_names_from_rows, ) - def csv_record_to_objects(info): + def encounter_slots_from_rows(info: list[str]) -> Iterator[EncounterSlot]: yield EncounterSlot( id=int(info[0]), version_group_id=int(info[1]), @@ -2169,9 +1590,9 @@ def csv_record_to_objects(info): rarity=int(info[4]), ) - build_generic((EncounterSlot,), "encounter_slots.csv", csv_record_to_objects) + build_generic((EncounterSlot,), "encounter_slots.csv", encounter_slots_from_rows) - def csv_record_to_objects(info): + def encounters_from_rows(info: list[str]) -> Iterator[Encounter]: yield Encounter( id=int(info[0]), version_id=int(info[1]), @@ -2182,9 +1603,9 @@ def csv_record_to_objects(info): max_level=int(info[6]), ) - build_generic((Encounter,), "encounters.csv", csv_record_to_objects) + build_generic((Encounter,), "encounters.csv", encounters_from_rows) - def csv_record_to_objects(info): + def encounter_condition_values_from_rows(info: list[str]) -> Iterator[EncounterConditionValue]: yield EncounterConditionValue( id=int(info[0]), encounter_condition_id=int(info[1]), @@ -2195,10 +1616,10 @@ def csv_record_to_objects(info): build_generic( (EncounterConditionValue,), "encounter_condition_values.csv", - csv_record_to_objects, + encounter_condition_values_from_rows, ) - def csv_record_to_objects(info): + def encounter_condition_value_names_from_rows(info: list[str]) -> Iterator[EncounterConditionValueName]: yield EncounterConditionValueName( encounter_condition_value_id=int(info[0]), language_id=int(info[1]), @@ -2208,19 +1629,19 @@ def csv_record_to_objects(info): build_generic( (EncounterConditionValueName,), "encounter_condition_value_prose.csv", - csv_record_to_objects, + encounter_condition_value_names_from_rows, ) - def csv_record_to_objects(info): + def encounter_condition_value_maps_from_rows(info: list[str]) -> Iterator[EncounterConditionValueMap]: yield EncounterConditionValueMap(encounter_id=int(info[0]), encounter_condition_value_id=int(info[1])) build_generic( (EncounterConditionValueMap,), "encounter_condition_value_map.csv", - csv_record_to_objects, + encounter_condition_value_maps_from_rows, ) - def csv_record_to_objects(info): + def encounter_pokemon_details_from_rows(info: list[str]) -> Iterator[EncounterPokemonDetail]: yield EncounterPokemonDetail( encounter_id=int(info[0]), min_perfect_ivs=int(info[1]) if info[1] != "" else None, @@ -2232,27 +1653,22 @@ def csv_record_to_objects(info): build_generic( (EncounterPokemonDetail,), "encounter_pokemon_details.csv", - csv_record_to_objects, + encounter_pokemon_details_from_rows, ) -############## -# PAL PARK # -############## - - -def _build_pal_parks(): - def csv_record_to_objects(info): +def _build_pal_parks() -> None: + def pal_park_areas_from_rows(info: list[str]) -> Iterator[PalParkArea]: yield PalParkArea(id=int(info[0]), name=info[1]) - build_generic((PalParkArea,), "pal_park_areas.csv", csv_record_to_objects) + build_generic((PalParkArea,), "pal_park_areas.csv", pal_park_areas_from_rows) - def csv_record_to_objects(info): + def pal_park_area_names_from_rows(info: list[str]) -> Iterator[PalParkAreaName]: yield PalParkAreaName(pal_park_area_id=int(info[0]), language_id=int(info[1]), name=info[2]) - build_generic((PalParkAreaName,), "pal_park_area_names.csv", csv_record_to_objects) + build_generic((PalParkAreaName,), "pal_park_area_names.csv", pal_park_area_names_from_rows) - def csv_record_to_objects(info): + def pal_parks_from_rows(info: list[str]) -> Iterator[PalPark]: yield PalPark( pokemon_species_id=int(info[0]), pal_park_area_id=int(info[1]), @@ -2260,10 +1676,10 @@ def csv_record_to_objects(info): rate=int(info[3]), ) - build_generic((PalPark,), "pal_park.csv", csv_record_to_objects) + build_generic((PalPark,), "pal_park.csv", pal_parks_from_rows) -def build_all(): +def build_all() -> None: _build_languages() _build_regions() _build_generations() diff --git a/data/v2/utils/__init__.py b/data/v2/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/data/v2/utils/helpers.py b/data/v2/utils/helpers.py new file mode 100644 index 000000000..502a4b1b4 --- /dev/null +++ b/data/v2/utils/helpers.py @@ -0,0 +1,56 @@ +import csv +from collections.abc import Callable, Generator, Iterable +from contextlib import closing +from pathlib import Path + +from django.db import connection +from django.db.models import Model + +DATA_LOCATION = Path(__file__).resolve().parents[1] / "csv" +BATCH_SIZE: int = 1_000 + + +def load_data(file_name: str) -> Generator[list[str], None, None]: + with open(DATA_LOCATION / file_name, encoding="utf8") as file: + yield from csv.reader(file, delimiter=",") + + +def clear_table(model: type[Model]) -> None: + table_name = model._meta.db_table + model.objects.all().delete() + print("building " + table_name) + with connection.cursor() as cursor: + if connection.vendor == "sqlite": + cursor.execute("DELETE FROM sqlite_sequence WHERE name = " + "'" + table_name + "'") + else: + # Reset DB auto increments to start at 1 + cursor.execute("SELECT setval(pg_get_serial_sequence(" + "'" + table_name + "'" + ",'id'), 1, false);") + + +def build_generic( + model_classes: tuple[type[Model], ...], + file_name: str, + csv_record_to_objects: Callable[[list[str]], Iterable[Model]], +) -> None: + batches: dict[type[Model], list[Model]] = {} + + for model_class in model_classes: + clear_table(model_class) + batches[model_class] = [] + + with closing(load_data(file_name)) as csv_data: + next(csv_data, None) # skip header + + for csv_record in csv_data: + for obj in csv_record_to_objects(csv_record): + model_class = type(obj) + batch = batches[model_class] + batch.append(obj) + + if len(batch) >= BATCH_SIZE: + model_class.objects.bulk_create(batch) + batches[model_class] = [] + + for model_class, batch in batches.items(): + if batch: + model_class.objects.bulk_create(batch) diff --git a/data/v2/utils/media.py b/data/v2/utils/media.py new file mode 100644 index 000000000..e909efb0c --- /dev/null +++ b/data/v2/utils/media.py @@ -0,0 +1,609 @@ +import contextlib +import gzip +import json +import os +import urllib.error +import urllib.request +from collections.abc import Callable +from email.utils import parsedate_to_datetime +from pathlib import Path +from typing import Any + +MEDIA_DIR = "{prefix}{{file_name}}".format( + prefix=os.environ.get( + "POKEAPI_SPRITES_PREFIX", + "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/", + ) +) +SOUND_DIR = "{prefix}{{file_name}}".format( + prefix=os.environ.get( + "POKEAPI_CRIES_PREFIX", + "https://raw.githubusercontent.com/PokeAPI/cries/main/cries/", + ) +) +DATA_DIR = Path(__file__).resolve().parents[1] +IMAGE_DIR = DATA_DIR / "sprites" / "sprites" +CRIES_DIR = DATA_DIR / "cries" / "cries" + + +def _load_manifest_from_path(path: Path) -> set[str] | None: + try: + with gzip.open(path, "rt", encoding="utf-8") as f: + return set(json.load(f)) + except (OSError, json.JSONDecodeError) as e: + print(f"Warning: Failed to read local sprite manifest at {path}: {e}") + return None + + +def _fetch_manifest(url: str, cache_file: Path) -> set[str] | None: + try: + if cache_file.is_file(): + try: + head_req = urllib.request.Request(url, method="HEAD") + with urllib.request.urlopen(head_req, timeout=10) as head_resp: + last_mod_header = head_resp.headers.get("Last-Modified") + if last_mod_header: + remote_mtime = parsedate_to_datetime(last_mod_header).timestamp() + if cache_file.stat().st_mtime >= remote_mtime: + return _load_manifest_from_path(cache_file) + except (OSError, urllib.error.URLError, ValueError): + pass + + with urllib.request.urlopen(url, timeout=30) as resp: + raw_bytes: bytes = resp.read() + data = json.loads(gzip.decompress(raw_bytes).decode("utf-8")) + + with contextlib.suppress(OSError): + cache_file.write_bytes(raw_bytes) + last_mod = resp.headers.get("Last-Modified") + if last_mod: + mtime = parsedate_to_datetime(last_mod).timestamp() + os.utime(cache_file, (mtime, mtime)) + + return set(data) + except (OSError, urllib.error.URLError, json.JSONDecodeError, gzip.BadGzipFile) as e: + print(f"Warning: Failed to fetch sprite manifest from {url}: {e}") + return None + + +def _load_local_sprites() -> set[str]: + """Scan the local cloned sprites submodule directory.""" + if not IMAGE_DIR.is_dir(): + return set() + + images: set[str] = set() + for image_path in IMAGE_DIR.rglob("*"): + if not image_path.is_dir(): + images.add(image_path.relative_to(IMAGE_DIR).as_posix()) + return images + + +def _load_resource_images() -> set[str]: + use_local = os.environ.get("POKEAPI_USE_LOCAL_SPRITES", "").lower() in ("1", "true", "yes") + if use_local and (local_images := _load_local_sprites()): + return local_images + + cache_file = DATA_DIR / "manifest.json.gz" + manifest_urls: list[str] = list( + filter( + None, + [ + os.environ.get("POKEAPI_SPRITES_MANIFEST_URL", ""), + "https://pokeapi.github.io/sprites/manifest.json.gz", + "https://github.com/PokeAPI/sprites/releases/latest/download/manifest.json.gz", + ], + ) + ) + + for url in manifest_urls: + if (data := _fetch_manifest(url, cache_file)) is not None: + return data + + # If offline / all remote requests failed, use cached manifest if available + if cache_file.is_file() and (cached := _load_manifest_from_path(cache_file)) is not None: + return cached + + # Fallback to local files if manifest is completely unavailable + if local_images := _load_local_sprites(): + return local_images + + print("Warning: Could not load sprite resources from local files or remote manifest. Sprites will be empty.") + return set() + + +RESOURCE_IMAGES: set[str] = _load_resource_images() +RESOURCE_CRIES: set[str] = set() + +for cry_path in CRIES_DIR.rglob("*"): + if not cry_path.is_dir(): + cry_key = cry_path.relative_to(CRIES_DIR).as_posix() + RESOURCE_CRIES.add(cry_key) + + +TYPE_SPRITE_GAME_MAP: dict[str, list[str]] = { + "generation-iii": [ + "colosseum", + "emerald", + "firered-leafgreen", + "ruby-sapphire", + "xd", + ], + "generation-iv": ["diamond-pearl", "heartgold-soulsilver", "platinum"], + "generation-v": ["black-2-white-2", "black-white"], + "generation-vi": ["omega-ruby-alpha-sapphire", "x-y"], + "generation-vii": [ + "lets-go-pikachu-lets-go-eevee", + "sun-moon", + "ultra-sun-ultra-moon", + ], + "generation-viii": [ + "brilliant-diamond-shining-pearl", + "legends-arceus", + "sword-shield", + ], + "generation-ix": ["scarlet-violet", "champions"], +} + + +POKEMON_SPRITE_CONFIG: dict[str, Any] = { + "front_default": ("pokemon/", "png"), + "front_female": ("pokemon/female/", "png"), + "front_shiny": ("pokemon/shiny/", "png"), + "front_shiny_female": ("pokemon/shiny/female/", "png"), + "back_default": ("pokemon/back/", "png"), + "back_female": ("pokemon/back/female/", "png"), + "back_shiny": ("pokemon/back/shiny/", "png"), + "back_shiny_female": ("pokemon/back/shiny/female/", "png"), + "other": { + "dream_world": { + "front_default": ("pokemon/other/dream-world/", "svg"), + "front_female": ("pokemon/other/dream-world/female/", "svg"), + }, + "home": { + "front_default": ("pokemon/other/home/", "png"), + "front_female": ("pokemon/other/home/female/", "png"), + "front_shiny": ("pokemon/other/home/shiny/", "png"), + "front_shiny_female": ("pokemon/other/home/shiny/female/", "png"), + }, + "official-artwork": { + "front_default": ("pokemon/other/official-artwork/", "png"), + "front_shiny": ("pokemon/other/official-artwork/shiny/", "png"), + "versions": { + "generation-i": { + "red-and-blue": { + "front_default": ("pokemon/other/official-artwork/versions/generation-i/red-and-blue/", "png") + }, + "red-and-green": { + "front_default": ("pokemon/other/official-artwork/versions/generation-i/red-and-green/", "png") + }, + }, + "generation-ii": { + "gold-and-silver": { + "front_default": ( + "pokemon/other/official-artwork/versions/generation-ii/gold-and-silver/", + "png", + ) + } + }, + }, + }, + "showdown": { + "front_default": ("pokemon/other/showdown/", "gif"), + "front_female": ("pokemon/other/showdown/female/", "gif"), + "front_shiny": ("pokemon/other/showdown/shiny/", "gif"), + "front_shiny_female": ("pokemon/other/showdown/shiny/female/", "gif"), + "back_default": ("pokemon/other/showdown/back/", "gif"), + "back_female": ("pokemon/other/showdown/back/female/", "gif"), + "back_shiny": ("pokemon/other/showdown/back/shiny/", "gif"), + "back_shiny_female": ("pokemon/other/showdown/back/shiny/female/", "gif"), + }, + }, + "versions": { + "generation-i": { + "red-blue": { + "front_default": ("pokemon/versions/generation-i/red-blue/", "png"), + "front_gray": ("pokemon/versions/generation-i/red-blue/gray/", "png"), + "front_transparent": ("pokemon/versions/generation-i/red-blue/transparent/", "png"), + "front_transparent_gray": ("pokemon/versions/generation-i/red-blue/transparent/gray/", "png"), + "back_default": ("pokemon/versions/generation-i/red-blue/back/", "png"), + "back_gray": ("pokemon/versions/generation-i/red-blue/back/gray/", "png"), + "back_transparent": ("pokemon/versions/generation-i/red-blue/transparent/back/", "png"), + "back_transparent_gray": ("pokemon/versions/generation-i/red-blue/transparent/back/gray/", "png"), + }, + "red-green-japan": { + "front_default": ("pokemon/versions/generation-i/red-green-japan/", "png"), + "front_gray": ("pokemon/versions/generation-i/red-green-japan/gray/", "png"), + "back_default": ("pokemon/versions/generation-i/red-green-japan/back/", "png"), + "back_gray": ("pokemon/versions/generation-i/red-green-japan/back/gray/", "png"), + }, + "yellow": { + "front_default": ("pokemon/versions/generation-i/yellow/", "png"), + "front_gbc": ("pokemon/versions/generation-i/yellow/gbc/", "png"), + "front_gray": ("pokemon/versions/generation-i/yellow/gray/", "png"), + "front_transparent": ("pokemon/versions/generation-i/yellow/transparent/", "png"), + "front_transparent_gray": ("pokemon/versions/generation-i/yellow/transparent/gray/", "png"), + "back_default": ("pokemon/versions/generation-i/yellow/back/", "png"), + "back_gbc": ("pokemon/versions/generation-i/yellow/back/gbc/", "png"), + "back_gray": ("pokemon/versions/generation-i/yellow/back/gray/", "png"), + "back_transparent": ("pokemon/versions/generation-i/yellow/transparent/back/", "png"), + "back_transparent_gray": ("pokemon/versions/generation-i/yellow/transparent/back/gray/", "png"), + }, + }, + "generation-ii": { + "crystal": { + "front_default": ("pokemon/versions/generation-ii/crystal/", "png"), + "front_shiny": ("pokemon/versions/generation-ii/crystal/shiny/", "png"), + "front_transparent": ("pokemon/versions/generation-ii/crystal/transparent/", "png"), + "front_shiny_transparent": ("pokemon/versions/generation-ii/crystal/transparent/shiny/", "png"), + "back_default": ("pokemon/versions/generation-ii/crystal/back/", "png"), + "back_shiny": ("pokemon/versions/generation-ii/crystal/back/shiny/", "png"), + "back_transparent": ("pokemon/versions/generation-ii/crystal/transparent/back/", "png"), + "back_shiny_transparent": ("pokemon/versions/generation-ii/crystal/transparent/back/shiny/", "png"), + "animated": { + "front_default": ("pokemon/versions/generation-ii/crystal/animated/", "gif"), + "front_shiny": ("pokemon/versions/generation-ii/crystal/animated/shiny/", "gif"), + }, + }, + "gold": { + "front_default": ("pokemon/versions/generation-ii/gold/", "png"), + "front_shiny": ("pokemon/versions/generation-ii/gold/shiny/", "png"), + "front_transparent": ("pokemon/versions/generation-ii/gold/transparent/", "png"), + "front_shiny_transparent": ("pokemon/versions/generation-ii/gold/transparent/shiny/", "png"), + "back_default": ("pokemon/versions/generation-ii/gold/back/", "png"), + "back_shiny": ("pokemon/versions/generation-ii/gold/back/shiny/", "png"), + "back_transparent": ("pokemon/versions/generation-ii/gold/transparent/back/", "png"), + "back_shiny_transparent": ("pokemon/versions/generation-ii/gold/transparent/back/shiny/", "png"), + }, + "silver": { + "front_default": ("pokemon/versions/generation-ii/silver/", "png"), + "front_shiny": ("pokemon/versions/generation-ii/silver/shiny/", "png"), + "front_transparent": ("pokemon/versions/generation-ii/silver/transparent/", "png"), + "front_shiny_transparent": ("pokemon/versions/generation-ii/silver/transparent/shiny/", "png"), + "back_default": ("pokemon/versions/generation-ii/silver/back/", "png"), + "back_shiny": ("pokemon/versions/generation-ii/silver/back/shiny/", "png"), + "back_transparent": ("pokemon/versions/generation-ii/silver/transparent/back/", "png"), + "back_shiny_transparent": ("pokemon/versions/generation-ii/silver/transparent/back/shiny/", "png"), + }, + }, + "generation-iii": { + "emerald": { + "front_default": ("pokemon/versions/generation-iii/emerald/", "png"), + "front_shiny": ("pokemon/versions/generation-iii/emerald/shiny/", "png"), + "back_default": ("pokemon/versions/generation-iii/emerald/back/", "png"), + "back_shiny": ("pokemon/versions/generation-iii/emerald/back/shiny/", "png"), + "animated": { + "front_default": ("pokemon/versions/generation-iii/emerald/animated/", "png"), + "front_shiny": ("pokemon/versions/generation-iii/emerald/animated/shiny/", "png"), + "back_default": ("pokemon/versions/generation-iii/emerald/animated/back/", "png"), + "back_shiny": ("pokemon/versions/generation-iii/emerald/animated/back/shiny/", "png"), + }, + }, + "firered-leafgreen": { + "front_default": ("pokemon/versions/generation-iii/firered-leafgreen/", "png"), + "front_shiny": ("pokemon/versions/generation-iii/firered-leafgreen/shiny/", "png"), + "back_default": ("pokemon/versions/generation-iii/firered-leafgreen/back/", "png"), + "back_shiny": ("pokemon/versions/generation-iii/firered-leafgreen/back/shiny/", "png"), + }, + "icons": { + "front_default": ("pokemon/versions/generation-iii/icons/", "png"), + }, + "ruby-sapphire": { + "front_default": ("pokemon/versions/generation-iii/ruby-sapphire/", "png"), + "front_shiny": ("pokemon/versions/generation-iii/ruby-sapphire/shiny/", "png"), + "back_default": ("pokemon/versions/generation-iii/ruby-sapphire/back/", "png"), + "back_shiny": ("pokemon/versions/generation-iii/ruby-sapphire/back/shiny/", "png"), + }, + }, + "generation-iv": { + "diamond-pearl": { + "front_default": ("pokemon/versions/generation-iv/diamond-pearl/", "png"), + "front_female": ("pokemon/versions/generation-iv/diamond-pearl/female/", "png"), + "front_shiny": ("pokemon/versions/generation-iv/diamond-pearl/shiny/", "png"), + "front_shiny_female": ("pokemon/versions/generation-iv/diamond-pearl/shiny/female/", "png"), + "back_default": ("pokemon/versions/generation-iv/diamond-pearl/back/", "png"), + "back_female": ("pokemon/versions/generation-iv/diamond-pearl/back/female/", "png"), + "back_shiny": ("pokemon/versions/generation-iv/diamond-pearl/back/shiny/", "png"), + "back_shiny_female": ("pokemon/versions/generation-iv/diamond-pearl/back/shiny/female/", "png"), + "animated": { + "front_default": ("pokemon/versions/generation-iv/diamond-pearl/animated/", "png"), + "front_female": ("pokemon/versions/generation-iv/diamond-pearl/animated/female/", "png"), + "front_shiny": ("pokemon/versions/generation-iv/diamond-pearl/animated/shiny/", "png"), + "front_shiny_female": ( + "pokemon/versions/generation-iv/diamond-pearl/animated/shiny/female/", + "png", + ), + }, + }, + "heartgold-soulsilver": { + "front_default": ("pokemon/versions/generation-iv/heartgold-soulsilver/", "png"), + "front_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/female/", "png"), + "front_shiny": ("pokemon/versions/generation-iv/heartgold-soulsilver/shiny/", "png"), + "front_shiny_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/shiny/female/", "png"), + "back_default": ("pokemon/versions/generation-iv/heartgold-soulsilver/back/", "png"), + "back_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/back/female/", "png"), + "back_shiny": ("pokemon/versions/generation-iv/heartgold-soulsilver/back/shiny/", "png"), + "back_shiny_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/back/shiny/female/", "png"), + "animated": { + "front_default": ("pokemon/versions/generation-iv/heartgold-soulsilver/animated/", "png"), + "front_female": ("pokemon/versions/generation-iv/heartgold-soulsilver/animated/female/", "png"), + "front_shiny": ("pokemon/versions/generation-iv/heartgold-soulsilver/animated/shiny/", "png"), + "front_shiny_female": ( + "pokemon/versions/generation-iv/heartgold-soulsilver/animated/shiny/female/", + "png", + ), + }, + }, + "icons": { + "front_default": ("pokemon/versions/generation-iv/icons/", "png"), + }, + "platinum": { + "front_default": ("pokemon/versions/generation-iv/platinum/", "png"), + "front_female": ("pokemon/versions/generation-iv/platinum/female/", "png"), + "front_shiny": ("pokemon/versions/generation-iv/platinum/shiny/", "png"), + "front_shiny_female": ("pokemon/versions/generation-iv/platinum/shiny/female/", "png"), + "back_default": ("pokemon/versions/generation-iv/platinum/back/", "png"), + "back_female": ("pokemon/versions/generation-iv/platinum/back/female/", "png"), + "back_shiny": ("pokemon/versions/generation-iv/platinum/back/shiny/", "png"), + "back_shiny_female": ("pokemon/versions/generation-iv/platinum/back/shiny/female/", "png"), + "animated": { + "front_default": ("pokemon/versions/generation-iv/platinum/animated/", "png"), + "front_female": ("pokemon/versions/generation-iv/platinum/animated/female/", "png"), + "front_shiny": ("pokemon/versions/generation-iv/platinum/animated/shiny/", "png"), + "front_shiny_female": ("pokemon/versions/generation-iv/platinum/animated/shiny/female/", "png"), + }, + }, + }, + "generation-v": { + "black-white": { + "front_default": ("pokemon/versions/generation-v/black-white/", "png"), + "front_female": ("pokemon/versions/generation-v/black-white/female/", "png"), + "front_shiny": ("pokemon/versions/generation-v/black-white/shiny/", "png"), + "front_shiny_female": ("pokemon/versions/generation-v/black-white/shiny/female/", "png"), + "back_default": ("pokemon/versions/generation-v/black-white/back/", "png"), + "back_female": ("pokemon/versions/generation-v/black-white/back/female/", "png"), + "back_shiny": ("pokemon/versions/generation-v/black-white/back/shiny/", "png"), + "back_shiny_female": ("pokemon/versions/generation-v/black-white/back/shiny/female/", "png"), + "animated": { + "front_default": ("pokemon/versions/generation-v/black-white/animated/", "gif"), + "front_female": ("pokemon/versions/generation-v/black-white/animated/female/", "gif"), + "front_shiny": ("pokemon/versions/generation-v/black-white/animated/shiny/", "gif"), + "front_shiny_female": ("pokemon/versions/generation-v/black-white/animated/shiny/female/", "gif"), + "back_default": ("pokemon/versions/generation-v/black-white/animated/back/", "gif"), + "back_female": ("pokemon/versions/generation-v/black-white/animated/back/female/", "gif"), + "back_shiny": ("pokemon/versions/generation-v/black-white/animated/back/shiny/", "gif"), + "back_shiny_female": ( + "pokemon/versions/generation-v/black-white/animated/back/shiny/female/", + "gif", + ), + }, + }, + "icons": { + "front_default": ("pokemon/versions/generation-v/icons/", "png"), + "animated": { + "front_default": ("pokemon/versions/generation-v/icons/animated/", "png"), + }, + }, + }, + "generation-vi": { + "icons": { + "front_default": ("pokemon/versions/generation-vi/icons/", "png"), + "front_female": ("pokemon/versions/generation-vi/icons/female/", "png"), + }, + "omega-ruby-alpha-sapphire": { + "front_default": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/", "png"), + "front_female": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/female/", "png"), + "front_shiny": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/shiny/", "png"), + "front_shiny_female": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/shiny/female/", "png"), + "back_default": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/back/", "png"), + "back_female": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/back/female/", "png"), + "back_shiny": ("pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/back/shiny/", "png"), + "back_shiny_female": ( + "pokemon/versions/generation-vi/omega-ruby-alpha-sapphire/back/shiny/female/", + "png", + ), + }, + "x-y": { + "front_default": ("pokemon/versions/generation-vi/x-y/", "png"), + "front_female": ("pokemon/versions/generation-vi/x-y/female/", "png"), + "front_shiny": ("pokemon/versions/generation-vi/x-y/shiny/", "png"), + "front_shiny_female": ("pokemon/versions/generation-vi/x-y/shiny/female/", "png"), + "back_default": ("pokemon/versions/generation-vi/x-y/back/", "png"), + "back_female": ("pokemon/versions/generation-vi/x-y/back/female/", "png"), + "back_shiny": ("pokemon/versions/generation-vi/x-y/back/shiny/", "png"), + "back_shiny_female": ("pokemon/versions/generation-vi/x-y/back/shiny/female/", "png"), + }, + }, + "generation-vii": { + "icons": { + "front_default": ("pokemon/versions/generation-vii/icons/", "png"), + "front_female": ("pokemon/versions/generation-vii/icons/female/", "png"), + }, + "lets-go-pikachu-lets-go-eevee": { + "front_default": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/", "gif"), + "front_female": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/female/", "gif"), + "front_shiny": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/shiny/", "gif"), + "front_shiny_female": ( + "pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/shiny/female/", + "gif", + ), + "back_default": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/back/", "gif"), + "back_female": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/back/female/", "gif"), + "back_shiny": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/back/shiny/", "gif"), + "back_shiny_female": ( + "pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/back/shiny/female/", + "gif", + ), + "icons": { + "front_default": ("pokemon/versions/generation-vii/lets-go-pikachu-lets-go-eevee/icons/", "png") + }, + }, + "ultra-sun-ultra-moon": { + "front_default": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/", "gif"), + "front_female": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/female/", "gif"), + "front_shiny": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/shiny/", "gif"), + "front_shiny_female": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/shiny/female/", "gif"), + "back_default": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/back/", "gif"), + "back_female": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/back/female/", "gif"), + "back_shiny": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/back/shiny/", "gif"), + "back_shiny_female": ("pokemon/versions/generation-vii/ultra-sun-ultra-moon/back/shiny/female/", "gif"), + }, + }, + "generation-viii": { + "brilliant-diamond-shining-pearl": { + "front_default": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/", "webp"), + "front_female": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/female/", "webp"), + "front_shiny": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/shiny/", "webp"), + "front_shiny_female": ( + "pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/shiny/female/", + "webp", + ), + "back_default": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/back/", "webp"), + "back_female": ( + "pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/back/female/", + "webp", + ), + "back_shiny": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/back/shiny/", "webp"), + "back_shiny_female": ( + "pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/back/shiny/female/", + "webp", + ), + "icons": { + "front_default": ("pokemon/versions/generation-viii/brilliant-diamond-shining-pearl/icons/", "png"), + }, + }, + "legends-arceus": { + "front_default": ("pokemon/versions/generation-viii/legends-arceus/", "webp"), + "front_female": ("pokemon/versions/generation-viii/legends-arceus/female/", "webp"), + "front_shiny": ("pokemon/versions/generation-viii/legends-arceus/shiny/", "webp"), + "front_shiny_female": ("pokemon/versions/generation-viii/legends-arceus/shiny/female/", "webp"), + "back_default": ("pokemon/versions/generation-viii/legends-arceus/back/", "webp"), + "back_female": ("pokemon/versions/generation-viii/legends-arceus/back/female/", "webp"), + "back_shiny": ("pokemon/versions/generation-viii/legends-arceus/back/shiny/", "webp"), + "back_shiny_female": ("pokemon/versions/generation-viii/legends-arceus/back/shiny/female/", "webp"), + "icons": { + "front_default": ("pokemon/versions/generation-viii/legends-arceus/icons/", "png"), + "front_female": ("pokemon/versions/generation-viii/legends-arceus/icons/female/", "png"), + "front_shiny": ("pokemon/versions/generation-viii/legends-arceus/icons/shiny/", "png"), + "front_shiny_female": ( + "pokemon/versions/generation-viii/legends-arceus/icons/shiny/female/", + "png", + ), + }, + }, + "sword-shield": { + "front_default": ("pokemon/versions/generation-viii/sword-shield/", "webp"), + "front_female": ("pokemon/versions/generation-viii/sword-shield/female/", "webp"), + "front_shiny": ("pokemon/versions/generation-viii/sword-shield/shiny/", "webp"), + "front_shiny_female": ("pokemon/versions/generation-viii/sword-shield/shiny/female/", "webp"), + "back_default": ("pokemon/versions/generation-viii/sword-shield/back/", "webp"), + "back_female": ("pokemon/versions/generation-viii/sword-shield/back/female/", "webp"), + "back_shiny": ("pokemon/versions/generation-viii/sword-shield/back/shiny/", "webp"), + "back_shiny_female": ("pokemon/versions/generation-viii/sword-shield/back/shiny/female/", "webp"), + "icons": { + "front_default": ("pokemon/versions/generation-viii/sword-shield/icons/", "png"), + "front_female": ("pokemon/versions/generation-viii/sword-shield/icons/female/", "png"), + }, + }, + }, + "generation-ix": { + "champions": { + "front_default": ("pokemon/versions/generation-ix/champions/", "webp"), + "front_female": ("pokemon/versions/generation-ix/champions/female/", "webp"), + "front_shiny": ("pokemon/versions/generation-ix/champions/shiny/", "webp"), + "front_shiny_female": ("pokemon/versions/generation-ix/champions/shiny/female/", "webp"), + "back_default": ("pokemon/versions/generation-ix/champions/back/", "webp"), + "back_female": ("pokemon/versions/generation-ix/champions/back/female/", "webp"), + "back_shiny": ("pokemon/versions/generation-ix/champions/back/shiny/", "webp"), + "back_shiny_female": ("pokemon/versions/generation-ix/champions/back/shiny/female/", "webp"), + "icons": { + "front_default": ("pokemon/versions/generation-ix/champions/icons/", "png"), + "front_shiny": ("pokemon/versions/generation-ix/champions/icons/shiny/", "png"), + }, + }, + "legends-za": { + "front_default": ("pokemon/versions/generation-ix/legends-za/", "webp"), + "front_female": ("pokemon/versions/generation-ix/legends-za/female/", "webp"), + "front_shiny": ("pokemon/versions/generation-ix/legends-za/shiny/", "webp"), + "front_shiny_female": ("pokemon/versions/generation-ix/legends-za/shiny/female/", "webp"), + "back_default": ("pokemon/versions/generation-ix/legends-za/back/", "webp"), + "back_female": ("pokemon/versions/generation-ix/legends-za/back/female/", "webp"), + "back_shiny": ("pokemon/versions/generation-ix/legends-za/back/shiny/", "webp"), + "back_shiny_female": ("pokemon/versions/generation-ix/legends-za/back/shiny/female/", "webp"), + "icons": { + "front_default": ("pokemon/versions/generation-ix/legends-za/icons/", "png"), + "front_female": ("pokemon/versions/generation-ix/legends-za/icons/female/", "png"), + "front_shiny": ("pokemon/versions/generation-ix/legends-za/icons/shiny/", "png"), + "front_shiny_female": ("pokemon/versions/generation-ix/legends-za/icons/shiny/female/", "png"), + }, + }, + "scarlet-violet": { + "front_default": ("pokemon/versions/generation-ix/scarlet-violet/", "webp"), + "front_female": ("pokemon/versions/generation-ix/scarlet-violet/female/", "webp"), + "front_shiny": ("pokemon/versions/generation-ix/scarlet-violet/shiny/", "webp"), + "front_shiny_female": ("pokemon/versions/generation-ix/scarlet-violet/shiny/female/", "webp"), + "back_default": ("pokemon/versions/generation-ix/scarlet-violet/back/", "webp"), + "back_female": ("pokemon/versions/generation-ix/scarlet-violet/back/female/", "webp"), + "back_shiny": ("pokemon/versions/generation-ix/scarlet-violet/back/shiny/", "webp"), + "back_shiny_female": ("pokemon/versions/generation-ix/scarlet-violet/back/shiny/female/", "webp"), + "icons": { + "front_default": ("pokemon/versions/generation-ix/scarlet-violet/icons/", "png"), + "front_female": ("pokemon/versions/generation-ix/scarlet-violet/icons/female/", "png"), + }, + }, + }, + }, +} + + +def file_path_or_none(file_name: str, image_file: bool = True) -> str | None: + if not image_file: + return SOUND_DIR.format(file_name=file_name) if file_name in RESOURCE_CRIES else None + return MEDIA_DIR.format(file_name=file_name) if file_name in RESOURCE_IMAGES else None + + +def resolve_sprite_config(config: dict[str, Any], lookup: Callable[[str, str], str | None]) -> dict[str, Any]: + resolved: dict[str, Any] = {} + for name, value in config.items(): + if isinstance(value, dict): + resolved[name] = resolve_sprite_config(value, lookup) # pyright: ignore[reportUnknownArgumentType] + else: + path, extension = value + resolved[name] = lookup(path, extension) + return resolved + + +def pokemon_sprite_lookup(info: list[str]) -> Callable[[str, str], str | None]: + pokemon_id, identifier, species_id = info[:3] + file_names = [pokemon_id] + if "-" in identifier: + file_names.append(f"{species_id}-{identifier.split('-', 1)[1]}") + + def lookup(path: str, extension: str) -> str | None: + for file_name in file_names: + sprite = file_path_or_none(f"{path}{file_name}.{extension}") + if sprite: + return sprite + return None + + return lookup + + +def pokemon_form_sprite_lookup(info: list[str]) -> Callable[[str, str], str | None]: + form_identifier = info[2] + pokemon_id = int(info[3]) + is_default = info[5] == "1" + + file_names: list[str] = [] + if form_identifier: + file_names.append(f"{pokemon_id}-{form_identifier}") + if is_default or not form_identifier: + file_names.append(str(pokemon_id)) + + def lookup(path: str, extension: str) -> str | None: + for file_name in file_names: + sprite = file_path_or_none(f"{path}{file_name}.{extension}") + if sprite: + return sprite + return None + + return lookup diff --git a/pokemon_v2/test_models.py b/pokemon_v2/test_models.py index 9a54394d4..06036713b 100644 --- a/pokemon_v2/test_models.py +++ b/pokemon_v2/test_models.py @@ -7,8 +7,8 @@ from django.test import TestCase from typing_extensions import override -from data.v2 import build -from data.v2.build import POKEMON_SPRITE_CONFIG, _pokemon_form_sprite_lookup +from data.v2.utils import media +from data.v2.utils.media import POKEMON_SPRITE_CONFIG, pokemon_form_sprite_lookup from pokemon_v2.models import * @@ -255,14 +255,14 @@ def test_form_sprite_lookup_falls_back_to_pokemon_id_for_default_forms(self): unown_b = ["10001", "unown-b", "b", "201", "3", "0"] images = {"pokemon/201.png", "pokemon/back/201-a.png", "pokemon/back/201.png"} - with mock.patch.object(build, "RESOURCE_IMAGES", images): - unown_a_lookup = _pokemon_form_sprite_lookup(unown_a) - unown_b_lookup = _pokemon_form_sprite_lookup(unown_b) + with mock.patch.object(media, "RESOURCE_IMAGES", images): + unown_a_lookup = pokemon_form_sprite_lookup(unown_a) + unown_b_lookup = pokemon_form_sprite_lookup(unown_b) - self.assertEqual(unown_a_lookup("pokemon/", "png"), build.MEDIA_DIR.format(file_name="pokemon/201.png")) + self.assertEqual(unown_a_lookup("pokemon/", "png"), media.MEDIA_DIR.format(file_name="pokemon/201.png")) # a form-specific sprite still takes precedence over the fallback self.assertEqual( - unown_a_lookup("pokemon/back/", "png"), build.MEDIA_DIR.format(file_name="pokemon/back/201-a.png") + unown_a_lookup("pokemon/back/", "png"), media.MEDIA_DIR.format(file_name="pokemon/back/201-a.png") ) # non-default forms never borrow the base pokemon's sprite self.assertIsNone(unown_b_lookup("pokemon/", "png")) diff --git a/ruff.toml b/ruff.toml index d32503079..0cc7fa747 100644 --- a/ruff.toml +++ b/ruff.toml @@ -35,7 +35,6 @@ preview = true "pokemon_v2/tests.py" = ["ANN", "UP032", "F403", "F405"] "pokemon_v2/test_models.py" = ["ANN", "F403", "F405"] # TODO: Data loading scripts -"data/v2/build.py" = ["ANN", "ERA001", "F403", "F405"] "data/v2/__init__.py" = ["F403", "F405"] # Django migration generated code "pokemon_v2/migrations/*.py" = ["RUF012", "ERA001", "E501"] From e39678f1dca1327728e92da3b9a3b5b98dffed22 Mon Sep 17 00:00:00 2001 From: Triyan Mukherjee Date: Sat, 31 Oct 2026 01:20:41 +0530 Subject: [PATCH 2/4] feat: Optimize pokemon and encounters builder --- data/v2/build.py | 71 ++++++++++++++++++++++++++++++---------- data/v2/utils/helpers.py | 7 +++- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/data/v2/build.py b/data/v2/build.py index 8e38413ff..b7297117c 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -13,9 +13,10 @@ import re from collections.abc import Iterator +from contextlib import closing from copy import deepcopy -from data.v2.utils.helpers import build_generic, load_data +from data.v2.utils.helpers import BATCH_SIZE, build_generic, load_data from data.v2.utils.media import ( POKEMON_SPRITE_CONFIG, TYPE_SPRITE_GAME_MAP, @@ -1172,6 +1173,31 @@ def location_area_names_from_rows(info: list[str]) -> Iterator[LocationAreaName] build_generic((LocationAreaName,), "location_area_prose.csv", location_area_names_from_rows) +def _link_species_evolutions() -> None: + species_ids = set(PokemonSpecies.objects.values_list("pk", flat=True)) + + def species_id_from_csv(value: str) -> int: + species_id = int(value) + if species_id not in species_ids: + PokemonSpecies.objects.get(pk=species_id) + return species_id + + species_links: list[PokemonSpecies] = [] + with closing(load_data("pokemon_species.csv")) as data: + next(data, None) + for info in data: + if info[3] == "": + continue + parent_id = species_id_from_csv(info[3]) + species_id = species_id_from_csv(info[0]) + species_links.append(PokemonSpecies(pk=species_id, evolves_from_species_id=parent_id)) + if len(species_links) >= BATCH_SIZE: + PokemonSpecies.objects.bulk_update(species_links, ["evolves_from_species"], batch_size=BATCH_SIZE) + species_links = [] + if species_links: + PokemonSpecies.objects.bulk_update(species_links, ["evolves_from_species"], batch_size=BATCH_SIZE) + + def _build_pokemons() -> None: def pokemon_colors_from_rows(info: list[str]) -> Iterator[PokemonColor]: yield PokemonColor(id=int(info[0]), name=info[1]) @@ -1229,14 +1255,7 @@ def pokemon_species_from_rows(info: list[str]) -> Iterator[PokemonSpecies]: build_generic((PokemonSpecies,), "pokemon_species.csv", pokemon_species_from_rows) # PokemonSpecies.evolves_from_species can't be set until all the species are created - data = load_data("pokemon_species.csv") - for index, info in enumerate(data): - if index > 0: - evolves = PokemonSpecies.objects.get(pk=int(info[3])) if info[3] != "" else None - if evolves: - species = PokemonSpecies.objects.get(pk=int(info[0])) - species.evolves_from_species = evolves - species.save() + _link_species_evolutions() def pokemon_species_names_from_rows(info: list[str]) -> Iterator[PokemonSpeciesName]: yield PokemonSpeciesName( @@ -1285,11 +1304,16 @@ def pokemon_from_rows(info: list[str]) -> Iterator[Pokemon]: build_generic((Pokemon,), "pokemon.csv", pokemon_from_rows) + pokemon_ids = set(Pokemon.objects.values_list("pk", flat=True)) + def pokemon_sprites_from_rows(info: list[str]) -> Iterator[PokemonSprites]: sprites = resolve_sprite_config(POKEMON_SPRITE_CONFIG, pokemon_sprite_lookup(info)) + pokemon_id = int(info[0]) + if pokemon_id not in pokemon_ids: + Pokemon.objects.get(pk=pokemon_id) yield PokemonSprites( - id=int(info[0]), - pokemon=Pokemon.objects.get(pk=int(info[0])), + id=pokemon_id, + pokemon_id=pokemon_id, sprites=sprites, ) @@ -1307,9 +1331,12 @@ def pokemon_cries_from_rows(info: list[str]) -> Iterator[PokemonCries]: "latest": try_cry_names(latest, info, "ogg"), "legacy": try_cry_names(legacy, info, "ogg"), } + pokemon_id = int(info[0]) + if pokemon_id not in pokemon_ids: + Pokemon.objects.get(pk=pokemon_id) yield PokemonCries( - id=int(info[0]), - pokemon=Pokemon.objects.get(pk=int(info[0])), + id=pokemon_id, + pokemon_id=pokemon_id, cries=cries, ) @@ -1418,10 +1445,19 @@ def pokemon_form_sprites_from_rows(info: list[str]) -> Iterator[PokemonFormSprit build_generic((PokemonFormSprites,), "pokemon_forms.csv", pokemon_form_sprites_from_rows) + form_ids = set(PokemonForm.objects.values_list("pk", flat=True)) + language_ids = set(Language.objects.values_list("pk", flat=True)) + def pokemon_form_names_from_rows(info: list[str]) -> Iterator[PokemonFormName]: + form_id = int(info[0]) + if form_id not in form_ids: + PokemonForm.objects.get(pk=form_id) + language_id = int(info[1]) + if language_id not in language_ids: + Language.objects.get(pk=language_id) yield PokemonFormName( - pokemon_form=PokemonForm.objects.get(pk=int(info[0])), - language=Language.objects.get(pk=int(info[1])), + pokemon_form_id=form_id, + language_id=language_id, name=info[2], pokemon_name=info[3], ) @@ -1503,7 +1539,7 @@ def pokemon_moves_from_rows(info: list[str]) -> Iterator[PokemonMove]: order=int(info[5]) if info[5] != "" else None, ) - build_generic((PokemonMove,), "pokemon_moves.csv", pokemon_moves_from_rows) + build_generic((PokemonMove,), "pokemon_moves.csv", pokemon_moves_from_rows, batch_size=2_000) def pokemon_stats_from_rows(info: list[str]) -> Iterator[PokemonStat]: yield PokemonStat( @@ -1603,7 +1639,7 @@ def encounters_from_rows(info: list[str]) -> Iterator[Encounter]: max_level=int(info[6]), ) - build_generic((Encounter,), "encounters.csv", encounters_from_rows) + build_generic((Encounter,), "encounters.csv", encounters_from_rows, batch_size=2_000) def encounter_condition_values_from_rows(info: list[str]) -> Iterator[EncounterConditionValue]: yield EncounterConditionValue( @@ -1639,6 +1675,7 @@ def encounter_condition_value_maps_from_rows(info: list[str]) -> Iterator[Encoun (EncounterConditionValueMap,), "encounter_condition_value_map.csv", encounter_condition_value_maps_from_rows, + batch_size=2_000, ) def encounter_pokemon_details_from_rows(info: list[str]) -> Iterator[EncounterPokemonDetail]: diff --git a/data/v2/utils/helpers.py b/data/v2/utils/helpers.py index 502a4b1b4..9c97230e1 100644 --- a/data/v2/utils/helpers.py +++ b/data/v2/utils/helpers.py @@ -31,7 +31,12 @@ def build_generic( model_classes: tuple[type[Model], ...], file_name: str, csv_record_to_objects: Callable[[list[str]], Iterable[Model]], + *, + batch_size: int = BATCH_SIZE, ) -> None: + if batch_size < 1: + raise ValueError("batch_size must be positive") + batches: dict[type[Model], list[Model]] = {} for model_class in model_classes: @@ -47,7 +52,7 @@ def build_generic( batch = batches[model_class] batch.append(obj) - if len(batch) >= BATCH_SIZE: + if len(batch) >= batch_size: model_class.objects.bulk_create(batch) batches[model_class] = [] From f5189b26c73a8455c85e37144a238de0c052b18d Mon Sep 17 00:00:00 2001 From: Triyan Mukherjee Date: Sat, 31 Oct 2026 01:33:46 +0530 Subject: [PATCH 3/4] fix: Add time logging to build steps --- data/v2/build.py | 2 +- data/v2/utils/helpers.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/data/v2/build.py b/data/v2/build.py index b7297117c..5ccbcd920 100644 --- a/data/v2/build.py +++ b/data/v2/build.py @@ -1539,7 +1539,7 @@ def pokemon_moves_from_rows(info: list[str]) -> Iterator[PokemonMove]: order=int(info[5]) if info[5] != "" else None, ) - build_generic((PokemonMove,), "pokemon_moves.csv", pokemon_moves_from_rows, batch_size=2_000) + build_generic((PokemonMove,), "pokemon_moves.csv", pokemon_moves_from_rows, batch_size=4_000) def pokemon_stats_from_rows(info: list[str]) -> Iterator[PokemonStat]: yield PokemonStat( diff --git a/data/v2/utils/helpers.py b/data/v2/utils/helpers.py index 9c97230e1..2b979d112 100644 --- a/data/v2/utils/helpers.py +++ b/data/v2/utils/helpers.py @@ -2,6 +2,7 @@ from collections.abc import Callable, Generator, Iterable from contextlib import closing from pathlib import Path +from time import perf_counter from django.db import connection from django.db.models import Model @@ -15,16 +16,18 @@ def load_data(file_name: str) -> Generator[list[str], None, None]: yield from csv.reader(file, delimiter=",") -def clear_table(model: type[Model]) -> None: +def clear_table(model: type[Model]) -> float: table_name = model._meta.db_table + print("building " + table_name, flush=True) + started_at = perf_counter() model.objects.all().delete() - print("building " + table_name) with connection.cursor() as cursor: if connection.vendor == "sqlite": cursor.execute("DELETE FROM sqlite_sequence WHERE name = " + "'" + table_name + "'") else: # Reset DB auto increments to start at 1 cursor.execute("SELECT setval(pg_get_serial_sequence(" + "'" + table_name + "'" + ",'id'), 1, false);") + return started_at def build_generic( @@ -38,9 +41,10 @@ def build_generic( raise ValueError("batch_size must be positive") batches: dict[type[Model], list[Model]] = {} + build_started_at: dict[type[Model], float] = {} for model_class in model_classes: - clear_table(model_class) + build_started_at[model_class] = clear_table(model_class) batches[model_class] = [] with closing(load_data(file_name)) as csv_data: @@ -59,3 +63,5 @@ def build_generic( for model_class, batch in batches.items(): if batch: model_class.objects.bulk_create(batch) + elapsed = perf_counter() - build_started_at[model_class] + print(f"built {model_class._meta.db_table} in {elapsed:.2f}s", flush=True) From f841170a13683c7b52aaec77fe6cf4ac22baed9a Mon Sep 17 00:00:00 2001 From: Triyan Mukherjee Date: Sat, 31 Oct 2026 02:32:56 +0530 Subject: [PATCH 4/4] ci: Change github runners to arm64 runners for more efficiency --- .github/workflows/code-quality.yml | 40 +++------------------ .github/workflows/database.yml | 6 ++-- .github/workflows/docker-build-and-push.yml | 2 +- .github/workflows/docker-k8s.yml | 6 ++-- .github/workflows/nix-environment.yml | 2 +- .github/workflows/release.yml | 2 +- data/v2/__init__.py | 4 ++- ruff.toml | 2 -- 8 files changed, 16 insertions(+), 48 deletions(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 570b611d7..354c4c99d 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -18,9 +18,7 @@ on: jobs: ruff: name: Ruff (lint) - runs-on: ubuntu-latest - # TODO: remove once the codebase is fully lint-clean - continue-on-error: true + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 @@ -29,27 +27,11 @@ jobs: - name: Install dependencies run: make install - name: Lint - run: make lint-check || true - - name: Summary - if: always() - run: | - { - echo "## Ruff lint results" - echo - echo "
Expand to see all errors (backlog while the codebase is being linted incrementally)" - echo - echo '```' - uv run ruff check --output-format=concise . || true - echo '```' - echo - echo "
" - } >> "$GITHUB_STEP_SUMMARY" + run: make lint-check ty: name: ty (type-check) - runs-on: ubuntu-latest - # TODO: remove once the codebase is fully typed - continue-on-error: true + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 @@ -58,18 +40,4 @@ jobs: - name: Install dependencies run: make install - name: Type-check - run: make typecheck || true - - name: Summary - if: always() - run: | - { - echo "## ty type-check results" - echo - echo "
Expand to see all errors (backlog while the codebase is being typed incrementally)" - echo - echo '```' - uv run ty check --output-format=concise || true - echo '```' - echo - echo "
" - } >> "$GITHUB_STEP_SUMMARY" + run: make typecheck diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml index fa4a46198..4ba3256c2 100644 --- a/.github/workflows/database.yml +++ b/.github/workflows/database.yml @@ -34,7 +34,7 @@ jobs: ./csvlint -lazyquotes "$filename" # TODO: remove lazyquotes when https://github.com/Clever/csvlint/issues/45 will be addressed done openapi: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 @@ -45,7 +45,7 @@ jobs: make install-base make openapi-generate sqlite: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm strategy: fail-fast: false matrix: @@ -71,7 +71,7 @@ jobs: - name: Test data run: curl -Ss http://localhost:8000/api/v2/pokemon/1/ | grep -q 'bulbasaur' postgres: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/docker-build-and-push.yml b/.github/workflows/docker-build-and-push.yml index 285d55582..c2caa84dd 100644 --- a/.github/workflows/docker-build-and-push.yml +++ b/.github/workflows/docker-build-and-push.yml @@ -17,7 +17,7 @@ on: jobs: docker: if: ${{ !github.event.repository.fork }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/docker-k8s.yml b/.github/workflows/docker-k8s.yml index 183d6e2e9..6760031cc 100644 --- a/.github/workflows/docker-k8s.yml +++ b/.github/workflows/docker-k8s.yml @@ -16,7 +16,7 @@ on: jobs: docker: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 @@ -45,7 +45,7 @@ jobs: - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} k8s: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 @@ -112,7 +112,7 @@ jobs: - name: Get GQL output run: kubectl logs jobs/load-graphql compose: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/nix-environment.yml b/.github/workflows/nix-environment.yml index 798b1c497..894b94c66 100644 --- a/.github/workflows/nix-environment.yml +++ b/.github/workflows/nix-environment.yml @@ -16,7 +16,7 @@ on: jobs: nix-test: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6b49f55c9..54f898f05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ on: jobs: release: if: ${{ !github.event.repository.fork }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04-arm steps: - name: Checkout uses: actions/checkout@v6 diff --git a/data/v2/__init__.py b/data/v2/__init__.py index 39496a2f6..9212439dc 100644 --- a/data/v2/__init__.py +++ b/data/v2/__init__.py @@ -1 +1,3 @@ -from .build import * +from .build import build_all + +__all__: tuple[str, ...] = ("build_all",) diff --git a/ruff.toml b/ruff.toml index 0cc7fa747..40e593363 100644 --- a/ruff.toml +++ b/ruff.toml @@ -34,7 +34,5 @@ preview = true # TODO: Test files "pokemon_v2/tests.py" = ["ANN", "UP032", "F403", "F405"] "pokemon_v2/test_models.py" = ["ANN", "F403", "F405"] -# TODO: Data loading scripts -"data/v2/__init__.py" = ["F403", "F405"] # Django migration generated code "pokemon_v2/migrations/*.py" = ["RUF012", "ERA001", "E501"]