Add --export flag to export all seed data from DB to JSON

Replaces --export-bosses with a unified --export that dumps games,
pokemon, evolutions, routes/encounters, and bosses to seeds/data/.
Each export function mirrors the corresponding API export endpoint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 12:39:00 +01:00
parent 053dece33e
commit 0a2d42a6d0
3 changed files with 235 additions and 54 deletions

View File

@@ -0,0 +1,19 @@
---
# nuzlocke-tracker-009n
title: Add CLI export for all seed data types
status: completed
type: feature
priority: normal
created_at: 2026-02-08T11:37:27Z
updated_at: 2026-02-08T11:38:48Z
---
Add export functions for games, pokemon, routes/encounters, and evolutions to the seed CLI, matching the existing export API endpoints. Consolidate with the existing --export-bosses into a single --export flag that dumps everything.
## Checklist
- [x] Add export_games() to run.py — writes games.json
- [x] Add export_pokemon() to run.py — writes pokemon.json
- [x] Add export_routes() to run.py — writes {game_slug}.json per game (routes + encounters)
- [x] Add export_evolutions() to run.py — writes evolutions.json
- [x] Replace --export-bosses with --export flag that exports all data types
- [x] Update __main__.py docstring

View File

@@ -3,22 +3,22 @@
Usage: Usage:
python -m app.seeds # Run seed python -m app.seeds # Run seed
python -m app.seeds --verify # Run seed + verification python -m app.seeds --verify # Run seed + verification
python -m app.seeds --export-bosses # Export boss data to seed JSON files python -m app.seeds --export # Export all seed data from DB to JSON files
""" """
import asyncio import asyncio
import sys import sys
from app.core.database import engine from app.core.database import engine
from app.seeds.run import export_bosses, seed, verify from app.seeds.run import export_all, seed, verify
async def main(): async def main():
verbose = "--verbose" in sys.argv or "-v" in sys.argv verbose = "--verbose" in sys.argv or "-v" in sys.argv
engine.echo = verbose engine.echo = verbose
if "--export-bosses" in sys.argv: if "--export" in sys.argv:
await export_bosses() await export_all()
return return
await seed() await seed()

View File

