diff --git a/README.md b/README.md
index 8b4fd14..cebee09 100644
--- a/README.md
+++ b/README.md
@@ -33,8 +33,9 @@ This repo is deployed on [Google Cloud Run](hhttps://cloud.google.com/run/)
Access the Swagger UI interface of this FastAPI at https://dqmonstersdb-api-743047725852.us-central1.run.app/docs
-
-
+
+
+
## How to Run Locally
1. Clone this repo onto your local machine.
diff --git a/src/app/create_database.py b/src/app/create_database.py
index 06b44ae..3d97cff 100644
--- a/src/app/create_database.py
+++ b/src/app/create_database.py
@@ -1,92 +1,88 @@
-import csv
-from pathlib import Path
-
-from sqlmodel import Session, select
-from sqlalchemy.exc import IntegrityError
-
-from src.app.database import create_db_and_tables, engine
-from src.app.models import (
- Item,
- MonsterBreedingLink,
- MonsterDetail,
- MonsterFamily,
- MonsterSkillLink,
- Skill,
- SkillCombine,
-)
-
-current_dir = Path(__file__).resolve().parent
-csv_dir = current_dir.parent / "csv_files"
-
-
-def _insert_data(csv_file, Model):
- """
- helper function that uses dictionary unpacking to add csv file data
- into database
- """
- with Session(engine) as session:
- with open(csv_file, encoding="utf-8-sig") as f:
- reader = csv.DictReader(f)
- for row in reader:
- # replace empty string with None
- row = {k: (None if v == "" else v) for k, v in row.items()}
- if "id" in row:
- existing_entry = session.exec(
- select(Model).where(Model.id == row["id"])
- ).first()
-
- if existing_entry:
- print(
- f"Entry with id {row['id']} already exists. Skipping insertion."
- )
- continue
- try:
- session.add(Model(**row))
- except IntegrityError as e:
- session.rollback()
- print(f"IntegrityError has occurred: {e.orig}")
-
- session.commit()
-
-
-def create_item_csv():
- _insert_data(csv_dir / "DQM1_items.csv", Item)
-
-
-def create_monster_family_csv():
- _insert_data(csv_dir / "DQM1_monster_family.csv", MonsterFamily)
-
-
-def create_skill_csv():
- _insert_data(csv_dir / "DQM1_skills.csv", Skill)
-
-
-def create_skillcombo_csv():
- _insert_data(csv_dir / "DQM1_skill_combo.csv", SkillCombine)
-
-
-def create_monster_detail_csv():
- _insert_data(csv_dir / "DQM1_monsterdetails.csv", MonsterDetail)
-
-
-def create_breed_combo():
- _insert_data(csv_dir / "DQM1_breeding_combo.csv", MonsterBreedingLink)
-
-
-def create_monster_skill_link():
- _insert_data(csv_dir / "DQM1_monster_skill_link.csv", MonsterSkillLink)
-
-
-def load_all_csv_data():
- create_db_and_tables()
- create_item_csv()
- create_monster_family_csv()
- create_skill_csv()
- create_skillcombo_csv()
- create_monster_detail_csv()
- create_breed_combo()
- create_monster_skill_link()
-
-
-if __name__ == "__main__":
- load_all_csv_data()
+import csv
+from pathlib import Path
+
+from sqlalchemy.exc import IntegrityError
+from sqlmodel import Session, select
+
+from src.app.database import create_db_and_tables, engine
+from src.app.models.dqm1.associations import MonsterSkillLink
+from src.app.models.dqm1.item import Item
+from src.app.models.dqm1.monster import MonsterBreedingLink, MonsterDetail
+from src.app.models.dqm1.monster_family import MonsterFamily
+from src.app.models.dqm1.skill import Skill, SkillCombine
+
+current_dir = Path(__file__).resolve().parent
+csv_dir = current_dir.parent / "csv_files"
+
+
+def _insert_data(csv_file, Model):
+ """
+ helper function that uses dictionary unpacking to add csv file data
+ into database
+ """
+ with Session(engine) as session:
+ with open(csv_file, encoding="utf-8-sig") as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ # replace empty string with None
+ row = {k: (None if v == "" else v) for k, v in row.items()}
+ if "id" in row:
+ existing_entry = session.exec(
+ select(Model).where(Model.id == row["id"])
+ ).first()
+
+ if existing_entry:
+ print(
+ f"Entry with id {row['id']} already exists. Skipping insertion."
+ )
+ continue
+ try:
+ session.add(Model(**row))
+ except IntegrityError as e:
+ session.rollback()
+ print(f"IntegrityError has occurred: {e.orig}")
+
+ session.commit()
+
+
+def create_item_csv():
+ _insert_data(csv_dir / "DQM1_items.csv", Item)
+
+
+def create_monster_family_csv():
+ _insert_data(csv_dir / "DQM1_monster_family.csv", MonsterFamily)
+
+
+def create_skill_csv():
+ _insert_data(csv_dir / "DQM1_skills.csv", Skill)
+
+
+def create_skillcombo_csv():
+ _insert_data(csv_dir / "DQM1_skill_combo.csv", SkillCombine)
+
+
+def create_monster_detail_csv():
+ _insert_data(csv_dir / "DQM1_monsterdetails.csv", MonsterDetail)
+
+
+def create_breed_combo():
+ _insert_data(csv_dir / "DQM1_breeding_combo.csv", MonsterBreedingLink)
+
+
+def create_monster_skill_link():
+ _insert_data(csv_dir / "DQM1_monster_skill_link.csv", MonsterSkillLink)
+
+
+def load_all_csv_data():
+ create_db_and_tables()
+ create_item_csv()
+ create_monster_family_csv()
+ create_skill_csv()
+ create_skillcombo_csv()
+ create_monster_detail_csv()
+ create_breed_combo()
+ create_monster_skill_link()
+
+
+if __name__ == "__main__":
+ load_all_csv_data()
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 3d3e4ad..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 = [
{
@@ -55,6 +32,7 @@
"http://localhost",
"http://localhost:8080",
"http://localhost:3000",
+ "https://dqmonsters-db.vercel.app",
]
app.add_middleware(
@@ -65,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("/")
@@ -76,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.py b/src/app/models.py
deleted file mode 100644
index d372bdc..0000000
--- a/src/app/models.py
+++ /dev/null
@@ -1,264 +0,0 @@
-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
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/associations.py b/src/app/models/dqm1/associations.py
new file mode 100644
index 0000000..38abef2
--- /dev/null
+++ b/src/app/models/dqm1/associations.py
@@ -0,0 +1,19 @@
+from typing import Optional
+
+from sqlmodel import Field, 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",
+ )
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/dqm1/monster.py b/src/app/models/dqm1/monster.py
new file mode 100644
index 0000000..b3fe453
--- /dev/null
+++ b/src/app/models/dqm1/monster.py
@@ -0,0 +1,92 @@
+from typing import List, Optional, TYPE_CHECKING
+
+from sqlmodel import Field, Relationship, SQLModel
+
+from .associations import MonsterSkillLink
+
+if TYPE_CHECKING:
+ from .skill import Skill
+ from .monster_family import MonsterFamily
+
+
+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: Optional["MonsterFamily"] = Relationship(back_populates="monsters")
+ skills: List["Skill"] = Relationship(
+ back_populates="monsters", link_model=MonsterSkillLink
+ )
+
+
+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",
+ }
+ )
diff --git a/src/app/models/dqm1/monster_family.py b/src/app/models/dqm1/monster_family.py
new file mode 100644
index 0000000..58e42a8
--- /dev/null
+++ b/src/app/models/dqm1/monster_family.py
@@ -0,0 +1,23 @@
+from typing import List, Optional, TYPE_CHECKING
+
+from sqlmodel import Field, Relationship, SQLModel
+
+if TYPE_CHECKING:
+ from .monster import MonsterDetail
+
+
+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")
diff --git a/src/app/models/dqm1/schemas.py b/src/app/models/dqm1/schemas.py
new file mode 100644
index 0000000..8f880b4
--- /dev/null
+++ b/src/app/models/dqm1/schemas.py
@@ -0,0 +1,55 @@
+from typing import List, Optional
+
+from .monster import MonsterBreedingLinkBase, MonsterDetailBase
+from .monster_family import MonsterFamilyBase
+from .skill import Skill, SkillBase, SkillCombineBase
+
+
+class MonsterFamilyRead(MonsterFamilyBase):
+ id: int
+
+
+class MonsterFamilyReadWithMonsterDetail(MonsterFamilyRead):
+ monsters: List["MonsterDetailRead"] = []
+
+
+class MonsterDetailRead(MonsterDetailBase):
+ id: int
+
+
+class MonsterDetailWithFamily(MonsterDetailRead):
+ family: Optional[MonsterFamilyRead] = None
+
+
+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 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 SkillCombineRead(SkillCombineBase):
+ id: int
+ needed_skill: Optional[SkillRead]
diff --git a/src/app/models/dqm1/skill.py b/src/app/models/dqm1/skill.py
new file mode 100644
index 0000000..9603a36
--- /dev/null
+++ b/src/app/models/dqm1/skill.py
@@ -0,0 +1,89 @@
+from typing import List, Optional, TYPE_CHECKING
+
+from sqlmodel import Field, Relationship, SQLModel
+
+from .associations import MonsterSkillLink
+
+if TYPE_CHECKING:
+ from .monster import MonsterDetail
+
+
+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 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",
+ }
+ )
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..017d042
--- /dev/null
+++ b/src/app/routers/dqm1_endpoints.py
@@ -0,0 +1,168 @@
+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, MonsterDetail
+from src.app.models.dqm1.monster_family import MonsterFamily
+from src.app.models.dqm1.schemas import (
+ MonsterBreedingLinkReadWithInfo,
+ MonsterDetailSkill,
+ MonsterDetailWithFamily,
+ MonsterFamilyReadWithMonsterDetail,
+ SkillCombineRead,
+ SkillUpgradeRead,
+)
+from src.app.models.dqm1.skill import Skill, SkillCombine
+
+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/src/static/images/readme/FastAPI-readme-1.jpg b/src/static/images/readme/FastAPI-readme-1.jpg
deleted file mode 100644
index b299853..0000000
Binary files a/src/static/images/readme/FastAPI-readme-1.jpg and /dev/null differ
diff --git a/src/static/images/readme/FastAPI-readme-2.jpg b/src/static/images/readme/FastAPI-readme-2.jpg
deleted file mode 100644
index c2b340d..0000000
Binary files a/src/static/images/readme/FastAPI-readme-2.jpg and /dev/null differ
diff --git a/src/static/images/readme/FastAPI-screenshot-1.JPG b/src/static/images/readme/FastAPI-screenshot-1.JPG
new file mode 100644
index 0000000..44ef8e5
Binary files /dev/null and b/src/static/images/readme/FastAPI-screenshot-1.JPG differ
diff --git a/src/static/images/readme/FastAPI-screenshot-2.JPG b/src/static/images/readme/FastAPI-screenshot-2.JPG
new file mode 100644
index 0000000..79e9007
Binary files /dev/null and b/src/static/images/readme/FastAPI-screenshot-2.JPG differ
diff --git a/tests/conftest.py b/tests/conftest.py
index 1c158be..68e365d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,97 +1,97 @@
-import csv
-from pathlib import Path
-
-import pytest
-from fastapi.testclient import TestClient
-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,
- MonsterBreedingLink,
- MonsterDetail,
- MonsterFamily,
- MonsterSkillLink,
- Skill,
- SkillCombine,
-)
-
-
-@pytest.fixture(name="session")
-def session_fixture():
- test_engine = create_engine(
- "sqlite://",
- connect_args={"check_same_thread": False},
- poolclass=StaticPool,
- )
- SQLModel.metadata.create_all(test_engine)
- with Session(test_engine) as session:
- yield session
-
- SQLModel.metadata.drop_all(test_engine)
-
-
-@pytest.fixture(name="client")
-def client_fixture(session: Session):
- def get_session_override():
- yield session
-
- app.dependency_overrides[get_session] = get_session_override
-
- client = TestClient(app)
- yield client
- app.dependency_overrides.clear()
-
-
-@pytest.fixture(name="session_module", scope="module")
-def session_module():
- test_engine = create_engine(
- "sqlite://",
- connect_args={"check_same_thread": False},
- poolclass=StaticPool,
- )
- SQLModel.metadata.create_all(test_engine)
- with Session(test_engine) as session:
- yield session
-
- SQLModel.metadata.drop_all(test_engine)
-
-
-@pytest.fixture(name="client_module", scope="module")
-def client_module(session_module: Session):
- def get_session_override():
- yield session_module
-
- app.dependency_overrides[get_session] = get_session_override
-
- client = TestClient(app)
- yield client
- app.dependency_overrides.clear()
-
-
-@pytest.fixture(name="load_all_csvdata", scope="module")
-def load_csv_data(session_module: Session):
- CSV_FILES_PATH = Path(__file__).resolve().parent.parent / "src" / "csv_files"
-
- csv_files = {
- (CSV_FILES_PATH / "DQM1_items.csv", Item),
- (CSV_FILES_PATH / "DQM1_monster_family.csv", MonsterFamily),
- (CSV_FILES_PATH / "DQM1_skills.csv", Skill),
- (CSV_FILES_PATH / "DQM1_skill_combo.csv", SkillCombine),
- (CSV_FILES_PATH / "DQM1_monsterdetails.csv", MonsterDetail),
- (CSV_FILES_PATH / "DQM1_breeding_combo.csv", MonsterBreedingLink),
- (CSV_FILES_PATH / "DQM1_monster_skill_link.csv", MonsterSkillLink),
- }
-
- for csvfile, Model in csv_files:
- try:
- with open(csvfile, encoding="utf-8-sig") as file:
- reader = csv.DictReader(file)
- for row in reader:
- # replace empty string with None
- row = {k: (None if v == "" else v) for k, v in row.items()}
- session_module.add(Model(**row))
- session_module.commit()
- except Exception as e:
- print(f"Error loading {csvfile} : {e}")
+import csv
+from pathlib import Path
+
+import pytest
+from fastapi.testclient import TestClient
+from sqlmodel import Session, SQLModel, create_engine
+from sqlmodel.pool import StaticPool
+
+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.associations import MonsterSkillLink
+from src.app.models.dqm1.monster import (
+ MonsterBreedingLink,
+ MonsterDetail,
+)
+from src.app.models.dqm1.monster_family import MonsterFamily
+from src.app.models.dqm1.skill import Skill, SkillCombine
+
+
+@pytest.fixture(name="session")
+def session_fixture():
+ test_engine = create_engine(
+ "sqlite://",
+ connect_args={"check_same_thread": False},
+ poolclass=StaticPool,
+ )
+ SQLModel.metadata.create_all(test_engine)
+ with Session(test_engine) as session:
+ yield session
+
+ SQLModel.metadata.drop_all(test_engine)
+
+
+@pytest.fixture(name="client")
+def client_fixture(session: Session):
+ def get_session_override():
+ yield session
+
+ app.dependency_overrides[get_session] = get_session_override
+
+ client = TestClient(app)
+ yield client
+ app.dependency_overrides.clear()
+
+
+@pytest.fixture(name="session_module", scope="module")
+def session_module():
+ test_engine = create_engine(
+ "sqlite://",
+ connect_args={"check_same_thread": False},
+ poolclass=StaticPool,
+ )
+ SQLModel.metadata.create_all(test_engine)
+ with Session(test_engine) as session:
+ yield session
+
+ SQLModel.metadata.drop_all(test_engine)
+
+
+@pytest.fixture(name="client_module", scope="module")
+def client_module(session_module: Session):
+ def get_session_override():
+ yield session_module
+
+ app.dependency_overrides[get_session] = get_session_override
+
+ client = TestClient(app)
+ yield client
+ app.dependency_overrides.clear()
+
+
+@pytest.fixture(name="load_all_csvdata", scope="module")
+def load_csv_data(session_module: Session):
+ CSV_FILES_PATH = Path(__file__).resolve().parent.parent / "src" / "csv_files"
+
+ csv_files = {
+ (CSV_FILES_PATH / "DQM1_items.csv", Item),
+ (CSV_FILES_PATH / "DQM1_monster_family.csv", MonsterFamily),
+ (CSV_FILES_PATH / "DQM1_skills.csv", Skill),
+ (CSV_FILES_PATH / "DQM1_skill_combo.csv", SkillCombine),
+ (CSV_FILES_PATH / "DQM1_monsterdetails.csv", MonsterDetail),
+ (CSV_FILES_PATH / "DQM1_breeding_combo.csv", MonsterBreedingLink),
+ (CSV_FILES_PATH / "DQM1_monster_skill_link.csv", MonsterSkillLink),
+ }
+
+ for csvfile, Model in csv_files:
+ try:
+ with open(csvfile, encoding="utf-8-sig") as file:
+ reader = csv.DictReader(file)
+ for row in reader:
+ # replace empty string with None
+ row = {k: (None if v == "" else v) for k, v in row.items()}
+ session_module.add(Model(**row))
+ session_module.commit()
+ except Exception as e:
+ print(f"Error loading {csvfile} : {e}")
diff --git a/tests/test_insert_data.py b/tests/test_insert_data.py
index 5169a8c..35c4f2d 100644
--- a/tests/test_insert_data.py
+++ b/tests/test_insert_data.py
@@ -1,585 +1,584 @@
-from fastapi.testclient import TestClient
-from sqlmodel import Session
-
-from src.app.models import (
- Item,
- MonsterBreedingLink,
- MonsterDetail,
- MonsterFamily,
- MonsterSkillLink,
- Skill,
- SkillCombine,
-)
-
-
-def test_read_root(client: TestClient):
- response = client.get("/")
- assert response.status_code == 200
- assert response.json() == {
- "message": ("Welcome to the DQMonsters API. Go to the Swagger UI interface")
- }
-
-
-def test_insert_monster(client: TestClient, session: Session):
- """
- - Tests individual insertion of monster data entry into monsterdetail
- datatable
- """
- session.add(
- MonsterDetail(
- new_name="Slime",
- old_name="Slime",
- description="The most abundant of this popular specie",
- family_id=1,
- )
- )
- session.commit()
-
- response = client.get("/dqm1/monsters/1")
- data_entry = response.json()
-
- monster_comparison = {
- "id": 1,
- "new_name": "Slime",
- "old_name": "Slime",
- "description": "The most abundant of this popular specie",
- "family_id": 1,
- "family": None,
- }
-
- assert response.status_code == 200
- assert data_entry["new_name"] == monster_comparison["new_name"]
- assert data_entry["old_name"] == monster_comparison["old_name"]
- assert data_entry["description"] == monster_comparison["description"]
- assert data_entry["family_id"] == monster_comparison["family_id"]
- assert data_entry == monster_comparison
-
-
-def test_insert_monster_family(client: TestClient, session: Session):
- """
- Test individual insertion of monster family data into monsterfamily table
- """
- family_list = [
- "SLIME",
- "DRAGON",
- "BEAST",
- "BIRD",
- "PLANT",
- "BUG",
- "DEVIL",
- "UNDEAD",
- "MATERIAL",
- "???",
- ]
-
- for family in family_list:
- session.add(MonsterFamily(family_eng=f"{family}"))
- session.commit()
-
- for i in range(1, 11):
- response = client.get(f"dqm1/family/{i}")
- family_entry = response.json()
-
- assert response.status_code == 200
- assert family_entry["family_eng"] == family_list[i - 1]
-
-
-def test_insert_skill(client: TestClient, session: Session):
- """
- Tests individual insertion of skill data into skill datatable
- Tests association between skills via upgrade_to and upgrade_from
- - 'Blaze' upgrades to 'Blazemore', which upgrades to 'Blazemost'
- """
- session.add(
- Skill(
- category_type="Attack",
- family_type="Frizz",
- new_name="Frizz",
- old_name="Blaze",
- description="Inflict damage with small fireball ",
- mp_cost=2,
- required_level=2,
- required_mp=7,
- required_intelligence=20,
- upgrade_to_id=2,
- )
- )
- session.add(
- Skill(
- category_type="Attack",
- family_type="Frizz",
- new_name="Frizzle",
- old_name="Blazemore",
- description="Inflict damage with giant fireball",
- mp_cost=4,
- required_level=13,
- required_mp=46,
- required_intelligence=64,
- upgrade_to_id=3,
- upgrade_from_id=1,
- )
- )
- session.add(
- Skill(
- category_type="Attack",
- family_type="Frizz",
- new_name="Kafrizzle",
- old_name="Blazemost",
- description="Inflict damage with pillars of fire",
- mp_cost=10,
- required_level=28,
- required_mp=112,
- required_intelligence=146,
- upgrade_from_id=2,
- )
- )
- session.commit()
-
- response = client.get("dqm1/skills/1")
- skill_entry = response.json()
-
- skill_comparison = {
- "category_type": "Attack",
- "family_type": "Frizz",
- "new_name": "Frizz",
- "old_name": "Blaze",
- "description": "Inflict damage with small fireball ",
- "mp_cost": 2,
- "required_level": 2,
- "required_hp": None,
- "required_mp": 7,
- "required_attack": None,
- "required_defense": None,
- "required_speed": None,
- "required_intelligence": 20,
- "id": 1,
- "upgrade_to": {
- "new_name": "Frizzle",
- "required_hp": None,
- "required_mp": 46,
- "required_attack": None,
- "required_defense": None,
- "required_speed": None,
- "required_intelligence": 64,
- "id": 2,
- "upgrade_to_id": 3,
- "upgrade_from_id": 1,
- "category_type": "Attack",
- "family_type": "Frizz",
- "old_name": "Blazemore",
- "description": "Inflict damage with giant fireball",
- "mp_cost": 4,
- "required_level": 13,
- },
- "upgrade_from": None,
- }
-
- assert response.status_code == 200
- assert skill_entry == skill_comparison
-
- skill_entry_2 = client.get("dqm1/skills/2").json()
- assert response.status_code == 200
- assert skill_entry_2["upgrade_from"]["old_name"] == "Blaze"
- assert skill_entry_2["upgrade_to"]["old_name"] == "Blazemost"
-
-
-def test_insert_item(client: TestClient, session: Session):
- """
- Tests individual insertion of item data into items datatable
- """
- session.add(
- Item(
- item_name="Herb",
- item_category="recovery",
- item_description="Restores around 30 HP",
- price=10,
- sell_price=6,
- sell_location="Bazaar shop 1",
- )
- )
- session.commit()
-
- response = client.get("/dqm1/items/1")
- item_entry = response.json()
-
- item_comparison = {
- "item_name": "Herb",
- "item_category": "recovery",
- "item_description": "Restores around 30 HP",
- "price": 10,
- "sell_price": 6,
- "sell_location": "Bazaar shop 1",
- }
-
- assert response.status_code == 200
- for key, value in item_comparison.items():
- assert item_entry[key] == value
-
-
-def test_insert_item_with_none(client: TestClient, session: Session):
- """
- Tests individual insertion of item data into items datatable that has a
- price and sell_price of None
- """
- session.add(
- Item(
- item_name="Tiny medal",
- item_category="dungeon use",
- item_description="Collect and give to medal master for a prize",
- price=None,
- sell_price=None,
- sell_location="found in field",
- )
- )
- session.commit()
-
- response = client.get("/dqm1/items/1")
- item_entry = response.json()
-
- item_comparison = {
- "item_name": "Tiny medal",
- "item_category": "dungeon use",
- "item_description": "Collect and give to medal master for a prize",
- "price": None,
- "sell_price": None,
- "sell_location": "found in field",
- }
-
- assert response.status_code == 200
- for key, value in item_comparison.items():
- assert item_entry[key] == value
-
-
-def test_monster_skill_link(client: TestClient, session: Session):
- """
- Tests monster datatable association with skill datatable
- """
- session.add(
- MonsterDetail(
- new_name="Slime",
- old_name="Slime",
- description="The most abundant of this popular specie",
- family_id=1,
- )
- )
- session.add(
- Skill(
- category_type="Attack",
- family_type="Sizz",
- new_name="Sizz",
- old_name="Firebal",
- description="Inflict damage to all enemies with a small blaze",
- mp_cost=4,
- required_level=3,
- required_mp=11,
- required_intelligence=23,
- )
- )
- session.add(
- Skill(
- category_type="Attack",
- family_type="Magic Burst",
- new_name="Magic Burst",
- old_name="MegaMagic",
- description="The most powerful spell to affect all enemies",
- mp_cost=999,
- required_level=38,
- required_mp=210,
- required_attack=114,
- required_speed=224,
- )
- )
- session.add(
- Skill(
- category_type="Support",
- family_type="Dazzle",
- new_name="Dazzleflash",
- old_name="Radiant",
- description="Blinds all enemies with its bright light",
- mp_cost=2,
- required_level=12,
- required_mp=42,
- required_speed=72,
- required_intelligence=72,
- )
- )
-
- session.add(
- MonsterSkillLink(
- monster_id=1,
- skill_id=1,
- )
- )
- session.add(
- MonsterSkillLink(
- monster_id=1,
- skill_id=2,
- )
- )
- session.add(
- MonsterSkillLink(
- monster_id=1,
- skill_id=3,
- )
- )
- session.commit()
-
- response = client.get("dqm1/monstersandskill/1")
- monster_entry = response.json()
-
- assert response.status_code == 200
- assert len(monster_entry["skills"]) == 3
- assert monster_entry["skills"][0]["old_name"] == "Firebal"
- assert monster_entry["skills"][1]["old_name"] == "MegaMagic"
- assert monster_entry["skills"][2]["old_name"] == "Radiant"
-
-
-def test_skill_combine(client: TestClient, session: Session):
- """
- Tests many-to-many connection between skills via SkillCombine Model
- """
- # Add skill to skills datatable.
- session.add(
- Skill(
- # id = 1
- category_type="Attack",
- family_type="Frizz",
- new_name="Frizz",
- old_name="Blaze",
- description="Inflict damage with small fireball ",
- mp_cost=2,
- required_level=2,
- required_mp=7,
- required_intelligence=20,
- upgrade_to_id=2,
- )
- )
- session.add(
- Skill(
- # id = 2
- category_type="Attack",
- family_type="Frizz",
- new_name="Frizzle",
- old_name="Blazemore",
- description="Inflict damage with giant fireball",
- mp_cost=4,
- required_level=13,
- required_mp=46,
- required_intelligence=64,
- upgrade_to_id=3,
- upgrade_from_id=1,
- )
- )
- session.add(
- Skill(
- # id = 3
- category_type="Attack",
- family_type="Frizz",
- new_name="Kafrizzle",
- old_name="Blazemost",
- description="Inflict damage with pillars of fire",
- mp_cost=10,
- required_level=28,
- required_mp=112,
- required_intelligence=146,
- upgrade_from_id=2,
- )
- )
- session.add(
- Skill(
- # id = 4
- category_type="Attack",
- family_type="Frizz",
- new_name="Flame Slash",
- old_name="FireSlash",
- description="Burning blade sword attack",
- mp_cost=3,
- required_level=11,
- required_hp=77,
- required_mp=34,
- required_attack=66,
- required_intelligence=42,
- )
- )
- session.add(
- Skill(
- # id = 5
- category_type="Support",
- family_type="Status support",
- new_name="Muster Strength",
- old_name="ChargeUP",
- description="Additional Damage next turn",
- mp_cost=0,
- required_level=14,
- required_hp=98,
- required_defense=84,
- )
- )
-
- # Add SkillCombine connection
- # 'FireSlash' can be learned if 'Blazemore' and 'ChargeUP' is known
- session.add(
- SkillCombine(
- combo_skill_id=4,
- needed_skill_id=2,
- )
- )
- session.add(
- SkillCombine(
- combo_skill_id=4,
- needed_skill_id=5,
- )
- )
- session.commit()
-
- response = client.get("dqm1/skillcombine/4")
- skill_combo = response.json()
-
- assert response.status_code == 200
-
- assert skill_combo[0]["needed_skill_id"] == 2
- assert skill_combo[0]["needed_skill"]["old_name"] == "Blazemore"
-
- assert skill_combo[1]["needed_skill_id"] == 5
- assert skill_combo[1]["needed_skill"]["old_name"] == "ChargeUP"
-
-
-def test_monster_breeding_link(client: TestClient, session: Session):
- """
- Test breeding combo insertion.
- """
- family_list = [
- "SLIME", # family id = 1
- "DRAGON", # family id = 2
- "BEAST",
- "BIRD",
- "PLANT",
- "BUG",
- "DEVIL",
- "UNDEAD",
- "MATERIAL",
- "???",
- ]
-
- for family in family_list:
- session.add(MonsterFamily(family_eng=f"{family}"))
-
- session.add(
- MonsterDetail(
- # id = 1
- new_name="Drake Slime",
- old_name="DrakSlime",
- description="Moves & jumps with its tail and wings",
- family_id=1,
- )
- )
- session.add(
- MonsterDetail(
- # id = 2
- new_name="Wild slime",
- old_name="FangSlime",
- description="Has a red Mohawk and is very brave & proud",
- family_id=1,
- )
- )
- session.add(
- MonsterDetail(
- # id = 3
- new_name="Spiked hare",
- old_name="Almiraj",
- description="When cornered, it charges with its sharp horns",
- family_id=3,
- )
- )
-
- # tests pedigree_family + family2 connection
- session.add(
- MonsterBreedingLink(
- child_id=1,
- pedigree_family_id=1,
- family2_id=2,
- )
- )
- # tests pedigree_family + parent2 connection
- session.add(
- MonsterBreedingLink(
- child_id=2,
- parent2_id=3,
- pedigree_family_id=1,
- )
- )
-
- session.commit()
-
- response1 = client.get("dqm1/breeding/1")
- breeding_query1 = response1.json()
-
- entry_comparison1 = [
- {
- "id": 1,
- "child_id": 1,
- "pedigree_id": None,
- "parent2_id": None,
- "pedigree_family_id": 1,
- "family2_id": 2,
- "child": {
- "id": 1,
- "new_name": "Drake Slime",
- "old_name": "DrakSlime",
- "description": "Moves & jumps with its tail and wings",
- "family_id": 1,
- },
- "pedigree": None,
- "parent2": None,
- "pedigree_family": {
- "family_eng": "SLIME",
- "id": 1,
- },
- "family2": {
- "family_eng": "DRAGON",
- "id": 2,
- },
- },
- ]
-
- assert response1.status_code == 200
- assert breeding_query1[0]["child_id"] == 1
- assert breeding_query1[0]["child"]["old_name"] == "DrakSlime"
- assert breeding_query1 == entry_comparison1
-
- response2 = client.get("dqm1/breeding/2")
- breeding_query2 = response2.json()
-
- entry_comparison2 = [
- {
- "id": 2,
- "child_id": 2,
- "pedigree_id": None,
- "parent2_id": 3,
- "pedigree_family_id": 1,
- "family2_id": None,
- "child": {
- "id": 2,
- "new_name": "Wild slime",
- "old_name": "FangSlime",
- "description": "Has a red Mohawk and is very brave & proud",
- "family_id": 1,
- },
- "pedigree": None,
- "parent2": {
- "id": 3,
- "new_name": "Spiked hare",
- "old_name": "Almiraj",
- "description": ("When cornered, it charges with its sharp horns"),
- "family_id": 3,
- },
- "pedigree_family": {
- "family_eng": "SLIME",
- "id": 1,
- },
- "family2": None,
- }
- ]
-
- assert response2.status_code == 200
- assert breeding_query2 == entry_comparison2
+from fastapi.testclient import TestClient
+from sqlmodel import Session
+
+from src.app.models.dqm1.item import Item
+from src.app.models.dqm1.monster import (
+ MonsterBreedingLink,
+ MonsterDetail,
+ MonsterSkillLink,
+)
+from src.app.models.dqm1.monster_family import MonsterFamily
+from src.app.models.dqm1.skill import Skill, SkillCombine
+
+
+def test_read_root(client: TestClient):
+ response = client.get("/")
+ assert response.status_code == 200
+ assert response.json() == {
+ "message": ("Welcome to the DQMonsters API. Go to the Swagger UI interface")
+ }
+
+
+def test_insert_monster(client: TestClient, session: Session):
+ """
+ - Tests individual insertion of monster data entry into monsterdetail
+ datatable
+ """
+ session.add(
+ MonsterDetail(
+ new_name="Slime",
+ old_name="Slime",
+ description="The most abundant of this popular specie",
+ family_id=1,
+ )
+ )
+ session.commit()
+
+ response = client.get("/dqm1/monsters/1")
+ data_entry = response.json()
+
+ monster_comparison = {
+ "id": 1,
+ "new_name": "Slime",
+ "old_name": "Slime",
+ "description": "The most abundant of this popular specie",
+ "family_id": 1,
+ "family": None,
+ }
+
+ assert response.status_code == 200
+ assert data_entry["new_name"] == monster_comparison["new_name"]
+ assert data_entry["old_name"] == monster_comparison["old_name"]
+ assert data_entry["description"] == monster_comparison["description"]
+ assert data_entry["family_id"] == monster_comparison["family_id"]
+ assert data_entry == monster_comparison
+
+
+def test_insert_monster_family(client: TestClient, session: Session):
+ """
+ Test individual insertion of monster family data into monsterfamily table
+ """
+ family_list = [
+ "SLIME",
+ "DRAGON",
+ "BEAST",
+ "BIRD",
+ "PLANT",
+ "BUG",
+ "DEVIL",
+ "UNDEAD",
+ "MATERIAL",
+ "???",
+ ]
+
+ for family in family_list:
+ session.add(MonsterFamily(family_eng=f"{family}"))
+ session.commit()
+
+ for i in range(1, 11):
+ response = client.get(f"dqm1/family/{i}")
+ family_entry = response.json()
+
+ assert response.status_code == 200
+ assert family_entry["family_eng"] == family_list[i - 1]
+
+
+def test_insert_skill(client: TestClient, session: Session):
+ """
+ Tests individual insertion of skill data into skill datatable
+ Tests association between skills via upgrade_to and upgrade_from
+ - 'Blaze' upgrades to 'Blazemore', which upgrades to 'Blazemost'
+ """
+ session.add(
+ Skill(
+ category_type="Attack",
+ family_type="Frizz",
+ new_name="Frizz",
+ old_name="Blaze",
+ description="Inflict damage with small fireball ",
+ mp_cost=2,
+ required_level=2,
+ required_mp=7,
+ required_intelligence=20,
+ upgrade_to_id=2,
+ )
+ )
+ session.add(
+ Skill(
+ category_type="Attack",
+ family_type="Frizz",
+ new_name="Frizzle",
+ old_name="Blazemore",
+ description="Inflict damage with giant fireball",
+ mp_cost=4,
+ required_level=13,
+ required_mp=46,
+ required_intelligence=64,
+ upgrade_to_id=3,
+ upgrade_from_id=1,
+ )
+ )
+ session.add(
+ Skill(
+ category_type="Attack",
+ family_type="Frizz",
+ new_name="Kafrizzle",
+ old_name="Blazemost",
+ description="Inflict damage with pillars of fire",
+ mp_cost=10,
+ required_level=28,
+ required_mp=112,
+ required_intelligence=146,
+ upgrade_from_id=2,
+ )
+ )
+ session.commit()
+
+ response = client.get("dqm1/skills/1")
+ skill_entry = response.json()
+
+ skill_comparison = {
+ "category_type": "Attack",
+ "family_type": "Frizz",
+ "new_name": "Frizz",
+ "old_name": "Blaze",
+ "description": "Inflict damage with small fireball ",
+ "mp_cost": 2,
+ "required_level": 2,
+ "required_hp": None,
+ "required_mp": 7,
+ "required_attack": None,
+ "required_defense": None,
+ "required_speed": None,
+ "required_intelligence": 20,
+ "id": 1,
+ "upgrade_to": {
+ "new_name": "Frizzle",
+ "required_hp": None,
+ "required_mp": 46,
+ "required_attack": None,
+ "required_defense": None,
+ "required_speed": None,
+ "required_intelligence": 64,
+ "id": 2,
+ "upgrade_to_id": 3,
+ "upgrade_from_id": 1,
+ "category_type": "Attack",
+ "family_type": "Frizz",
+ "old_name": "Blazemore",
+ "description": "Inflict damage with giant fireball",
+ "mp_cost": 4,
+ "required_level": 13,
+ },
+ "upgrade_from": None,
+ }
+
+ assert response.status_code == 200
+ assert skill_entry == skill_comparison
+
+ skill_entry_2 = client.get("dqm1/skills/2").json()
+ assert response.status_code == 200
+ assert skill_entry_2["upgrade_from"]["old_name"] == "Blaze"
+ assert skill_entry_2["upgrade_to"]["old_name"] == "Blazemost"
+
+
+def test_insert_item(client: TestClient, session: Session):
+ """
+ Tests individual insertion of item data into items datatable
+ """
+ session.add(
+ Item(
+ item_name="Herb",
+ item_category="recovery",
+ item_description="Restores around 30 HP",
+ price=10,
+ sell_price=6,
+ sell_location="Bazaar shop 1",
+ )
+ )
+ session.commit()
+
+ response = client.get("/dqm1/items/1")
+ item_entry = response.json()
+
+ item_comparison = {
+ "item_name": "Herb",
+ "item_category": "recovery",
+ "item_description": "Restores around 30 HP",
+ "price": 10,
+ "sell_price": 6,
+ "sell_location": "Bazaar shop 1",
+ }
+
+ assert response.status_code == 200
+ for key, value in item_comparison.items():
+ assert item_entry[key] == value
+
+
+def test_insert_item_with_none(client: TestClient, session: Session):
+ """
+ Tests individual insertion of item data into items datatable that has a
+ price and sell_price of None
+ """
+ session.add(
+ Item(
+ item_name="Tiny medal",
+ item_category="dungeon use",
+ item_description="Collect and give to medal master for a prize",
+ price=None,
+ sell_price=None,
+ sell_location="found in field",
+ )
+ )
+ session.commit()
+
+ response = client.get("/dqm1/items/1")
+ item_entry = response.json()
+
+ item_comparison = {
+ "item_name": "Tiny medal",
+ "item_category": "dungeon use",
+ "item_description": "Collect and give to medal master for a prize",
+ "price": None,
+ "sell_price": None,
+ "sell_location": "found in field",
+ }
+
+ assert response.status_code == 200
+ for key, value in item_comparison.items():
+ assert item_entry[key] == value
+
+
+def test_monster_skill_link(client: TestClient, session: Session):
+ """
+ Tests monster datatable association with skill datatable
+ """
+ session.add(
+ MonsterDetail(
+ new_name="Slime",
+ old_name="Slime",
+ description="The most abundant of this popular specie",
+ family_id=1,
+ )
+ )
+ session.add(
+ Skill(
+ category_type="Attack",
+ family_type="Sizz",
+ new_name="Sizz",
+ old_name="Firebal",
+ description="Inflict damage to all enemies with a small blaze",
+ mp_cost=4,
+ required_level=3,
+ required_mp=11,
+ required_intelligence=23,
+ )
+ )
+ session.add(
+ Skill(
+ category_type="Attack",
+ family_type="Magic Burst",
+ new_name="Magic Burst",
+ old_name="MegaMagic",
+ description="The most powerful spell to affect all enemies",
+ mp_cost=999,
+ required_level=38,
+ required_mp=210,
+ required_attack=114,
+ required_speed=224,
+ )
+ )
+ session.add(
+ Skill(
+ category_type="Support",
+ family_type="Dazzle",
+ new_name="Dazzleflash",
+ old_name="Radiant",
+ description="Blinds all enemies with its bright light",
+ mp_cost=2,
+ required_level=12,
+ required_mp=42,
+ required_speed=72,
+ required_intelligence=72,
+ )
+ )
+
+ session.add(
+ MonsterSkillLink(
+ monster_id=1,
+ skill_id=1,
+ )
+ )
+ session.add(
+ MonsterSkillLink(
+ monster_id=1,
+ skill_id=2,
+ )
+ )
+ session.add(
+ MonsterSkillLink(
+ monster_id=1,
+ skill_id=3,
+ )
+ )
+ session.commit()
+
+ response = client.get("dqm1/monstersandskill/1")
+ monster_entry = response.json()
+
+ assert response.status_code == 200
+ assert len(monster_entry["skills"]) == 3
+ assert monster_entry["skills"][0]["old_name"] == "Firebal"
+ assert monster_entry["skills"][1]["old_name"] == "MegaMagic"
+ assert monster_entry["skills"][2]["old_name"] == "Radiant"
+
+
+def test_skill_combine(client: TestClient, session: Session):
+ """
+ Tests many-to-many connection between skills via SkillCombine Model
+ """
+ # Add skill to skills datatable.
+ session.add(
+ Skill(
+ # id = 1
+ category_type="Attack",
+ family_type="Frizz",
+ new_name="Frizz",
+ old_name="Blaze",
+ description="Inflict damage with small fireball ",
+ mp_cost=2,
+ required_level=2,
+ required_mp=7,
+ required_intelligence=20,
+ upgrade_to_id=2,
+ )
+ )
+ session.add(
+ Skill(
+ # id = 2
+ category_type="Attack",
+ family_type="Frizz",
+ new_name="Frizzle",
+ old_name="Blazemore",
+ description="Inflict damage with giant fireball",
+ mp_cost=4,
+ required_level=13,
+ required_mp=46,
+ required_intelligence=64,
+ upgrade_to_id=3,
+ upgrade_from_id=1,
+ )
+ )
+ session.add(
+ Skill(
+ # id = 3
+ category_type="Attack",
+ family_type="Frizz",
+ new_name="Kafrizzle",
+ old_name="Blazemost",
+ description="Inflict damage with pillars of fire",
+ mp_cost=10,
+ required_level=28,
+ required_mp=112,
+ required_intelligence=146,
+ upgrade_from_id=2,
+ )
+ )
+ session.add(
+ Skill(
+ # id = 4
+ category_type="Attack",
+ family_type="Frizz",
+ new_name="Flame Slash",
+ old_name="FireSlash",
+ description="Burning blade sword attack",
+ mp_cost=3,
+ required_level=11,
+ required_hp=77,
+ required_mp=34,
+ required_attack=66,
+ required_intelligence=42,
+ )
+ )
+ session.add(
+ Skill(
+ # id = 5
+ category_type="Support",
+ family_type="Status support",
+ new_name="Muster Strength",
+ old_name="ChargeUP",
+ description="Additional Damage next turn",
+ mp_cost=0,
+ required_level=14,
+ required_hp=98,
+ required_defense=84,
+ )
+ )
+
+ # Add SkillCombine connection
+ # 'FireSlash' can be learned if 'Blazemore' and 'ChargeUP' is known
+ session.add(
+ SkillCombine(
+ combo_skill_id=4,
+ needed_skill_id=2,
+ )
+ )
+ session.add(
+ SkillCombine(
+ combo_skill_id=4,
+ needed_skill_id=5,
+ )
+ )
+ session.commit()
+
+ response = client.get("dqm1/skillcombine/4")
+ skill_combo = response.json()
+
+ assert response.status_code == 200
+
+ assert skill_combo[0]["needed_skill_id"] == 2
+ assert skill_combo[0]["needed_skill"]["old_name"] == "Blazemore"
+
+ assert skill_combo[1]["needed_skill_id"] == 5
+ assert skill_combo[1]["needed_skill"]["old_name"] == "ChargeUP"
+
+
+def test_monster_breeding_link(client: TestClient, session: Session):
+ """
+ Test breeding combo insertion.
+ """
+ family_list = [
+ "SLIME", # family id = 1
+ "DRAGON", # family id = 2
+ "BEAST",
+ "BIRD",
+ "PLANT",
+ "BUG",
+ "DEVIL",
+ "UNDEAD",
+ "MATERIAL",
+ "???",
+ ]
+
+ for family in family_list:
+ session.add(MonsterFamily(family_eng=f"{family}"))
+
+ session.add(
+ MonsterDetail(
+ # id = 1
+ new_name="Drake Slime",
+ old_name="DrakSlime",
+ description="Moves & jumps with its tail and wings",
+ family_id=1,
+ )
+ )
+ session.add(
+ MonsterDetail(
+ # id = 2
+ new_name="Wild slime",
+ old_name="FangSlime",
+ description="Has a red Mohawk and is very brave & proud",
+ family_id=1,
+ )
+ )
+ session.add(
+ MonsterDetail(
+ # id = 3
+ new_name="Spiked hare",
+ old_name="Almiraj",
+ description="When cornered, it charges with its sharp horns",
+ family_id=3,
+ )
+ )
+
+ # tests pedigree_family + family2 connection
+ session.add(
+ MonsterBreedingLink(
+ child_id=1,
+ pedigree_family_id=1,
+ family2_id=2,
+ )
+ )
+ # tests pedigree_family + parent2 connection
+ session.add(
+ MonsterBreedingLink(
+ child_id=2,
+ parent2_id=3,
+ pedigree_family_id=1,
+ )
+ )
+
+ session.commit()
+
+ response1 = client.get("dqm1/breeding/1")
+ breeding_query1 = response1.json()
+
+ entry_comparison1 = [
+ {
+ "id": 1,
+ "child_id": 1,
+ "pedigree_id": None,
+ "parent2_id": None,
+ "pedigree_family_id": 1,
+ "family2_id": 2,
+ "child": {
+ "id": 1,
+ "new_name": "Drake Slime",
+ "old_name": "DrakSlime",
+ "description": "Moves & jumps with its tail and wings",
+ "family_id": 1,
+ },
+ "pedigree": None,
+ "parent2": None,
+ "pedigree_family": {
+ "family_eng": "SLIME",
+ "id": 1,
+ },
+ "family2": {
+ "family_eng": "DRAGON",
+ "id": 2,
+ },
+ },
+ ]
+
+ assert response1.status_code == 200
+ assert breeding_query1[0]["child_id"] == 1
+ assert breeding_query1[0]["child"]["old_name"] == "DrakSlime"
+ assert breeding_query1 == entry_comparison1
+
+ response2 = client.get("dqm1/breeding/2")
+ breeding_query2 = response2.json()
+
+ entry_comparison2 = [
+ {
+ "id": 2,
+ "child_id": 2,
+ "pedigree_id": None,
+ "parent2_id": 3,
+ "pedigree_family_id": 1,
+ "family2_id": None,
+ "child": {
+ "id": 2,
+ "new_name": "Wild slime",
+ "old_name": "FangSlime",
+ "description": "Has a red Mohawk and is very brave & proud",
+ "family_id": 1,
+ },
+ "pedigree": None,
+ "parent2": {
+ "id": 3,
+ "new_name": "Spiked hare",
+ "old_name": "Almiraj",
+ "description": ("When cornered, it charges with its sharp horns"),
+ "family_id": 3,
+ },
+ "pedigree_family": {
+ "family_eng": "SLIME",
+ "id": 1,
+ },
+ "family2": None,
+ }
+ ]
+
+ assert response2.status_code == 200
+ assert breeding_query2 == entry_comparison2