Reload bug fix
This commit is contained in:
1 parent
5dbfd2a805
commit
1626fe83bf
1 file changed
+114
@@ -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.
|
||||||
Reference in new issue
Block a user