Merged in feature/pydantic_character_builder (pull request #1)
✨ pydantic character builder
Approved-by: Benjamin Baudouin
This commit is contained in:
@@ -72,3 +72,31 @@ 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
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
)
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user