From d60b472cdbb19c645a323efbe94e98a8021eafd7 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Mon, 23 Jun 2025 23:48:18 -0700 Subject: [PATCH 01/16] Moved get_session from main.py to database.py --- src/app/database.py | 7 ++++++- src/app/main.py | 7 +------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/database.py b/src/app/database.py index 47a1d14..a79540e 100644 --- a/src/app/database.py +++ b/src/app/database.py @@ -1,6 +1,6 @@ from pathlib import Path -from sqlmodel import SQLModel, create_engine +from sqlmodel import Session, SQLModel, create_engine app_dir = Path(__file__).resolve().parent project_dir = app_dir.parent @@ -13,3 +13,8 @@ def create_db_and_tables(): SQLModel.metadata.create_all(engine) + + +async def get_session(): + with Session(engine) as session: + yield session diff --git a/src/app/main.py b/src/app/main.py index d9190bf..f26ae47 100644 --- a/src/app/main.py +++ b/src/app/main.py @@ -5,7 +5,7 @@ from fastapi.staticfiles import StaticFiles from sqlmodel import Session, select -from src.app.database import engine +from src.app.database import get_session from src.app.model_enums import ( ItemCategory, ItemSellLocation, @@ -67,11 +67,6 @@ ) -async def get_session(): # place in database.py? - with Session(engine) as session: - yield session - - @app.get("/") def root(): return { From ebb24f744dfcd4f736b99ec9a423e1db090026ec Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 24 Jun 2025 00:03:25 -0700 Subject: [PATCH 02/16] Moved read_monsters endpoints to dqm1_endpoint router from main.py --- src/app/main.py | 98 +++++++++++++++---------------- src/app/routers/__init__.py | 0 src/app/routers/dqm1_endpoints.py | 78 ++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 49 deletions(-) create mode 100644 src/app/routers/__init__.py create mode 100644 src/app/routers/dqm1_endpoints.py diff --git a/src/app/main.py b/src/app/main.py index f26ae47..0c390c1 100644 --- a/src/app/main.py +++ b/src/app/main.py @@ -16,9 +16,6 @@ Item, MonsterBreedingLink, MonsterBreedingLinkReadWithInfo, - MonsterDetail, - MonsterDetailSkill, - MonsterDetailWithFamily, MonsterFamily, MonsterFamilyReadWithMonsterDetail, Skill, @@ -26,6 +23,7 @@ SkillCombineRead, SkillUpgradeRead, ) +from src.app.routers import dqm1_endpoints tags_metadata = [ { @@ -66,6 +64,8 @@ allow_headers=["*"], ) +app.include_router(dqm1_endpoints.router) + @app.get("/") def root(): @@ -74,52 +74,52 @@ def root(): } -@app.get( - "/dqm1/monsters", - response_model=List[MonsterDetailWithFamily], - tags=["dqm1 monsters"], -) -async def read_monsters( - *, session: Session = Depends(get_session), family: Optional[int] = None -): - """ - **Parameter Descriptions**
- **new_name** : updated name used in later Dragon Quest games
- **old_name** : name used in the game
- **description** : in game beastiary description
- **family** : a monster is part of one of 10 different monster families
- """ - monsters = select(MonsterDetail) - if family: - monsters = monsters.where(MonsterDetail.family_id == family) - monsters_result = session.exec(monsters).all() - return monsters_result - - -@app.get( - "/dqm1/monsters/{monster_id}", - response_model=MonsterDetailWithFamily, - tags=["dqm1 monsters"], -) -async def read_monster(*, session: Session = Depends(get_session), monster_id: int): - monster = session.get(MonsterDetail, monster_id) - if not monster: - raise HTTPException(status_code=404, detail="Monster not found") - return monster - - -@app.get( - "/dqm1/monstersandskill/{monster_id}", - response_model=MonsterDetailSkill, - tags=["dqm1 monsters"], -) -async def read_monster_skill( - *, session: Session = Depends(get_session), monster_id: int -): - monster = session.get(MonsterDetail, monster_id) - if not monster: - raise HTTPException(status_code=404, detail="Monster not found") - return monster +# @app.get( +# "/dqm1/monsters", +# response_model=List[MonsterDetailWithFamily], +# tags=["dqm1 monsters"], +# ) +# async def read_monsters( +# *, session: Session = Depends(get_session), family: Optional[int] = None +# ): +# """ +# **Parameter Descriptions**
+# **new_name** : updated name used in later Dragon Quest games
+# **old_name** : name used in the game
+# **description** : in game beastiary description
+# **family** : a monster is part of one of 10 different monster families
+# """ +# monsters = select(MonsterDetail) +# if family: +# monsters = monsters.where(MonsterDetail.family_id == family) +# monsters_result = session.exec(monsters).all() +# return monsters_result + + +# @app.get( +# "/dqm1/monsters/{monster_id}", +# response_model=MonsterDetailWithFamily, +# tags=["dqm1 monsters"], +# ) +# async def read_monster(*, session: Session = Depends(get_session), monster_id: int): +# monster = session.get(MonsterDetail, monster_id) +# if not monster: +# raise HTTPException(status_code=404, detail="Monster not found") +# return monster + + +# @app.get( +# "/dqm1/monstersandskill/{monster_id}", +# response_model=MonsterDetailSkill, +# tags=["dqm1 monsters"], +# ) +# async def read_monster_skill( +# *, session: Session = Depends(get_session), monster_id: int +# ): +# monster = session.get(MonsterDetail, monster_id) +# if not monster: +# raise HTTPException(status_code=404, detail="Monster not found") +# return monster @app.get( diff --git a/src/app/routers/__init__.py b/src/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py new file mode 100644 index 0000000..afb089a --- /dev/null +++ b/src/app/routers/dqm1_endpoints.py @@ -0,0 +1,78 @@ +from typing import List, Optional + +from fastapi import APIRouter, Depends, HTTPException +from sqlmodel import Session, select + +from src.app.database import get_session +from src.app.models import ( + MonsterDetail, + MonsterDetailWithFamily, + MonsterDetailSkill, +) + +router = APIRouter( + prefix="/dqm1", +) + +tags_metadata = [ + { + "name": "dqm1 monsters", + "description": "Monster list", + }, + { + "name": "dqm1 skills", + "description": "Skills that monsters learn and inherit", + }, + { + "name": "dqm1 items", + "description": "Useful items found in the game and their description", + }, +] + + +@router.get( + "/monsters", + response_model=List[MonsterDetailWithFamily], + tags=["dqm1 monsters"], +) +async def read_monsters( + *, session: Session = Depends(get_session), family: Optional[int] = None +): + """ + **Parameter Descriptions**
+ **new_name** : updated name used in later Dragon Quest games
+ **old_name** : name used in the game
+ **description** : in game beastiary description
+ **family** : a monster is part of one of 10 different monster families
+ """ + monsters = select(MonsterDetail) + if family: + monsters = monsters.where(MonsterDetail.family_id == family) + monsters_result = session.exec(monsters).all() + return monsters_result + + +@router.get( + "/monsters/{monster_id}", + response_model=MonsterDetailWithFamily, + tags=["dqm1 monsters"], +) +async def read_monster(*, session: Session = Depends(get_session), monster_id: int): + monster = session.get(MonsterDetail, monster_id) + if not monster: + raise HTTPException(status_code=404, detail="Monster not found") + return monster + + +@router.get( + "/monstersandskill/{monster_id}", + response_model=MonsterDetailSkill, + tags=["dqm1 monsters"], +) +async def read_monster_skill( + *, session: Session = Depends(get_session), monster_id: int +): + monster = session.get(MonsterDetail, monster_id) + if not monster: + raise HTTPException(status_code=404, detail="Monster not found") + return monster From 3f464280536962c483fadbe2e2675ebdab5cbf87 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 24 Jun 2025 00:41:13 -0700 Subject: [PATCH 03/16] Moved read_family endpoints from main.py to dqm1 router --- src/app/main.py | 62 ------------------------------- src/app/routers/dqm1_endpoints.py | 14 +++++++ 2 files changed, 14 insertions(+), 62 deletions(-) diff --git a/src/app/main.py b/src/app/main.py index 0c390c1..1112379 100644 --- a/src/app/main.py +++ b/src/app/main.py @@ -16,8 +16,6 @@ Item, MonsterBreedingLink, MonsterBreedingLinkReadWithInfo, - MonsterFamily, - MonsterFamilyReadWithMonsterDetail, Skill, SkillCombine, SkillCombineRead, @@ -74,66 +72,6 @@ def root(): } -# @app.get( -# "/dqm1/monsters", -# response_model=List[MonsterDetailWithFamily], -# tags=["dqm1 monsters"], -# ) -# async def read_monsters( -# *, session: Session = Depends(get_session), family: Optional[int] = None -# ): -# """ -# **Parameter Descriptions**
-# **new_name** : updated name used in later Dragon Quest games
-# **old_name** : name used in the game
-# **description** : in game beastiary description
-# **family** : a monster is part of one of 10 different monster families
-# """ -# monsters = select(MonsterDetail) -# if family: -# monsters = monsters.where(MonsterDetail.family_id == family) -# monsters_result = session.exec(monsters).all() -# return monsters_result - - -# @app.get( -# "/dqm1/monsters/{monster_id}", -# response_model=MonsterDetailWithFamily, -# tags=["dqm1 monsters"], -# ) -# async def read_monster(*, session: Session = Depends(get_session), monster_id: int): -# monster = session.get(MonsterDetail, monster_id) -# if not monster: -# raise HTTPException(status_code=404, detail="Monster not found") -# return monster - - -# @app.get( -# "/dqm1/monstersandskill/{monster_id}", -# response_model=MonsterDetailSkill, -# tags=["dqm1 monsters"], -# ) -# async def read_monster_skill( -# *, session: Session = Depends(get_session), monster_id: int -# ): -# monster = session.get(MonsterDetail, monster_id) -# if not monster: -# raise HTTPException(status_code=404, detail="Monster not found") -# return monster - - -@app.get( - "/dqm1/family/{family_id}", - response_model=MonsterFamilyReadWithMonsterDetail, - tags=["dqm1 monsters"], -) -async def read_family(*, session: Session = Depends(get_session), family_id: int): - family = session.get(MonsterFamily, family_id) - if not family: - raise HTTPException(status_code=404, detail="Family not found") - return family - - @app.get("/dqm1/skills", tags=["dqm1 skills"]) async def read_skills( *, diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py index afb089a..e8a49bf 100644 --- a/src/app/routers/dqm1_endpoints.py +++ b/src/app/routers/dqm1_endpoints.py @@ -8,6 +8,8 @@ MonsterDetail, MonsterDetailWithFamily, MonsterDetailSkill, + MonsterFamily, + MonsterFamilyReadWithMonsterDetail, ) router = APIRouter( @@ -76,3 +78,15 @@ async def read_monster_skill( if not monster: raise HTTPException(status_code=404, detail="Monster not found") return monster + + +@router.get( + "/family/{family_id}", + response_model=MonsterFamilyReadWithMonsterDetail, + tags=["dqm1 monsters"], +) +async def read_family(*, session: Session = Depends(get_session), family_id: int): + family = session.get(MonsterFamily, family_id) + if not family: + raise HTTPException(status_code=404, detail="Family not found") + return family From 285c314c841e55d05c46d49469e09534bfbfd2f4 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 24 Jun 2025 00:51:03 -0700 Subject: [PATCH 04/16] Moved read_skill endpoints from main.py to dqm1 router --- src/app/main.py | 43 ------------------------------- src/app/routers/dqm1_endpoints.py | 21 +++++++++++++++ 2 files changed, 21 insertions(+), 43 deletions(-) diff --git a/src/app/main.py b/src/app/main.py index 1112379..822ba16 100644 --- a/src/app/main.py +++ b/src/app/main.py @@ -9,17 +9,11 @@ from src.app.model_enums import ( ItemCategory, ItemSellLocation, - SkillCategory, - SkillFamily, ) from src.app.models import ( Item, MonsterBreedingLink, MonsterBreedingLinkReadWithInfo, - Skill, - SkillCombine, - SkillCombineRead, - SkillUpgradeRead, ) from src.app.routers import dqm1_endpoints @@ -72,43 +66,6 @@ def root(): } -@app.get("/dqm1/skills", tags=["dqm1 skills"]) -async def read_skills( - *, - session: Session = Depends(get_session), - category: Optional[SkillCategory] = None, - skill_family: Optional[SkillFamily] = None, -): - skills = select(Skill) - if category: - skills = skills.where(Skill.category_type == category) - if skill_family: - skills = skills.where(Skill.family_type == skill_family) - skills_result = session.exec(skills).all() - return skills_result - - -@app.get( - "/dqm1/skills/{skill_id}", response_model=SkillUpgradeRead, tags=["dqm1 skills"] -) -async def read_skill(*, session: Session = Depends(get_session), skill_id: int): - skill = session.get(Skill, skill_id) - if not skill: - raise HTTPException(status_code=404, detail="Skill not found") - return skill - - -@app.get( - "/dqm1/skillcombine/{skill_id}", - response_model=List[SkillCombineRead], - tags=["dqm1 skills"], -) -async def get_skill_combo(*, session: Session = Depends(get_session), skill_id: int): - query = select(SkillCombine).where(SkillCombine.combo_skill_id == skill_id) - skill = session.exec(query).all() - return skill - - @app.get("/dqm1/items", tags=["dqm1 items"]) async def read_items( *, diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py index e8a49bf..c6c3f44 100644 --- a/src/app/routers/dqm1_endpoints.py +++ b/src/app/routers/dqm1_endpoints.py @@ -4,12 +4,17 @@ from sqlmodel import Session, select from src.app.database import get_session +from src.app.model_enums import ( + SkillCategory, + SkillFamily, +) from src.app.models import ( MonsterDetail, MonsterDetailWithFamily, MonsterDetailSkill, MonsterFamily, MonsterFamilyReadWithMonsterDetail, + Skill, ) router = APIRouter( @@ -90,3 +95,19 @@ async def read_family(*, session: Session = Depends(get_session), family_id: int if not family: raise HTTPException(status_code=404, detail="Family not found") return family + + +@router.get("/dqm1/skills", tags=["dqm1 skills"]) +async def read_skills( + *, + session: Session = Depends(get_session), + category: Optional[SkillCategory] = None, + skill_family: Optional[SkillFamily] = None, +): + skills = select(Skill) + if category: + skills = skills.where(Skill.category_type == category) + if skill_family: + skills = skills.where(Skill.family_type == skill_family) + skills_result = session.exec(skills).all() + return skills_result From fdc0897dac8cc7fb69bbcb28a472a430e5256758 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 24 Jun 2025 00:51:40 -0700 Subject: [PATCH 05/16] Moved read_skill endpoints from main.py to dqm1 router --- src/app/routers/dqm1_endpoints.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py index c6c3f44..4639be7 100644 --- a/src/app/routers/dqm1_endpoints.py +++ b/src/app/routers/dqm1_endpoints.py @@ -15,6 +15,9 @@ MonsterFamily, MonsterFamilyReadWithMonsterDetail, Skill, + SkillCombine, + SkillCombineRead, + SkillUpgradeRead, ) router = APIRouter( @@ -97,7 +100,7 @@ async def read_family(*, session: Session = Depends(get_session), family_id: int return family -@router.get("/dqm1/skills", tags=["dqm1 skills"]) +@router.get("/skills", tags=["dqm1 skills"]) async def read_skills( *, session: Session = Depends(get_session), @@ -111,3 +114,22 @@ async def read_skills( skills = skills.where(Skill.family_type == skill_family) skills_result = session.exec(skills).all() return skills_result + + +@router.get("/skills/{skill_id}", response_model=SkillUpgradeRead, tags=["dqm1 skills"]) +async def read_skill(*, session: Session = Depends(get_session), skill_id: int): + skill = session.get(Skill, skill_id) + if not skill: + raise HTTPException(status_code=404, detail="Skill not found") + return skill + + +@router.get( + "/skillcombine/{skill_id}", + response_model=List[SkillCombineRead], + tags=["dqm1 skills"], +) +async def get_skill_combo(*, session: Session = Depends(get_session), skill_id: int): + query = select(SkillCombine).where(SkillCombine.combo_skill_id == skill_id) + skill = session.exec(query).all() + return skill From 55c1ad7ca2658237db91a1658457824df0ccbb30 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 24 Jun 2025 00:53:12 -0700 Subject: [PATCH 06/16] Moved read_item endpoints from main.py to dqm1 router --- src/app/routers/dqm1_endpoints.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py index 4639be7..8a59aac 100644 --- a/src/app/routers/dqm1_endpoints.py +++ b/src/app/routers/dqm1_endpoints.py @@ -5,10 +5,13 @@ from src.app.database import get_session from src.app.model_enums import ( + ItemCategory, + ItemSellLocation, SkillCategory, SkillFamily, ) from src.app.models import ( + Item, MonsterDetail, MonsterDetailWithFamily, MonsterDetailSkill, @@ -133,3 +136,27 @@ async def get_skill_combo(*, session: Session = Depends(get_session), skill_id: query = select(SkillCombine).where(SkillCombine.combo_skill_id == skill_id) skill = session.exec(query).all() return skill + + +@router.get("/items", tags=["dqm1 items"]) +async def read_items( + *, + session: Session = Depends(get_session), + category: Optional[ItemCategory] = None, + selllocation: Optional[ItemSellLocation] = None, +): + items = select(Item) + if category: + items = items.where(Item.item_category == category) + if selllocation: + items = items.where(Item.sell_location == selllocation) + items_result = session.exec(items).all() + return items_result + + +@router.get("/items/{item_id}", tags=["dqm1 items"]) +async def read_item(*, session: Session = Depends(get_session), item_id: int): + item = session.get(Item, item_id) + if not item: + raise HTTPException(status_code=404, detail="Item not found") + return item From 44509011ac1e38653e996c7d2e385994ab8fc6a1 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 24 Jun 2025 00:54:34 -0700 Subject: [PATCH 07/16] Moved breading combos endpoints from main.py to dqm1 router --- src/app/routers/dqm1_endpoints.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py index 8a59aac..e6f3e0f 100644 --- a/src/app/routers/dqm1_endpoints.py +++ b/src/app/routers/dqm1_endpoints.py @@ -12,6 +12,8 @@ ) from src.app.models import ( Item, + MonsterBreedingLink, + MonsterBreedingLinkReadWithInfo, MonsterDetail, MonsterDetailWithFamily, MonsterDetailSkill, @@ -160,3 +162,24 @@ async def read_item(*, session: Session = Depends(get_session), item_id: int): if not item: raise HTTPException(status_code=404, detail="Item not found") return item + + +@router.get( + "/breeding/{monster_id}", + response_model=List[MonsterBreedingLinkReadWithInfo], + tags=["dqm1 monsters"], +) +async def get_breeding_combos( + *, session: Session = Depends(get_session), monster_id: int +): + """ + Given a monster_id, finds all breeding combination that results in + the target monster or uses the target monster as a parent + """ + query = select(MonsterBreedingLink).where( + (MonsterBreedingLink.child_id == monster_id) + | (MonsterBreedingLink.pedigree_id == monster_id) + | (MonsterBreedingLink.parent2_id == monster_id) + ) + breeding_combos = session.exec(query).all() + return breeding_combos From be8c8cfb4760040518d1cf183d4966e037784e56 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 24 Jun 2025 00:56:58 -0700 Subject: [PATCH 08/16] Removed unused imports in main.py --- src/app/main.py | 60 +------------------------------------------------ 1 file changed, 1 insertion(+), 59 deletions(-) diff --git a/src/app/main.py b/src/app/main.py index 822ba16..0c3eabf 100644 --- a/src/app/main.py +++ b/src/app/main.py @@ -1,20 +1,7 @@ -from typing import List, Optional - -from fastapi import Depends, FastAPI, HTTPException +from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles -from sqlmodel import Session, select -from src.app.database import get_session -from src.app.model_enums import ( - ItemCategory, - ItemSellLocation, -) -from src.app.models import ( - Item, - MonsterBreedingLink, - MonsterBreedingLinkReadWithInfo, -) from src.app.routers import dqm1_endpoints tags_metadata = [ @@ -64,48 +51,3 @@ def root(): return { "message": ("Welcome to the DQMonsters API. " "Go to the Swagger UI interface") } - - -@app.get("/dqm1/items", tags=["dqm1 items"]) -async def read_items( - *, - session: Session = Depends(get_session), - category: Optional[ItemCategory] = None, - selllocation: Optional[ItemSellLocation] = None, -): - items = select(Item) - if category: - items = items.where(Item.item_category == category) - if selllocation: - items = items.where(Item.sell_location == selllocation) - items_result = session.exec(items).all() - return items_result - - -@app.get("/dqm1/items/{item_id}", tags=["dqm1 items"]) -async def read_item(*, session: Session = Depends(get_session), item_id: int): - item = session.get(Item, item_id) - if not item: - raise HTTPException(status_code=404, detail="Item not found") - return item - - -@app.get( - "/dqm1/breeding/{monster_id}", - response_model=List[MonsterBreedingLinkReadWithInfo], - tags=["dqm1 monsters"], -) -async def get_breeding_combos( - *, session: Session = Depends(get_session), monster_id: int -): - """ - Given a monster_id, finds all breeding combination that results in - the target monster or uses the target monster as a parent - """ - query = select(MonsterBreedingLink).where( - (MonsterBreedingLink.child_id == monster_id) - | (MonsterBreedingLink.pedigree_id == monster_id) - | (MonsterBreedingLink.parent2_id == monster_id) - ) - breeding_combos = session.exec(query).all() - return breeding_combos From 86bd51e37cbd38c4bf055bf75fb034aa20139a88 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 24 Jun 2025 01:00:30 -0700 Subject: [PATCH 09/16] Removed tags_metadata since it's needed in main.py --- src/app/routers/dqm1_endpoints.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py index e6f3e0f..bc6fbd8 100644 --- a/src/app/routers/dqm1_endpoints.py +++ b/src/app/routers/dqm1_endpoints.py @@ -29,21 +29,6 @@ prefix="/dqm1", ) -tags_metadata = [ - { - "name": "dqm1 monsters", - "description": "Monster list", - }, - { - "name": "dqm1 skills", - "description": "Skills that monsters learn and inherit", - }, - { - "name": "dqm1 items", - "description": "Useful items found in the game and their description", - }, -] - @router.get( "/monsters", From 9b2c6ffad9d49747f2dbd496f981541993b132a0 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 1 Jul 2025 15:49:15 -0700 Subject: [PATCH 10/16] refactor (models): Relocate DQM1 models to dedicated directory - Moved all DQM1 specific models from `src/app/models.py` to `src/app/models/dqm1/` for better organization - `src/app/models.py` file deleted because it's empty --- src/app/models/__init__.py | 0 src/app/models/dqm1/__init__.py | 0 src/app/models/dqm1/item.py | 17 + src/app/{models.py => models/dqm1/monster.py} | 414 +++++++----------- src/app/models/dqm1/skill.py | 120 +++++ 5 files changed, 287 insertions(+), 264 deletions(-) create mode 100644 src/app/models/__init__.py create mode 100644 src/app/models/dqm1/__init__.py create mode 100644 src/app/models/dqm1/item.py rename src/app/{models.py => models/dqm1/monster.py} (58%) create mode 100644 src/app/models/dqm1/skill.py diff --git a/src/app/models/__init__.py b/src/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app/models/dqm1/__init__.py b/src/app/models/dqm1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/app/models/dqm1/item.py b/src/app/models/dqm1/item.py new file mode 100644 index 0000000..708732c --- /dev/null +++ b/src/app/models/dqm1/item.py @@ -0,0 +1,17 @@ +from typing import Optional + +from sqlmodel import Field, SQLModel + + +class Item(SQLModel, table=True): + """ + Lists all items sold in shops and found in the field + """ + + id: Optional[int] = Field(default=None, primary_key=True) + item_name: str + item_category: str + item_description: str + price: Optional[int] = Field(default=None) + sell_price: Optional[int] = Field(default=None) + sell_location: str diff --git a/src/app/models.py b/src/app/models/dqm1/monster.py similarity index 58% rename from src/app/models.py rename to src/app/models/dqm1/monster.py index d372bdc..1666b7a 100644 --- a/src/app/models.py +++ b/src/app/models/dqm1/monster.py @@ -1,264 +1,150 @@ -from typing import List, Optional - -from sqlmodel import Field, Relationship, SQLModel - - -class MonsterSkillLink(SQLModel, table=True): - """ - many-to-many association table linking a monster to three different skills. - """ - - id: Optional[int] = Field(default=None, primary_key=True) - monster_id: Optional[int] = Field( - default=None, - foreign_key="monsterdetail.id", - ) - skill_id: Optional[int] = Field( - default=None, - foreign_key="skill.id", - ) - - -class MonsterDetailBase(SQLModel): - """ - Monster details from in-game bestiary. Shows name, family, and description. - """ - - new_name: str - old_name: str - description: str - - # one-to-many relation where a family is linked to many monsters - family_id: int = Field(foreign_key="monsterfamily.id") - - -class MonsterDetail(MonsterDetailBase, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - - family: List["MonsterFamily"] = Relationship(back_populates="monsters") - skills: List["Skill"] = Relationship( - back_populates="monsters", link_model=MonsterSkillLink - ) - - -class MonsterDetailRead(MonsterDetailBase): - id: int - - -class MonsterFamilyBase(SQLModel): - """ - There are 10 monster families in the game. - """ - - family_eng: str - - -class MonsterFamily(MonsterFamilyBase, table=True): - """ - one-to-many relation between family and monsters. - """ - - id: Optional[int] = Field(default=None, primary_key=True) - monsters: List[MonsterDetail] = Relationship(back_populates="family") - - -class MonsterFamilyRead(MonsterFamilyBase): - id: int - - -class MonsterDetailWithFamily(MonsterDetailRead): - family: Optional[MonsterFamilyRead] - - -class MonsterFamilyReadWithMonsterDetail(MonsterFamilyRead): - monsters: List[MonsterDetailRead] = [] - - -class MonsterBreedingLinkBase(SQLModel): - child_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id") - pedigree_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id") - parent2_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id") - pedigree_family_id: Optional[int] = Field( - default=None, foreign_key="monsterfamily.id" - ) - family2_id: Optional[int] = Field(default=None, foreign_key="monsterfamily.id") - - -class MonsterBreedingLink(MonsterBreedingLinkBase, table=True): - """ - many-to-many association table between MonsterDetail and MonsterFamily - that represents breeding combinations. - - child_id, pedigree, and parent_2 represent individual monster ids. - pedigree_family and family_2 represent family type. - - In order to make new monster, two parents are required. - - 4 different combinations possible: - pedigree + parent_2 -- specific monster + specific monster - pedigree + family_2 -- specific monster + any monster from the family type - pedigree_family + parent_2 -- specific family type + specific monster - pedigree_family + family_2 -- family + different family type - """ - - id: Optional[int] = Field(default=None, primary_key=True) - child: "MonsterDetail" = Relationship( - sa_relationship_kwargs={ - "primaryjoin": "MonsterBreedingLink.child_id==MonsterDetail.id", - "lazy": "joined", - } - ) - pedigree: "MonsterDetail" = Relationship( - sa_relationship_kwargs={ - "primaryjoin": "MonsterBreedingLink.pedigree_id==MonsterDetail.id", - "lazy": "joined", - } - ) - parent2: "MonsterDetail" = Relationship( - sa_relationship_kwargs={ - "primaryjoin": "MonsterBreedingLink.parent2_id==MonsterDetail.id", - "lazy": "joined", - } - ) - pedigree_family: "MonsterFamily" = Relationship( - sa_relationship_kwargs={ - "primaryjoin": "MonsterBreedingLink.pedigree_family_id" - "==MonsterFamily.id", - "lazy": "joined", - } - ) - family2: "MonsterFamily" = Relationship( - sa_relationship_kwargs={ - "primaryjoin": "MonsterBreedingLink.family2_id==MonsterFamily.id", - "lazy": "joined", - } - ) - - -class MonsterBreedingLinkRead(MonsterBreedingLinkBase): - id: int - - -class MonsterBreedingLinkReadWithInfo(MonsterBreedingLinkRead): - child: Optional[MonsterDetailRead] - pedigree: Optional[MonsterDetailRead] - parent2: Optional[MonsterDetailRead] - pedigree_family: Optional[MonsterFamilyRead] - family2: Optional[MonsterFamilyRead] - - -class SkillBase(SQLModel): - """ - Shows description, MP cost, and required stats to learn skill. - Each monster naturally learns 3 skills. - """ - - category_type: str - family_type: str - new_name: Optional[str] = Field(default=None) - old_name: str - description: str - mp_cost: int - required_level: int - required_hp: Optional[int] = None - required_mp: Optional[int] = None - required_attack: Optional[int] = None - required_defense: Optional[int] = None - required_speed: Optional[int] = None - required_intelligence: Optional[int] = None - - -class Skill(SkillBase, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - - upgrade_to_id: Optional[int] = Field( - foreign_key="skill.id", # lowercase refers to database table name - default=None, - ) - upgrade_to: Optional["Skill"] = Relationship( - sa_relationship_kwargs={ - "primaryjoin": "Skill.upgrade_to_id==Skill.id", - "lazy": "joined", - "remote_side": "Skill.id", # uppercase refers to this Skill class - } - ) - - upgrade_from_id: Optional[int] = Field( - foreign_key="skill.id", - default=None, - ) - upgrade_from: Optional["Skill"] = Relationship( - sa_relationship_kwargs={ - "primaryjoin": "Skill.upgrade_from_id==Skill.id", - "lazy": "joined", - "remote_side": "Skill.id", - } - ) - - monsters: List[MonsterDetail] = Relationship( - back_populates="skills", link_model=MonsterSkillLink - ) - - -class SkillRead(SkillBase): - id: int - - -class SkillReadWithMonster(SkillRead): - monsters: Optional[MonsterDetailRead] - - -class SkillUpgradeRead(SkillRead): - upgrade_to: Optional[Skill] - upgrade_from: Optional[Skill] - - -class MonsterDetailSkill(MonsterDetailWithFamily): - skills: List[SkillRead] = [] - - -class SkillCombineBase(SQLModel): - combo_skill_id: Optional[int] = Field(default=None, foreign_key="skill.id") - needed_skill_id: Optional[int] = Field(default=None, foreign_key="skill.id") - - -class SkillCombine(SkillCombineBase, table=True): - """ - many-to-many association table showing certain needed skills combine to - learn new combo skill. - """ - - id: Optional[int] = Field(default=None, primary_key=True) - - combo_skill: Skill = Relationship( - sa_relationship_kwargs={ - "primaryjoin": "SkillCombine.combo_skill_id==Skill.id", - "lazy": "joined", - } - ) - - needed_skill: Skill = Relationship( - sa_relationship_kwargs={ - "primaryjoin": "SkillCombine.needed_skill_id==Skill.id", - "lazy": "joined", - } - ) - - -class SkillCombineRead(SkillCombineBase): - id: int - needed_skill: Optional[SkillRead] - - -class Item(SQLModel, table=True): - """ - Lists all items sold in shops and found in the field - """ - - id: Optional[int] = Field(default=None, primary_key=True) - item_name: str - item_category: str - item_description: str - price: Optional[int] = Field(default=None) - sell_price: Optional[int] = Field(default=None) - sell_location: str +from typing import List, Optional, TYPE_CHECKING + +from sqlmodel import Field, Relationship, SQLModel + +if TYPE_CHECKING: + from .skill import Skill + + +class MonsterSkillLink(SQLModel, table=True): + """ + many-to-many association table linking a monster to three different skills. + """ + + id: Optional[int] = Field(default=None, primary_key=True) + monster_id: Optional[int] = Field( + default=None, + foreign_key="monsterdetail.id", + ) + skill_id: Optional[int] = Field( + default=None, + foreign_key="skill.id", + ) + + +class MonsterDetailBase(SQLModel): + """ + Monster details from in-game bestiary. Shows name, family, and description. + """ + + new_name: str + old_name: str + description: str + + # one-to-many relation where a family is linked to many monsters + family_id: int = Field(foreign_key="monsterfamily.id") + + +class MonsterDetail(MonsterDetailBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + + family: List["MonsterFamily"] = Relationship(back_populates="monsters") + skills: List["Skill"] = Relationship( + back_populates="monsters", link_model=MonsterSkillLink + ) + + +class MonsterDetailRead(MonsterDetailBase): + id: int + + +class MonsterFamilyBase(SQLModel): + """ + There are 10 monster families in the game. + """ + + family_eng: str + + +class MonsterFamily(MonsterFamilyBase, table=True): + """ + one-to-many relation between family and monsters. + """ + + id: Optional[int] = Field(default=None, primary_key=True) + monsters: List[MonsterDetail] = Relationship(back_populates="family") + + +class MonsterFamilyRead(MonsterFamilyBase): + id: int + + +class MonsterDetailWithFamily(MonsterDetailRead): + family: Optional[MonsterFamilyRead] + + +class MonsterFamilyReadWithMonsterDetail(MonsterFamilyRead): + monsters: List[MonsterDetailRead] = [] + + +class MonsterBreedingLinkBase(SQLModel): + child_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id") + pedigree_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id") + parent2_id: Optional[int] = Field(default=None, foreign_key="monsterdetail.id") + pedigree_family_id: Optional[int] = Field( + default=None, foreign_key="monsterfamily.id" + ) + family2_id: Optional[int] = Field(default=None, foreign_key="monsterfamily.id") + + +class MonsterBreedingLink(MonsterBreedingLinkBase, table=True): + """ + many-to-many association table between MonsterDetail and MonsterFamily + that represents breeding combinations. + + child_id, pedigree, and parent_2 represent individual monster ids. + pedigree_family and family_2 represent family type. + + In order to make new monster, two parents are required. + + 4 different combinations possible: + pedigree + parent_2 -- specific monster + specific monster + pedigree + family_2 -- specific monster + any monster from the family type + pedigree_family + parent_2 -- specific family type + specific monster + pedigree_family + family_2 -- family + different family type + """ + + id: Optional[int] = Field(default=None, primary_key=True) + child: "MonsterDetail" = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "MonsterBreedingLink.child_id==MonsterDetail.id", + "lazy": "joined", + } + ) + pedigree: "MonsterDetail" = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "MonsterBreedingLink.pedigree_id==MonsterDetail.id", + "lazy": "joined", + } + ) + parent2: "MonsterDetail" = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "MonsterBreedingLink.parent2_id==MonsterDetail.id", + "lazy": "joined", + } + ) + pedigree_family: "MonsterFamily" = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "MonsterBreedingLink.pedigree_family_id" + "==MonsterFamily.id", + "lazy": "joined", + } + ) + family2: "MonsterFamily" = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "MonsterBreedingLink.family2_id==MonsterFamily.id", + "lazy": "joined", + } + ) + + +class MonsterBreedingLinkRead(MonsterBreedingLinkBase): + id: int + + +class MonsterBreedingLinkReadWithInfo(MonsterBreedingLinkRead): + child: Optional[MonsterDetailRead] + pedigree: Optional[MonsterDetailRead] + parent2: Optional[MonsterDetailRead] + pedigree_family: Optional[MonsterFamilyRead] + family2: Optional[MonsterFamilyRead] diff --git a/src/app/models/dqm1/skill.py b/src/app/models/dqm1/skill.py new file mode 100644 index 0000000..0d92333 --- /dev/null +++ b/src/app/models/dqm1/skill.py @@ -0,0 +1,120 @@ +from typing import List, Optional, TYPE_CHECKING + +from sqlmodel import Field, Relationship, SQLModel +from .monster import ( + MonsterDetail, + MonsterSkillLink, + MonsterDetailRead, + MonsterDetailWithFamily, +) + +if TYPE_CHECKING: + from .monster import ( + MonsterDetail, + MonsterSkillLink, + MonsterDetailRead, + MonsterDetailWithFamily, + ) + + +class SkillBase(SQLModel): + """ + Shows description, MP cost, and required stats to learn skill. + Each monster naturally learns 3 skills. + """ + + category_type: str + family_type: str + new_name: Optional[str] = Field(default=None) + old_name: str + description: str + mp_cost: int + required_level: int + required_hp: Optional[int] = None + required_mp: Optional[int] = None + required_attack: Optional[int] = None + required_defense: Optional[int] = None + required_speed: Optional[int] = None + required_intelligence: Optional[int] = None + + +class Skill(SkillBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + + upgrade_to_id: Optional[int] = Field( + foreign_key="skill.id", # lowercase refers to database table name + default=None, + ) + upgrade_to: Optional["Skill"] = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "Skill.upgrade_to_id==Skill.id", + "lazy": "joined", + "remote_side": "Skill.id", # uppercase refers to this Skill class + } + ) + + upgrade_from_id: Optional[int] = Field( + foreign_key="skill.id", + default=None, + ) + upgrade_from: Optional["Skill"] = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "Skill.upgrade_from_id==Skill.id", + "lazy": "joined", + "remote_side": "Skill.id", + } + ) + + monsters: List["MonsterDetail"] = Relationship( + back_populates="skills", link_model=MonsterSkillLink + ) + + +class SkillRead(SkillBase): + id: int + + +class SkillReadWithMonster(SkillRead): + monsters: Optional[MonsterDetailRead] + + +class SkillUpgradeRead(SkillRead): + upgrade_to: Optional[Skill] + upgrade_from: Optional[Skill] + + +class MonsterDetailSkill(MonsterDetailWithFamily): + skills: List[SkillRead] = [] + + +class SkillCombineBase(SQLModel): + combo_skill_id: Optional[int] = Field(default=None, foreign_key="skill.id") + needed_skill_id: Optional[int] = Field(default=None, foreign_key="skill.id") + + +class SkillCombine(SkillCombineBase, table=True): + """ + many-to-many association table showing certain needed skills combine to + learn new combo skill. + """ + + id: Optional[int] = Field(default=None, primary_key=True) + + combo_skill: Skill = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "SkillCombine.combo_skill_id==Skill.id", + "lazy": "joined", + } + ) + + needed_skill: Skill = Relationship( + sa_relationship_kwargs={ + "primaryjoin": "SkillCombine.needed_skill_id==Skill.id", + "lazy": "joined", + } + ) + + +class SkillCombineRead(SkillCombineBase): + id: int + needed_skill: Optional[SkillRead] From 8e231136b6c94e55efe5c433096b0efe2aa563f7 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 1 Jul 2025 17:01:35 -0700 Subject: [PATCH 11/16] refactor: change model imports --- src/app/routers/dqm1_endpoints.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py index bc6fbd8..4346b6e 100644 --- a/src/app/routers/dqm1_endpoints.py +++ b/src/app/routers/dqm1_endpoints.py @@ -10,15 +10,17 @@ SkillCategory, SkillFamily, ) -from src.app.models import ( - Item, +from src.app.models.dqm1.item import Item +from src.app.models.dqm1.monster import ( MonsterBreedingLink, MonsterBreedingLinkReadWithInfo, MonsterDetail, MonsterDetailWithFamily, - MonsterDetailSkill, MonsterFamily, MonsterFamilyReadWithMonsterDetail, +) +from src.app.models.dqm1.skill import ( + MonsterDetailSkill, Skill, SkillCombine, SkillCombineRead, From d32fc63070ed453ec0cc981e341f1c31be6acedd Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 1 Jul 2025 17:04:41 -0700 Subject: [PATCH 12/16] refactor: change model imports in create_database.py --- src/app/create_database.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/create_database.py b/src/app/create_database.py index 06b44ae..835e3cf 100644 --- a/src/app/create_database.py +++ b/src/app/create_database.py @@ -5,16 +5,19 @@ from sqlalchemy.exc import IntegrityError from src.app.database import create_db_and_tables, engine -from src.app.models import ( - Item, +from src.app.models.dqm1.item import Item +from src.app.models.dqm1.monster import ( MonsterBreedingLink, MonsterDetail, MonsterFamily, MonsterSkillLink, +) +from src.app.models.dqm1.skill import ( Skill, SkillCombine, ) + current_dir = Path(__file__).resolve().parent csv_dir = current_dir.parent / "csv_files" From b8c7d2b2ef5d355b05e46f1e3c56b5171e80d0c4 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 1 Jul 2025 17:06:01 -0700 Subject: [PATCH 13/16] refactor: change model imports in create_database.py --- src/app/create_database.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/app/create_database.py b/src/app/create_database.py index 835e3cf..aa7eb85 100644 --- a/src/app/create_database.py +++ b/src/app/create_database.py @@ -12,10 +12,7 @@ MonsterFamily, MonsterSkillLink, ) -from src.app.models.dqm1.skill import ( - Skill, - SkillCombine, -) +from src.app.models.dqm1.skill import Skill, SkillCombine current_dir = Path(__file__).resolve().parent From 8f1054672c28757a45aac5ca8ac2ae8337042984 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 1 Jul 2025 17:36:40 -0700 Subject: [PATCH 14/16] refactor: change model import statements for conftest.py --- tests/conftest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1c158be..8b0b659 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,16 +6,16 @@ from sqlmodel import Session, SQLModel, create_engine from sqlmodel.pool import StaticPool -from src.app.main import app, get_session -from src.app.models import ( - Item, +from src.app.database import get_session +from src.app.main import app +from src.app.models.dqm1.item import Item +from src.app.models.dqm1.monster import ( MonsterBreedingLink, MonsterDetail, MonsterFamily, MonsterSkillLink, - Skill, - SkillCombine, ) +from src.app.models.dqm1.skill import Skill, SkillCombine @pytest.fixture(name="session") From 9836f2ad3e58f51ba374a8b07b4771282583122e Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 1 Jul 2025 17:37:16 -0700 Subject: [PATCH 15/16] refactor: change model import statements for test_insert_data.py --- tests/test_insert_data.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_insert_data.py b/tests/test_insert_data.py index 5169a8c..db1ebb0 100644 --- a/tests/test_insert_data.py +++ b/tests/test_insert_data.py @@ -1,15 +1,14 @@ from fastapi.testclient import TestClient from sqlmodel import Session -from src.app.models import ( - Item, +from src.app.models.dqm1.item import Item +from src.app.models.dqm1.monster import ( MonsterBreedingLink, MonsterDetail, MonsterFamily, MonsterSkillLink, - Skill, - SkillCombine, ) +from src.app.models.dqm1.skill import Skill, SkillCombine def test_read_root(client: TestClient): From e014502d9c5e6c320ede7e55dc35e607c811a613 Mon Sep 17 00:00:00 2001 From: Chris Sato Date: Tue, 1 Jul 2025 17:55:16 -0700 Subject: [PATCH 16/16] refactor: Moved models_enums.py to src/app/models/dqm1 directory --- src/app/{model_enums.py => models/dqm1/enums.py} | 0 src/app/routers/dqm1_endpoints.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/app/{model_enums.py => models/dqm1/enums.py} (100%) diff --git a/src/app/model_enums.py b/src/app/models/dqm1/enums.py similarity index 100% rename from src/app/model_enums.py rename to src/app/models/dqm1/enums.py diff --git a/src/app/routers/dqm1_endpoints.py b/src/app/routers/dqm1_endpoints.py index 4346b6e..70fa1df 100644 --- a/src/app/routers/dqm1_endpoints.py +++ b/src/app/routers/dqm1_endpoints.py @@ -4,7 +4,7 @@ from sqlmodel import Session, select from src.app.database import get_session -from src.app.model_enums import ( +from src.app.models.dqm1.enums import ( ItemCategory, ItemSellLocation, SkillCategory,