diff --git a/src/app/create_database.py b/src/app/create_database.py index 06b44ae..aa7eb85 100644 --- a/src/app/create_database.py +++ b/src/app/create_database.py @@ -5,15 +5,15 @@ 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, - Skill, - SkillCombine, ) +from src.app.models.dqm1.skill import Skill, SkillCombine + current_dir = Path(__file__).resolve().parent csv_dir = current_dir.parent / "csv_files" 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..0c3eabf 100644 --- a/src/app/main.py +++ b/src/app/main.py @@ -1,31 +1,8 @@ -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 engine -from src.app.model_enums import ( - ItemCategory, - ItemSellLocation, - SkillCategory, - SkillFamily, -) -from src.app.models import ( - Item, - MonsterBreedingLink, - MonsterBreedingLinkReadWithInfo, - MonsterDetail, - MonsterDetailSkill, - MonsterDetailWithFamily, - MonsterFamily, - MonsterFamilyReadWithMonsterDetail, - Skill, - SkillCombine, - SkillCombineRead, - SkillUpgradeRead, -) +from src.app.routers import dqm1_endpoints tags_metadata = [ { @@ -66,10 +43,7 @@ allow_headers=["*"], ) - -async def get_session(): # place in database.py? - with Session(engine) as session: - yield session +app.include_router(dqm1_endpoints.router) @app.get("/") @@ -77,145 +51,3 @@ def root(): return { "message": ("Welcome to the DQMonsters API. " "Go to the Swagger UI interface") } - - -@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( - *, - 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( - *, - 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 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/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/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] 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..70fa1df --- /dev/null +++ b/src/app/routers/dqm1_endpoints.py @@ -0,0 +1,172 @@ +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.dqm1.enums import ( + ItemCategory, + ItemSellLocation, + SkillCategory, + SkillFamily, +) +from src.app.models.dqm1.item import Item +from src.app.models.dqm1.monster import ( + MonsterBreedingLink, + MonsterBreedingLinkReadWithInfo, + MonsterDetail, + MonsterDetailWithFamily, + MonsterFamily, + MonsterFamilyReadWithMonsterDetail, +) +from src.app.models.dqm1.skill import ( + MonsterDetailSkill, + Skill, + SkillCombine, + SkillCombineRead, + SkillUpgradeRead, +) + +router = APIRouter( + prefix="/dqm1", +) + + +@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 + + +@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 + + +@router.get("/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 + + +@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 + + +@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 + + +@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 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") 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):