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

115 lines
4.2 KiB
Markdown

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