diff --git a/APPDAEMON_RELOAD_BUG_FIX.md b/APPDAEMON_RELOAD_BUG_FIX.md new file mode 100644 index 0000000..dd573b8 --- /dev/null +++ b/APPDAEMON_RELOAD_BUG_FIX.md @@ -0,0 +1,114 @@ +# Bug AppDaemon 4.5.10 : KeyError au rechargement des apps + +## Symptôme +Lors d'une modification de configuration YAML, le rechargement des apps +échoue dans la boucle utilitaire avec cette erreur : + +```text +check_app_config_files → utils.deep_compare(current_app, prev_app) + if isinstance(v, dict) and isinstance(data[k], dict): +KeyError: 'input_selection' +``` + +Le nom de la clé dépend de la configuration modifiée. + +## Cause +`deep_compare()` accède à `data[k]` sans vérifier que la clé existe dans +l'ancienne configuration. L'ajout d'une clé contenant un dictionnaire peut +ainsi provoquer un `KeyError`. La fonction peut aussi ignorer les clés +supprimées, car elle parcourt uniquement la nouvelle configuration. + +Le même problème a été signalé avec la clé `mutes` dans les commentaires de +[AppDaemon/appdaemon#2396](https://github.com/AppDaemon/appdaemon/issues/2396). +Le correctif du problème initial de ce ticket concerne les plugins personnalisés, +pas nécessairement ce problème de comparaison signalé en commentaire. + +## Statut +- Patch local appliqué dans le conteneur sur AppDaemon **4.5.10**. +- Le code défectueux et son appel lors du reload ont également été vérifiés + dans la version **4.5.13** : cette mise à jour seule ne corrige pas ce bug. +- Source de référence : + [utils.py en 4.5.13](https://github.com/AppDaemon/appdaemon/blob/4.5.13/appdaemon/utils.py). +- La correction compare les ensembles de clés avant de comparer les valeurs, + y compris dans les dictionnaires imbriqués. + +## Comment réappliquer le patch + +Fichier concerné : `/usr/local/lib/python3.12/site-packages/appdaemon/utils.py` + +Dans `deep_compare()`, juste après `data = data or {}`, ajouter : + +```python + if check.keys() != data.keys(): + return False +``` + +Le début de la fonction devient : + +```python +def deep_compare(check: dict, data: dict) -> bool: + """Compares 2 nested dictionaries of values""" + data = data or {} # Replaces a None value with an empty dict + + if check.keys() != data.keys(): + return False + + for k, v in tuple(check.items()): + if isinstance(v, dict) and isinstance(data[k], dict): +``` + +La suite de la fonction reste inchangée. Avant de réappliquer sur une autre +version, vérifier son implémentation : ne pas remplacer aveuglément une +fonction qui aurait été corrigée en amont. + +**Redémarrer AppDaemon après application.** Modifier le fichier sur disque +ne remplace pas la fonction déjà importée par le processus en cours ; un +simple reload d'app ne suffit pas. + +Ce patch réside dans `site-packages`, hors de `/conf`. Il est perdu lors +d'une recréation du conteneur ou peut être écrasé par une réinstallation +ou mise à jour d'AppDaemon. + +## Comment vérifier le patch + +Exécuter dans le conteneur : + +```sh +python - <<'PY' +from appdaemon.utils import deep_compare + +assert deep_compare({'input_selection': {}}, {}) is False +assert deep_compare({}, {'input_selection': {}}) is False +assert deep_compare({'x': {'a': 1}}, {'x': {}}) is False +assert deep_compare({'x': {}}, {'x': {'a': 1}}) is False +assert deep_compare({'x': None}, {}) is False +assert deep_compare({'x': [1, 2]}, {'x': [1, 2]}) is True +assert deep_compare({'x': [1, 2]}, {'x': [2, 1]}) is False +assert deep_compare({'x': {'a': 1}}, {'x': {'a': 1}}) is True +assert deep_compare({'x': {}}, {'x': None}) is False +assert deep_compare({}, None) is True +assert deep_compare({'input_selection': {}}, None) is False +print('OK : 11 vérifications') +PY +``` + +Ces 11 vérifications ont réussi après application sur le module installé. +Elles vérifient la fonction dans un nouveau processus Python, pas le reload +complet dans le service en cours. Le service n'a pas été redémarré lors de +l'application de ce patch. + +## Retour arrière + +Une sauvegarde du fichier original a été créée dans le même répertoire : +`utils.py.before-deep-compare-fix.bak`. + +Pour annuler le patch sur cette installation, avant toute autre modification +ou mise à jour de `utils.py` : + +```sh +cp /usr/local/lib/python3.12/site-packages/appdaemon/utils.py.before-deep-compare-fix.bak \ + /usr/local/lib/python3.12/site-packages/appdaemon/utils.py +``` + +Redémarrer ensuite AppDaemon. Ne pas restaurer cette sauvegarde sur une +version différente d'AppDaemon.