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.