# 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.