@@ -10,6 +10,7 @@ from sqlalchemy.orm import selectinload
from app.core.database import async_session from app.core.database import async_session
from app.models.boss_battle import BossBattle from app.models.boss_battle import BossBattle
from app.models.boss_pokemon import BossPokemon from app.models.boss_pokemon import BossPokemon
from app.models.evolution import Evolution
from app.models.game import Game from app.models.game import Game
from app.models.pokemon import Pokemon from app.models.pokemon import Pokemon
from app.models.route import Route from app.models.route import Route
@@ -220,22 +221,189 @@ async def verify():
print("\nVerification complete!") print("\nVerification complete!")
async def export_bosses(): def _write_json(filename: str, data) -> Path:
"""Export boss battles from the database to seed JSON files.""" """Write data as JSON to DATA_DIR, return the path."""
print("Exporting boss battles...") out_path = DATA_DIR / filename
with open(out_path, "w") as f:
json.dump(data, f, indent=2)
f.write("\n")
return out_path
async def export_all():
"""Export all seed data from the database to JSON files."""
async with async_session() as session: async with async_session() as session:
# Load version group data to get the first game slug for filenames
with open(VG_JSON) as f: with open(VG_JSON) as f:
vg_data = json.load(f) vg_data = json.load(f)
# Query all version groups with their games await _export_games(session)
vg_result = await session.execute( await _export_pokemon(session)
select(VersionGroup).options(selectinload(VersionGroup.games)) await _export_evolutions(session)
) await _export_routes(session, vg_data)
version_groups = vg_result.scalars().all() await _export_bosses(session, vg_data)
slug_to_vg = {vg.slug: vg for vg in version_groups} print("Export complete!")
async def _export_games(session: AsyncSession):
"""Export games to games.json."""
result = await session.execute(select(Game).order_by(Game.name))
games = result.scalars().all()
data = [
{
"name": g.name,
"slug": g.slug,
"generation": g.generation,
"region": g.region,
"release_year": g.release_year,
"color": g.color,
}
for g in games
]
_write_json("games.json", data)
print(f"Games: {len(data)} exported")
async def _export_pokemon(session: AsyncSession):
"""Export pokemon to pokemon.json."""
result = await session.execute(select(Pokemon).order_by(Pokemon.pokeapi_id))
pokemon_list = result.scalars().all()
data = [
{
"pokeapi_id": p.pokeapi_id,
"national_dex": p.national_dex,
"name": p.name,
"types": p.types,
"sprite_url": p.sprite_url,
}
for p in pokemon_list
]
_write_json("pokemon.json", data)
print(f"Pokemon: {len(data)} exported")
async def _export_evolutions(session: AsyncSession):
"""Export evolutions to evolutions.json."""
result = await session.execute(
select(Evolution)
.options(
selectinload(Evolution.from_pokemon),
selectinload(Evolution.to_pokemon),
)
.order_by(Evolution.id)
)
evolutions = result.scalars().all()
data = [
{
"from_pokeapi_id": e.from_pokemon.pokeapi_id,
"to_pokeapi_id": e.to_pokemon.pokeapi_id,
"trigger": e.trigger,
"min_level": e.min_level,
"item": e.item,
"held_item": e.held_item,
"condition": e.condition,
"region": e.region,
}
for e in evolutions
]
_write_json("evolutions.json", data)
print(f"Evolutions: {len(data)} exported")
async def _export_routes(session: AsyncSession, vg_data: dict):
"""Export routes and encounters per game."""
# Get all games keyed by slug
game_result = await session.execute(select(Game))
games_by_slug = {g.slug: g for g in game_result.scalars().all()}
exported = 0
for vg_slug, vg_info in vg_data.items():
for game_slug in vg_info["games"]:
game = games_by_slug.get(game_slug)
if game is None or game.version_group_id is None:
continue
# Load routes for this version group with encounters + pokemon
result = await session.execute(
select(Route)
.where(Route.version_group_id == game.version_group_id)
.options(
selectinload(Route.route_encounters).selectinload(
RouteEncounter.pokemon
),
)
.order_by(Route.order)
)
routes = result.scalars().all()
if not routes:
continue
parent_routes = [r for r in routes if r.parent_route_id is None]
children_by_parent: dict[int, list[Route]] = {}
for r in routes:
if r.parent_route_id is not None:
children_by_parent.setdefault(r.parent_route_id, []).append(r)
def format_encounters(route: Route) -> list[dict]:
game_encounters = [
enc
for enc in route.route_encounters
if enc.game_id == game.id
]
return [
{
"pokeapi_id": enc.pokemon.pokeapi_id,
"pokemon_name": enc.pokemon.name,
"method": enc.encounter_method,
"encounter_rate": enc.encounter_rate,
"min_level": enc.min_level,
"max_level": enc.max_level,
}
for enc in sorted(game_encounters, key=lambda e: -e.encounter_rate)
]
def format_child(route: Route) -> dict:
data: dict = {
"name": route.name,
"order": route.order,
"encounters": format_encounters(route),
}
if route.pinwheel_zone is not None:
data["pinwheel_zone"] = route.pinwheel_zone
return data
def format_route(route: Route) -> dict:
data: dict = {
"name": route.name,
"order": route.order,
"encounters": format_encounters(route),
}
children = children_by_parent.get(route.id, [])
if children:
data["children"] = [
format_child(c)
for c in sorted(children, key=lambda r: r.order)
]
return data
route_data = [format_route(r) for r in parent_routes]
_write_json(f"{game_slug}.json", route_data)
exported += 1
print(f"Routes: {exported} game files exported")
async def _export_bosses(session: AsyncSession, vg_data: dict):
"""Export boss battles per version group."""
vg_result = await session.execute(select(VersionGroup))
slug_to_vg = {vg.slug: vg for vg in vg_result.scalars().all()}
exported = 0 exported = 0
for vg_slug, vg_info in vg_data.items(): for vg_slug, vg_info in vg_data.items():
@@ -243,7 +411,6 @@ async def export_bosses():
if vg is None: if vg is None:
continue continue
# Query boss battles for this version group
result = await session.execute( result = await session.execute(
select(BossBattle) select(BossBattle)
.where(BossBattle.version_group_id == vg.id) .where(BossBattle.version_group_id == vg.id)
@@ -281,12 +448,7 @@ async def export_bosses():
for b in bosses for b in bosses
] ]
out_path = DATA_DIR / f"{first_game_slug}-bosses.json" _write_json(f"{first_game_slug}-bosses.json", data)
with open(out_path, "w") as f:
json.dump(data, f, indent=2)
f.write("\n")
print(f" {vg_slug}: {len(bosses)} bosses -> {out_path.name}")
exported += 1 exported += 1
print(f"Exported bosses for {exported} version groups.") print(f"Bosses: {exported} version group files exported")