Files
ad_trident/APPDAEMON_RELOAD_BUG_FIX.md
T
2026-10-18 13:19:24 +02:00

4.2 KiB

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 :

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. 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.
  • 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 :

    if check.keys() != data.keys():
        return False

Le début de la fonction devient :

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 :

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 :

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.