From 3d5c05ce23e01ccc45d90a3d6df9bbe63d83836d Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 12:59:18 -0500 Subject: [PATCH 01/10] Fix double initialization --- src/dice.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dice.py b/src/dice.py index 273024a..6ab5ceb 100644 --- a/src/dice.py +++ b/src/dice.py @@ -13,7 +13,6 @@ def __init__(self, num_dice: int, num_sides: int, modifier: int) -> None: self.modifier = modifier self.rolls = [] self.total = 0 - self.rolls = [] self.critical_success = False self.critical_fail = False From 7050f54fda431db565190274bbd9c64dacad9f8b Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 13:01:43 -0500 Subject: [PATCH 02/10] Handle errors, close db session --- src/db.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/db.py b/src/db.py index 5aac72d..1b9c6ed 100644 --- a/src/db.py +++ b/src/db.py @@ -11,14 +11,23 @@ class DB: def __init__(self) -> None: """Initialize the database connection.""" self.host = "dnd-db" - self.user = os.environ["MYSQL_USER"] - self.password = os.environ["MYSQL_PASSWORD"] - self.db = mysql.connector.connect( - host=self.host, - port=3306, - user=self.user, - password=self.password, - ) + try: + self.user = os.environ["MYSQL_USER"] + self.password = os.environ["MYSQL_PASSWORD"] + self.db = mysql.connector.connect( + host=self.host, + port=3306, + user=self.user, + password=self.password, + ) + except (KeyError, mysql.connector.Error) as e: + print(f"Database connection error: {e}") + self.db = None + + def __del__(self) -> None: + """Close the database connection when the object is destroyed.""" + if hasattr(self, 'db') and self.db is not None: + self.db.close() def insert_character( self, From 3b8944a6f7165ae93ca816126cd858e876c58d03 Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 13:03:16 -0500 Subject: [PATCH 03/10] Fix type error --- src/page_loader.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/page_loader.py b/src/page_loader.py index 5461131..294bae2 100644 --- a/src/page_loader.py +++ b/src/page_loader.py @@ -148,10 +148,9 @@ def left_right_dance( subtitle: str, ) -> str: """Load the left right page.""" - try: - if "submit" in submit: - pass - except TypeError: + if submit is not None and "submit" in submit: + pass + else: right_content = "" return self.load_left_right_page(left_content, right_content, subtitle) From fe7597e00c1f82fd488e620692cbd8bc4e7968f6 Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 13:06:08 -0500 Subject: [PATCH 04/10] Fix whitespace --- src/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db.py b/src/db.py index 1b9c6ed..61448bc 100644 --- a/src/db.py +++ b/src/db.py @@ -23,7 +23,7 @@ def __init__(self) -> None: except (KeyError, mysql.connector.Error) as e: print(f"Database connection error: {e}") self.db = None - + def __del__(self) -> None: """Close the database connection when the object is destroyed.""" if hasattr(self, 'db') and self.db is not None: From 75240dd9c185b9e94618bc668ac8ebf01536c688 Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 13:06:21 -0500 Subject: [PATCH 05/10] Generate docs --- docs/code.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/code.md b/docs/code.md index f51c270..227e0a3 100644 --- a/docs/code.md +++ b/docs/code.md @@ -23,6 +23,7 @@ * [db](#db) * [DB](#db.DB) * [\_\_init\_\_](#db.DB.__init__) + * [\_\_del\_\_](#db.DB.__del__) * [insert\_character](#db.DB.insert_character) * [insert\_into\_table](#db.DB.insert_into_table) * [read\_from\_table](#db.DB.read_from_table) @@ -325,6 +326,16 @@ def __init__() -> None Initialize the database connection. + + +#### \_\_del\_\_ + +```python +def __del__() -> None +``` + +Close the database connection when the object is destroyed. + #### insert\_character From 93d548b81339624d2c067b875e2537a667e3c228 Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 13:06:59 -0500 Subject: [PATCH 06/10] use integer correctly --- tests/test_page_loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_page_loader.py b/tests/test_page_loader.py index b0e5990..07d0634 100644 --- a/tests/test_page_loader.py +++ b/tests/test_page_loader.py @@ -301,12 +301,12 @@ def test_load_create_character( @patch("src.page_loader.Character") def test_load_old_character(mock_character) -> None: """Test the load_old_character method.""" - test_args = {"char_id": "42"} + char_id = 42 mock_test = Mock() mock_character.return_value = mock_test mock_character.load_character_from_db.return_value = None page_loader = PageLoader() - result = page_loader.load_old_character(test_args) + result = page_loader.load_old_character(char_id) assert result == mock_test From 34baaacc8444f5f53decfed235e7ee4f14c1df3e Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 13:07:47 -0500 Subject: [PATCH 07/10] Add validation for new character creation --- src/character.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/character.py b/src/character.py index d8cdb40..4119c32 100644 --- a/src/character.py +++ b/src/character.py @@ -3,11 +3,14 @@ from __future__ import annotations import contextlib +import typing + +if typing.TYPE_CHECKING: + from src.character_class import CharacterClass + from src.race import Race -from src.character_class import CharacterClass from src.db import DB from src.dice import Dice -from src.race import Race class Character: @@ -53,6 +56,15 @@ def new_character(self, name: str, race: str, dnd_class: str, stats: dict[str, i worst_stat = CharacterClass(self.dnd_class).get_worst_stat() self.roll_stats(primary_stat, worst_stat) else: + # Validate stats + required_stats = ["Constitution", "Dexterity", "Strength", "Wisdom", "Intelligence", "Charisma"] + for stat in required_stats: + if stat not in stats: + raise ValueError(f"Missing required stat: {stat}") + if not isinstance(stats[stat], int): + raise TypeError(f"Stat value for {stat} must be an integer") + if stats[stat] < 1 or stats[stat] > 20: # D&D stats typically range from 1-20 + raise ValueError(f"Stat value for {stat} must be between 1 and 20") self.stats = stats self.update_race_details() self.update_skills() From f83d177db2598fe03ea6ceecb2fa352659dd9399 Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 13:10:52 -0500 Subject: [PATCH 08/10] Fix lint issues --- src/character.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/character.py b/src/character.py index 4119c32..ed913bb 100644 --- a/src/character.py +++ b/src/character.py @@ -3,14 +3,10 @@ from __future__ import annotations import contextlib -import typing - -if typing.TYPE_CHECKING: - from src.character_class import CharacterClass - from src.race import Race - from src.db import DB from src.dice import Dice +from src.character_class import CharacterClass +from src.race import Race class Character: From 68f630f74877f6ddbcfe6a8fb141422285d20d07 Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 13:30:53 -0500 Subject: [PATCH 09/10] Fix lint issues --- src/character.py | 18 +++++++++++++----- src/db.py | 10 +++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/character.py b/src/character.py index ed913bb..7acb3a1 100644 --- a/src/character.py +++ b/src/character.py @@ -3,9 +3,10 @@ from __future__ import annotations import contextlib + +from src.character_class import CharacterClass from src.db import DB from src.dice import Dice -from src.character_class import CharacterClass from src.race import Race @@ -47,6 +48,10 @@ def new_character(self, name: str, race: str, dnd_class: str, stats: dict[str, i self.name = name self.race = race self.dnd_class = dnd_class + + min_stat = 1 + max_stat = 20 + if stats is None: primary_stat = CharacterClass(self.dnd_class).get_primary_stat() worst_stat = CharacterClass(self.dnd_class).get_worst_stat() @@ -56,11 +61,14 @@ def new_character(self, name: str, race: str, dnd_class: str, stats: dict[str, i required_stats = ["Constitution", "Dexterity", "Strength", "Wisdom", "Intelligence", "Charisma"] for stat in required_stats: if stat not in stats: - raise ValueError(f"Missing required stat: {stat}") + error_message = f"Missing required stat: {stat}" + raise ValueError(error_message) if not isinstance(stats[stat], int): - raise TypeError(f"Stat value for {stat} must be an integer") - if stats[stat] < 1 or stats[stat] > 20: # D&D stats typically range from 1-20 - raise ValueError(f"Stat value for {stat} must be between 1 and 20") + error_message = f"Stat value for {stat} must be an integer" + raise TypeError(error_message) + if stats[stat] < min_stat or stats[stat] > max_stat: # D&D stats typically range from 1-20 + error_message = f"Stat value for {stat} must be between {min_stat} and {max_stat}" + raise ValueError(error_message) self.stats = stats self.update_race_details() self.update_skills() diff --git a/src/db.py b/src/db.py index 61448bc..49fde19 100644 --- a/src/db.py +++ b/src/db.py @@ -1,9 +1,12 @@ """A module for the database connection.""" +import logging import os import mysql.connector +logging.basicConfig(level=logging.INFO) + class DB: """A class to represent a database connection.""" @@ -20,13 +23,14 @@ def __init__(self) -> None: user=self.user, password=self.password, ) - except (KeyError, mysql.connector.Error) as e: - print(f"Database connection error: {e}") + except (KeyError, mysql.connector.Error): + logger = logging.getLogger(__name__) + logger.exception("Database connection error") self.db = None def __del__(self) -> None: """Close the database connection when the object is destroyed.""" - if hasattr(self, 'db') and self.db is not None: + if hasattr(self, "db") and self.db is not None: self.db.close() def insert_character( From 6d42af0fc82ae4d3d66a41872bd0e3477e4eadb1 Mon Sep 17 00:00:00 2001 From: fladolcetta Date: Fri, 25 Jul 2025 13:33:48 -0500 Subject: [PATCH 10/10] Add tests --- tests/test_character.py | 18 ++++++++++++++++++ tests/test_db.py | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/tests/test_character.py b/tests/test_character.py index 8a3b8c9..748cd11 100644 --- a/tests/test_character.py +++ b/tests/test_character.py @@ -17,6 +17,12 @@ def fixture_mock_dice() -> None: yield mock_dice_object +def test_character_destruction() -> None: + """Test that the Character class closes the connection when the object is destroyed.""" + character = Character() + del character + + @pytest.fixture(name="_mock_race") def fixture_mock_race() -> None: """Mock the race class.""" @@ -42,6 +48,18 @@ def fixture_mock_character_class() -> None: yield mock_character_class_object +def test_character__stat_validation() -> None: + """Test the validation of the Character class.""" + char = Character() + + with pytest.raises(ValueError, match="Missing required stat: Dexterity"): + char.new_character("Aragorn", "Elf", "Ranger", {"Constitution": 19}) + with pytest.raises(TypeError): + char.new_character("Aragorn", "Elf", "Ranger", {"Constitution": "19"}) + with pytest.raises(ValueError, match="Constitution must be between 1 and 20"): + char.new_character("Aragorn", "Elf", "Ranger", {"Constitution": 200}) + + def test_character_initialization() -> None: """Test the initialization of the Character class.""" character = Character() diff --git a/tests/test_db.py b/tests/test_db.py index f6b28ae..0043bfd 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -23,6 +23,12 @@ def test_db_initialization(mock_connect) -> None: assert db.db is not None +def test_db_destruction() -> None: + """Test that the DB class closes the connection when the object is destroyed.""" + db = DB() + del db + + @patch.dict( os.environ, {"MYSQL_USER": "user", "MYSQL_PASSWORD": "password"},