École Hexagone

TP3 – Intégration de la sauvegarde

4PYTH – Projet fil rouge « Fondation » · En autonomie, du 10 octobre au 5 novembre

Une flotte de vaisseaux spatiaux en orbite autour d'une planète

Contexte

La galaxie est en plein chaos et la gestion de vos équipages devient critique. Aujourd'hui, chaque fois que le programme s'arrête, la flotte revient à son état de départ. Recrues, promotions et vaisseaux ajoutés disparaissent.

Pour ne plus rien perdre, votre poste de commandement doit sauvegarder l'état de la flotte dans un fichier et savoir le recharger… sans se laisser piéger par un fichier abîmé ou falsifié.

Ce TP en bref

1 Des objets aux dictionnaires

Méthodes · héritage · composition

JSON ne connaît pas vos classes. json.dumps(fleet) provoque donc une TypeError. Chaque classe doit savoir se convertir en dictionnaire, avec une méthode to_dict() écrite dans son propre fichier (person.py, spaceship.py ou fleet.py).

Classeto_dict() renvoie…
Personfirst_name, last_name, gender et age
Operator"type": "operator", les clés de Person, puis role et experience
Mentalist"type": "mentalist", les clés de Person, puis mana
Spaceshipname, ship_type, condition et crew : la liste des to_dict() de chaque membre
Fleetname et spaceships : la liste des to_dict() de chaque vaisseau
Le bon format, vous l'avez déjà Le résultat de fleet.to_dict() a exactement le format de FLEET_DATA du TP2, avec une clé experience en plus pour les opérateurs. Votre build_fleet() saura donc reconstruire la flotte. Adaptez-le seulement pour lire l'expérience avec m.get("experience", 0), puisqu'elle est absente de FLEET_DATA.

      fleet = build_fleet(FLEET_DATA)
      fleet.spaceships[0].crew[0].act()         # Hugo Crast pilote le vaisseau.

      data = fleet.to_dict()
      print(data["spaceships"][0]["crew"][0])
      # {'type': 'operator', 'first_name': 'Hugo', 'last_name': 'Crast', 'gender': 'M',
      #  'age': 37, 'role': 'pilote', 'experience': 1}

      again = build_fleet(data)
      print(again.to_dict() == data)             # True
    
Indices
  • Revoyez la notion de sauvegarde d'objets avec to_dict().
  • Dans Operator, ne réécrivez pas les quatre clés de Person. Partez de super().to_dict() puis complétez le dictionnaire.
  • Dans Spaceship et Fleet, chaque objet contenu sait déjà se convertir. Une boucle qui appelle leur to_dict() suffit. C'est la composition qui travaille pour vous.

2 Sauvegarder et charger

JSON · fichiers · exceptions

Dans un nouveau fichier storage.py, écrivez :

  1. save_fleet(fleet, path), qui enregistre fleet.to_dict() dans un fichier JSON lisible : indenté, en UTF-8, avec les accents conservés.
  2. load_fleet(path), qui relit le fichier et renvoie un objet Fleet grâce à build_fleet().

load_fleet() ne doit jamais faire planter le programme. Quand le chargement est impossible, elle lève une ValueError avec un message clair, que le menu affichera :

SituationException d'origineMessage conseillé
le fichier n'existe pasFileNotFoundErroraucune sauvegarde « fleet.json »
le JSON est mal forméjson.JSONDecodeErrorla sauvegarde « fleet.json » est corrompue
une clé attendue manqueKeyErrorla sauvegarde « fleet.json » est incomplète
une valeur est interdite (rôle inconnu…)ValueError de vos classesle message de votre classe, tel quel

      save_fleet(fleet, "fleet.json")
      print(load_fleet("fleet.json").to_dict() == fleet.to_dict())   # True

      load_fleet("missing.json")
      # ValueError: aucune sauvegarde « missing.json »
    
Indices
  • Revoyez l'écriture et la lecture du JSON ainsi que le chargement robuste.
  • json.dump(data, file, indent=2, ensure_ascii=False) dans un fichier ouvert avec encoding="utf-8".
  • Dans un except, vous pouvez lever une nouvelle exception : raise ValueError("…").

3 Le menu

Menu · exceptions
  1. Ajoutez [7] Sauvegarder la flotte et [8] Charger une flotte.
  2. Le nom du fichier est demandé à l'utilisateur, avec fleet.json par défaut s'il appuie simplement sur Entrée.
  3. Si le chargement échoue, le message d'erreur s'affiche et la flotte actuelle est conservée.

      Votre choix : 7
      Fichier de sauvegarde [fleet.json] :
      Flotte Galactica sauvegardée dans « fleet.json ».

      Votre choix : 8
      Fichier à charger [fleet.json] : old.json
      Chargement impossible : aucune sauvegarde « old.json »
    

4 Testez comme un attaquant

Validation des entréesSécurité

Un fichier de sauvegarde se modifie avec n'importe quel éditeur de texte. Mettez-vous à la place d'un utilisateur malveillant : faites une copie de fleet.json, modifiez-la, puis essayez de la charger. Votre programme doit réagir ainsi :

Modification dans la copieComportement attendu
un rôle devient "root"chargement refusé : rôle inconnu : root
un mana devient 9999chargé, mais le mana est ramené à 100 par votre setter
la clé "age" d'un membre est suppriméechargement refusé : sauvegarde incomplète
la dernière accolade } est suppriméechargement refusé : sauvegarde corrompue
Réfléchissez
  • Pour le mana, votre programme corrige la valeur. Pour le rôle, il la refuse. Quelle stratégie est la plus sûre ? Dans quels cas ?
  • Si l'attaquant change un âge de 37 en 38, aucune de vos validations ne le voit. La valeur est plausible. Valider les données ne détecte pas toutes les modifications.
  • C'est pour éviter ces pièges que l'on utilise JSON et jamais eval() sur des données non fiables.

5 Fichiers de test et README

Git · documentation
  1. Commitez deux fichiers de test dans votre dépôt : une sauvegarde valide (fleet.json) et une sauvegarde piégée (fleet-corrupted.json), que votre programme doit refuser proprement.
  2. Complétez votre README.md :
    • comment lancer le programme et à quoi sert chaque option du menu ;
    • les bonus réalisés, TP par TP ;
    • votre déclaration d'usage de l'IA : quels outils, pour quelles tâches.

+ Pour aller plus loin

Facultatif

Bonus 1 – Événements aléatoires

Bonus 2 – Statistiques enrichies

Bonus 3 – Chargement automatique

Bonus 4 – Une méthode de classe from_dict()

✓ Avant de rendre

Vérifiez chaque point. Ils correspondent à la grille d'évaluation du projet Fondation.

Et le 5 novembre ? Vous aurez encore du temps en séance pour terminer le TP3. Le projet complet est à rendre au plus tard à 16h30, juste avant l'examen écrit. Pendant la journée, je vous recevrai un par un pour la soutenance. Arrivez avec un projet qui fonctionne !