Merged in feature/pydantic_character_builder (pull request #1)

 pydantic character builder

Approved-by: Benjamin Baudouin
This commit is contained in:
Anthony Melin
2026-05-27 08:29:33 +00:00
committed by Benjamin Baudouin
4 changed files with 280 additions and 46 deletions
+29 -1
View File
@@ -71,4 +71,32 @@ Pour exécuter ce projet, vous aurez besoin de Python 3.7 ou supérieur.
```bash ```bash
python3 app.py python3 app.py
``` ```
## Personnalisation du CharacterBuilder
Le `CharacterBuilder` peut être personnalisé pour utiliser différentes implémentations de sauvegarde et de chargement selon vos besoins.
Cette personnalisation se fait simplement en définissant une variable d'environnement avant de lancer votre script Python.
### Variable d'environnement : `ELIRON_BUILDER`
- **Nom** : `ELIRON_BUILDER`
- **Valeur possible** :
- `"pydantic"` : Utilise des implementations basées sur `pydantic`.
- Toute autre valeur ou absence de variable : Utilise les fonctions par défaut.
> ⚠️ **Important** : Pour utiliser le builder pydantic, vous devez avoir installé le package `pydantic` au préalable :
> ```bash
> pip install pydantic
> ```
### Exemple d'utilisation sous Linux / macOS
```bash
# Pour utiliser l'implémentation pydantic
export ELIRON_BUILDER=pydantic
python app.py
# Pour utiliser l'implémentation par defaut
unset ELIRON_BUILDER
python app.py
+84 -45
View File
@@ -1,11 +1,18 @@
from base_mob import BaseMob import importlib.util
import json import json
import os import os
import sys
from typing import Callable
from base_mob import BaseMob
class CharacterBuilder: class CharacterBuilder:
@staticmethod save_character_impl: Callable[[BaseMob], str]
def build_character(): load_character_impl: Callable[[str], None]
@classmethod
def build_character(cls):
"""Demande à l'utilisateur de saisir les attributs du personnage.""" """Demande à l'utilisateur de saisir les attributs du personnage."""
print("Création d'un nouveau personnage :") print("Création d'un nouveau personnage :")
name = input("Nom du personnage : ") name = input("Nom du personnage : ")
@@ -16,51 +23,83 @@ class CharacterBuilder:
mob_type = 0 mob_type = 0
return BaseMob(name, max_pv, strength, protection, speed, mob_type) return BaseMob(name, max_pv, strength, protection, speed, mob_type)
@staticmethod @classmethod
def save_character(character): def save_character(cls, character: BaseMob) -> None:
"""Enregistre un personnage dans un fichier JSON.""" """Enregistre un personnage dans un fichier JSON."""
character_data = { filename = cls.save_character_impl(character)
"name": character.name,
"max_pv": character.max_pv,
"current_pv": character.current_pv,
"strength": character.strength,
"protection": character.protection,
"speed": character.speed,
"mob_type": character.mob_type
}
filename = character.name + ".json"
with open(filename, "w",encoding='UTF-8') as file:
json.dump(character_data, file, indent=4)
print(f"Personnage enregistré dans {filename}.") print(f"Personnage enregistré dans {filename}.")
@staticmethod @classmethod
def load_character(name): def load_character(cls, name: str) -> BaseMob | None:
"""Charge un personnage à partir d'un fichier JSON.""" """Charge un personnage à partir d'un fichier JSON."""
filename = name + ".json"
if not os.path.exists(filename):
print(f"Erreur : Le fichier {filename} n'existe pas.")
return None
try: try:
with open(filename, "r",encoding='UTF-8') as file: return cls.load_character_impl(name)
character_data = json.load(file) except Exception as e:
# Vérification des clés essentielles dans le JSON pour éviter les erreurs
required_keys = ["name", "max_pv", "current_pv",
"strength", "protection", "speed", "mob_type"]
if not all(key in character_data for key in required_keys):
print(f"Erreur : Le fichier {filename} ne contient pas toutes les clés nécessaires.")
return None
# Construction du personnage
return BaseMob(
name=character_data["name"],
max_pv=character_data["max_pv"],
strength=character_data["strength"],
protection=character_data["protection"],
speed=character_data["speed"],
mob_type=character_data["mob_type"]
)
except json.JSONDecodeError:
print(f"Erreur : Le fichier {filename} n'est pas un JSON valide.")
return None return None
def save_character(character: BaseMob) -> str:
"""Enregistre un personnage dans un fichier JSON."""
character_data = {
"name": character.name,
"max_pv": character.max_pv,
"current_pv": character.current_pv,
"strength": character.strength,
"protection": character.protection,
"speed": character.speed,
"mob_type": character.mob_type
}
filename = character.name + ".json"
with open(filename, "w", encoding='UTF-8') as file:
json.dump(character_data, file, indent=4)
return filename
def load_character(name: str) -> BaseMob | None:
"""Charge un personnage à partir d'un fichier JSON."""
filename = name + ".json"
if not os.path.exists(filename):
print(f"Erreur : Le fichier {filename} n'existe pas.")
return None
try:
with open(filename, "r", encoding='UTF-8') as file:
character_data = json.load(file)
# Vérification des clés essentielles dans le JSON pour éviter les erreurs
required_keys = ["name", "max_pv", "current_pv",
"strength", "protection", "speed", "mob_type"]
if not all(key in character_data for key in required_keys):
print(f"Erreur : Le fichier {filename} ne contient pas toutes les clés nécessaires.")
return None
# Construction du personnage
return BaseMob(
name=character_data["name"],
max_pv=character_data["max_pv"],
strength=character_data["strength"],
protection=character_data["protection"],
speed=character_data["speed"],
mob_type=character_data["mob_type"]
)
except json.JSONDecodeError:
print(f"Erreur : Le fichier {filename} n'est pas un JSON valide.")
return None
pydantic = None
name = 'pydantic'
if name in sys.modules:
pydantic = sys.modules[name]
elif (spec := importlib.util.find_spec(name)) is not None:
pydantic = importlib.util.module_from_spec(spec)
sys.modules[name] = pydantic
spec.loader.exec_module(pydantic)
if pydantic and os.environ.get('ELIRON_BUILDER') == 'pydantic':
pydantic_character_builder = importlib.import_module('pydantic_character_builder')
CharacterBuilder.save_character_impl = pydantic_character_builder.save_character
CharacterBuilder.load_character_impl = pydantic_character_builder.load_character
else:
CharacterBuilder.save_character_impl = save_character
CharacterBuilder.load_character_impl = load_character
+46
View File
@@ -0,0 +1,46 @@
import pathlib
from pydantic import BaseModel
from base_mob import BaseMob
class CharacterModel(BaseModel):
name: str
max_pv: int
current_pv: int
strength: int
protection: int
speed: int
mob_type: int
def save_character(character: BaseMob) -> str:
filename = f'{character.name}.json'
character_model = CharacterModel(
name=character.name,
max_pv=character.max_pv,
current_pv=character.current_pv,
strength=character.strength,
protection=character.protection,
speed=character.speed,
mob_type=character.mob_type
)
pathlib.Path(filename).write_text(
data=character_model.model_dump_json(indent=4),
encoding='utf-8'
)
return filename
def load_character(name: str) -> BaseMob | None:
json_data = pathlib.Path(f'{name}.json').read_text(encoding='utf-8')
character_model = CharacterModel.model_validate_json(json_data)
return BaseMob(
name=character_model.name,
max_pv=character_model.max_pv,
strength=character_model.strength,
protection=character_model.protection,
speed=character_model.speed,
mob_type=character_model.mob_type,
)
+121
View File
@@ -0,0 +1,121 @@
import unittest
from unittest.mock import patch
import json
import pathlib
from base_mob import BaseMob
from character_builder import CharacterBuilder
ARTHUR_JSON = """{
"name": "Arthur",
"max_pv": 100,
"current_pv": 100,
"strength": 20,
"protection": 10,
"speed": 5,
"mob_type": 0
}"""
ARTHUR_FILE = pathlib.Path('Arthur.json')
class TestCharacterBuilder(unittest.TestCase):
@classmethod
def tearDownClass(cls):
ARTHUR_FILE.unlink(missing_ok=True)
@patch("builtins.input")
def test_build_character(self, mock_input):
# Arrange
mock_input.side_effect = [
"Arthur", # name
"100", # max_pv
"20", # strength
"10", # protection
"5" # speed
]
# Act
character = CharacterBuilder.build_character()
# Assert
self.assertIsInstance(character, BaseMob)
self.assertEqual(character.name, "Arthur")
self.assertEqual(character.max_pv, 100)
self.assertEqual(character.strength, 20)
self.assertEqual(character.protection, 10)
self.assertEqual(character.speed, 5)
self.assertEqual(character.mob_type, 0)
def test_save_character(self):
# Arrange
character = BaseMob(
name="Arthur",
max_pv=100,
strength=20,
protection=10,
speed=5,
mob_type=0,
)
# Act
CharacterBuilder.save_character(character)
# Assert
self.assertTrue(ARTHUR_FILE.exists())
self.assertEqual(ARTHUR_JSON, ARTHUR_FILE.read_text(encoding='utf-8'))
def test_load_character_file_not_found(self):
# Act
result = CharacterBuilder.load_character("Lancelot")
# Assert
self.assertIsNone(result)
def test_load_character_success(self):
# Arrange
ARTHUR_FILE.write_text(ARTHUR_JSON, encoding='utf-8')
# Act
character = CharacterBuilder.load_character("Arthur")
# Assert
self.assertIsNotNone(character)
self.assertEqual(character.name, "Arthur")
self.assertEqual(character.max_pv, 100)
self.assertEqual(character.strength, 20)
self.assertEqual(character.protection, 10)
self.assertEqual(character.speed, 5)
self.assertEqual(character.mob_type, 0)
def test_load_character_missing_keys(self):
arthur_dict: dict = json.loads(ARTHUR_JSON)
for key in arthur_dict:
with self.subTest(f'test without key "{key}"'):
# Arrange
invalid_dict = arthur_dict.copy()
invalid_dict.pop(key)
ARTHUR_FILE.write_text(json.dumps(
invalid_dict), encoding='utf-8')
# Act
result = CharacterBuilder.load_character("Arthur")
# Assert
self.assertIsNone(result)
def test_load_character_invalid_json(self):
# Arrange
ARTHUR_FILE.write_text(ARTHUR_JSON[1:], encoding='utf-8')
# Act
result = CharacterBuilder.load_character("Arthur")
# Assert
self.assertIsNone(result)
if __name__ == "__main__":
unittest.main()