import json import re import sys import time from cogs5e.models.monster import Monster from cogs5e.models.sheet.base import BaseStats, Saves, Skills from cogs5e.models.sheet.spellcasting import Spellbook, SpellbookSpell def migrate_monster(old_monster): def spaced_to_camel(spaced): return re.sub(r"\s+(\w)", lambda m: m.group(1).upper(), spaced.lower()) for old_key in ('raw_saves', 'raw_skills'): if old_key in old_monster: del old_monster[old_key] if 'spellcasting' in old_monster and old_monster['spellcasting']: old_spellcasting = old_monster.pop('spellcasting') old_monster['spellbook'] = Spellbook({}, {}, [SpellbookSpell(s) for s in old_spellcasting['spells']], old_spellcasting['dc'], old_spellcasting['attackBonus'], old_spellcasting['casterLevel']).to_dict() else: old_monster['spellbook'] = Spellbook({}, {}, []).to_dict() base_stats = BaseStats( 0, old_monster.pop('strength'), old_monster.pop('dexterity'), old_monster.pop('constitution'), old_monster.pop('intelligence'), old_monster.pop('wisdom'), old_monster.pop('charisma') ) old_monster['ability_scores'] = base_stats.to_dict() old_saves = old_monster.pop('saves') saves = Saves.default(base_stats) save_updates = {} for save, value in old_saves.items(): if value != saves[save]: save_updates[save] = value saves.update(save_updates) old_monster['saves'] = saves.to_dict() old_skills = old_monster.pop('skills') skills = Skills.default(base_stats) skill_updates = {} for skill, value in old_skills.items(): name = spaced_to_camel(skill) if value != skills[name]: skill_updates[name] = value skills.update(skill_updates) old_monster['skills'] = skills.to_dict() new_monster = Monster.from_bestiary(old_monster) return new_monster def migrate(bestiary): new_monsters = [] for old_monster in bestiary['monsters']: new_monsters.append(migrate_monster(old_monster).to_dict()) bestiary['monsters'] = new_monsters return bestiary def local_test(fp): with open(fp) as f: bestiaries = json.load(f) num_monsters = sum(len(b['monsters']) for b in bestiaries) print(f"Migrating {len(bestiaries)} bestiaries ({num_monsters} monsters)...") new_bestiaries = [] for b in bestiaries: print(f"migrating {b['name']}") new_bestiaries.append(migrate(b)) out = fp.split('/')[-1] with open(f"temp/new-{out}", 'w') as f: json.dump(new_bestiaries, f, indent=2) print(f"Done migrating {len(new_bestiaries)} bestiaries.") async def from_db(mdb): import pymongo from bson import ObjectId coll_names = await mdb.list_collection_names() if "old_bestiaries" not in coll_names: print("Renaming bestiaries to old_bestiaries...") await mdb.bestiaries.rename("old_bestiaries") else: print("Dropping bestiaries_bak and making backup...") if "bestiaries_bak" in coll_names: await mdb.bestiaries_bak.drop() await mdb.bestiaries.rename("bestiaries_bak") num_old_bestiaries = await mdb.old_bestiaries.count_documents({}) print(f"Migrating {num_old_bestiaries} bestiaries...") async for old_bestiary in mdb.old_bestiaries.find({}): new_char = migrate(old_bestiary) new_char['_id'] = ObjectId(old_bestiary['_id']) await mdb.bestiaries.insert_one(new_char) print("Creating compound index on owner|critterdb_id...") await mdb.bestiaries.create_index([("owner", pymongo.ASCENDING), ("critterdb_id", pymongo.ASCENDING)], unique=True) num_bestiaries = await mdb.old_bestiaries.count_documents({}) print(f"Done migrating {num_bestiaries}/{num_old_bestiaries} bestiaries.") if num_bestiaries == num_old_bestiaries: print("It's probably safe to drop the collections old_bestiaries and bestiaries_bak now.") if __name__ == '__main__': import asyncio import motor.motor_asyncio import credentials start = time.time() if 'mdb' not in sys.argv: local_test("temp/bestiaries.json") else: input("Running full MDB migration. Press enter to continue.") mdb = motor.motor_asyncio.AsyncIOMotorClient(credentials.test_mongo_url if 'test' in sys.argv else "mongodb://localhost:27017").avrae asyncio.get_event_loop().run_until_complete(from_db(mdb)) end = time.time() print(f"Done! Took {end - start} seconds.")