Aller au contenu

Routage par Tags & Tentatives

Les tags Apprise ne font pas que regrouper des services — ils peuvent piloter l’ordre de livraison, les chaînes de secours et le comportement en cas d’échec sans modifier une seule ligne de code applicatif.

Cette page explique comment configurer ces fonctionnalités dans vos fichiers de configuration et comment les contrôler depuis la bibliothèque Python ou la CLI.


Chaque tag dans un fichier de configuration peut porter un préfixe numérique de priorité séparé par deux-points : N:nomdetag.

Les numéros inférieurs signifient une urgence plus haute — ils sont traités en premier.

# Priorité 1 -- canaux d'alerte principaux (urgence maximale)
1:alerts=slack://tokenA/tokenB/tokenC
1:alerts=discord://webhook_id/webhook_token
# Priorité 2 -- canaux secondaires (utilisés uniquement si la priorité 1 échoue entièrement)
2:alerts=telegram://bottoken/chatid
# Priorité 5 -- dernier recours
5:alerts=mailto://user:pass@example.com

Les tags sans préfixe de priorité ont par défaut la priorité 0 (urgence maximale).


Lorsque vous déclenchez un tag sans préfixe de priorité, Apprise utilise la chaîne d’escalade :

  1. Les services sont regroupés par priorité de tag configurée.
  2. Le groupe au numéro le plus bas (urgence la plus haute) s’exécute en premier.
  3. Si chaque service de ce groupe réussit, le résultat est considéré comme vrai immédiatement — les groupes de numéros supérieurs ne sont jamais déclenchés.
  4. Si un service du groupe échoue, Apprise escalade vers le niveau de priorité suivant.
# Déclencher tous les services 'alerts' avec la chaîne d'escalade.
# Les entrées de priorité 1 s'exécutent en premier. Si elles réussissent toutes,
# les entrées de priorité 5 ne sont jamais déclenchées.
apobj.notify(body="Utilisation disque à 90%", tag="alerts")
Fenêtre de terminal
# Équivalent CLI
apprise --config=config.yml --tag="alerts" --body="Utilisation disque à 90%"

Tous les services partageant le même nom de tag et le même numéro de priorité sont traités comme un seul groupe. Le groupe est considéré comme réussi uniquement si chaque service qu’il contient réussit. Si un seul service échoue, Apprise escalade immédiatement vers le groupe au numéro supérieur suivant.

Les tags sans préfixe numérique ont par défaut la priorité 0 — le niveau d’urgence le plus élevé. Cela signifie qu’il est possible de mélanger des versions simples et préfixées d’un même tag pour créer une chaîne d’escalade sans modifier les entrées d’URL existantes :

# Priorité 0 (par défaut, urgence maximale) -- ces deux entrées forment le premier groupe
abc=slack://tokenA/tokenB/tokenC
abc=discord://webhook_id/webhook_token
# Priorité 1 -- secours si le groupe de priorité 0 échoue
1:abc=telegram://bottoken/chatid

Lorsque notify(tag="abc") est appelé avec la configuration ci-dessus :

  1. Le groupe de priorité 0 (Slack + Discord) s’exécute en premier.
  2. Si les deux réussissent, True est retourné — Telegram n’est jamais contacté.
  3. Si l’un ou l’autre échoue, Apprise escalade vers le groupe de priorité 1 (Telegram).

Les tags sans préfixe ayant par défaut la priorité 0, les définitions abc et 0:abc sont entièrement équivalentes dans une configuration de service. Toutes deux placent le service dans le groupe de priorité 0, et un filtre "abc" comme un filtre "0:abc" correspondent aux services définis avec l’une ou l’autre forme. Si les deux formes figurent dans le même fichier de configuration, ces services seront toujours distribués ensemble comme un groupe unique.

Lorsque vous spécifiez un préfixe de priorité dans le filtre, Apprise ignore la chaîne d’escalade et ne notifie que les services dont le tag correspondant porte exactement cette priorité.

# Déclencher UNIQUEMENT les entrées 'alerts' configurées avec la priorité 2.
# Les entrées de priorité 1 et 5 ne sont pas affectées.
apobj.notify(body="Fenêtre de maintenance planifiée", tag="2:alerts")
Fenêtre de terminal
# Équivalent CLI
apprise --config=config.yml --tag="2:alerts" --body="Fenêtre de maintenance planifiée"
Valeur du filtreComportement
"alerts"Chaîne d’escalade — priorité la plus haute (numéro le plus bas) d’abord
"2:alerts"Exclusif — uniquement les entrées alerts de priorité 2
"0:alerts"Exclusif — uniquement les entrées de priorité 0 (alerts et 0:alerts sont identiques)

Quand notify() exécute plusieurs chaînes OR indépendantes en parallèle, le comportement par défaut est de laisser chaque chaîne se terminer avant de retourner — même si une chaîne a déjà échoué. Cela garantit que chaque service configuré obtient au moins une tentative.

Pour arrêter immédiatement tous les rounds d’escalade restants dès qu’une chaîne épuise ses groupes de priorité sans succès, définissez abort_on_chain_failure=True sur votre AppriseAsset :

from apprise import Apprise, AppriseAsset
asset = AppriseAsset(abort_on_chain_failure=True)
apobj = Apprise(asset=asset)
# ...ajout des services...
# Si la chaîne 'ops' échoue complètement, Apprise s'arrête immédiatement
# et ne lance pas de nouveaux rounds d'escalade pour la chaîne 'securite'.
apobj.notify(body="Alerte critique", tag=["ops", "securite"])
abort_on_chain_failureComportement en cas d’échec d’une chaîne
False (défaut)Toutes les chaînes se terminent ; chaque service a une chance d’être tenté
TrueLes rounds restants sont ignorés ; le résultat est considéré comme faux aussitôt

Chaque service peut être configuré avec son propre nombre de tentatives et délai inter-tentative. Ces paramètres sont indépendants de la chaîne d’escalade et s’appliquent à chaque échec de livraison.

  • retry — nombre de tentatives supplémentaires après le premier échec (0 = pas de nouvelle tentative).
  • wait — secondes de pause entre les tentatives (décimales acceptées ; 0.0 = pas de pause).

Ajoutez ?retry=N et/ou ?wait=S directement à l’URL sous forme de paramètres de requête.

# jusqu'à 3 nouvelles tentatives, avec 5 secondes d'attente entre chacune
1:alerts=slack://tokenA/tokenB/tokenC?retry=3&wait=5
# jusqu'à 2 nouvelles tentatives avec 1,5 seconde d'attente
1:alerts=discord://webhook_id/webhook_token?retry=2&wait=1.5
# Canal de secours -- pas besoin de nouvelles tentatives (comportement par défaut)
5:alerts=mailto://user:pass@example.com
ParamètreTypeContraintesPar défaut
retryint0 à 100
waitfloat0.0 à 20.0 ; les entiers sont promus en flottant0.5

Les valeurs invalides (nombres négatifs, chaînes non numériques, inf, nan) sont rejetées et reviennent à la valeur par défaut de l’asset (voir Valeurs par Défaut Globales ci-dessous).


Chaque service dispose d’un budget de temps — s’il (avec toutes ses tentatives et pauses) n’a pas terminé dans ce délai, Apprise cesse d’attendre plutôt que de rester bloqué indéfiniment. Ceci est indépendant de retry/wait ci-dessus : un service peut avoir droit à de nombreuses tentatives tout en étant coupé si l’ensemble prend trop de temps.

Par défaut, chaque service dispose de 60 secondes. Définissez votre propre valeur avec service_timeout sur AppriseAsset, ou désactivez complètement la limite avec 0 :

from apprise import Apprise, AppriseAsset
# Donne 10 secondes à chaque service au lieu des 60 secondes par défaut
asset = AppriseAsset(service_timeout=10)
apobj = Apprise(asset=asset)
apobj.add("slack://tokenA/tokenB/tokenC")
apobj.notify(body="Vérification rapide")
Fenêtre de terminal
# Équivalent CLI -- --service-limit (-SL) applique la même limite à chaque service
apprise --service-limit 10 --body "Vérification rapide" \
"slack://tokenA/tokenB/tokenC"

Un service qui dépasse son délai est signalé avec un statut TIMEOUT plutôt qu’un simple échec — voir Résultats de notification pour savoir comment vérifier cela depuis Python.

ParamètreTypeContraintesPar défaut
service_timeoutint / float0 ou plus ; 0 désactive la limite60

Le paramètre optional indique qu’un service peut échouer sans faire échouer toute la notification. Lorsque optional=yes est activé sur un service, un échec de livraison pour ce service n’affecte pas le résultat global. Le résultat global est quand même considéré comme vrai si tous les services requis ont réussi.

Utilisez ce paramètre pour les points de terminaison qui doivent recevoir la notification quand ils sont disponibles, sans faire échouer l’application appelante quand ils ne le sont pas.

Imaginons que votre système domotique possède quatre écrans Kodi avec le tag media. Vous souhaitez afficher une notification sur les écrans actuellement allumés, mais vous ne voulez pas qu’un écran éteint déclenche une alerte d’astreinte à trois heures du matin.

# Les quatre écrans sont optionnels -- leur indisponibilité n'est jamais une erreur.
media=kodi://192.168.1.10
media=kodi://192.168.1.11
media=kodi://192.168.1.12
media=kodi://192.168.1.13

Pour les rendre optionnels, ajoutez ?optional=yes à chaque URL :

media=kodi://192.168.1.10?optional=yes
media=kodi://192.168.1.11?optional=yes
media=kodi://192.168.1.12?optional=yes
media=kodi://192.168.1.13?optional=yes
# Le résultat reste vrai même si tous les écrans sont éteints ou inaccessibles.
apobj.notify(body="Le dîner est prêt", tag="media")
Fenêtre de terminal
apprise --config=config.yml --tag="media" --body="Le dîner est prêt"

Le paramètre est par service. Vous pouvez librement mélanger des services optionnels et requis au sein du même groupe de tags. Le résultat global suit ces règles :

Services dans le lotRésultat
Tous requis, tous réussissentVrai (SUCCESS)
Tous requis, un ou plusieurs échouentFaux (FAILURE)
Tous requis, délai dépassé sans échec confirméFaux (TIMEOUT)
Tous optionnels, tous échouentVrai (SUCCESS)
Tous optionnels, tous dépassent le délaiVrai (SUCCESS)
Tous optionnels, tous réussissentVrai (SUCCESS)
Mélange requis/optionnels ; tous les requis réussissentVrai (SUCCESS)
Mélange requis/optionnels ; au moins un requis échoueFaux (FAILURE)
Mélange requis/optionnels ; délai dépassé sur les requis seulementFaux (TIMEOUT)

En résumé : les échecs et dépassements de délai des services optionnels ne font pas échouer le résultat global. Seul l’échec ou le dépassement de délai d’un service requis peut rendre le résultat faux.

Activer optional=yes ne saute pas le service et ne contourne pas la logique de tentatives. La livraison est toujours tentée, et si retry est supérieur à zéro, Apprise retentera le nombre de fois configuré avant d’abandonner. Ce n’est qu’après l’épuisement de toutes les tentatives qu’Apprise vérifie le paramètre optional pour décider d’absorber ou non l’échec.

# Ce service est tenté jusqu'à 4 fois (tentative initiale + 3 nouvelles tentatives)
# avec une pause de 2 secondes entre chacune. Si les quatre tentatives échouent,
# l'échec est silencieusement absorbé grâce à optional=yes.
media=kodi://192.168.1.10?optional=yes&retry=3&wait=2
- kodi://192.168.1.10:
tag: media
optional: yes
retry: 3
wait: 2

Vous pouvez définir ce paramètre directement sur n’importe quel objet service chargé, ou le passer en argument lors de la construction manuelle d’objets service :

import apprise
apobj = apprise.Apprise()
# Option 1 -- paramètre de requête URL (fonctionne avec tous les chargeurs de config)
apobj.add("kodi://192.168.1.10?optional=yes")
# Option 2 -- définir l'attribut directement après chargement
obj = apprise.Apprise.instantiate("kodi://192.168.1.10")
obj.optional = True
apobj.add(obj)
# Option 3 -- argument mot-clé du constructeur
from apprise.plugins.NotifyJSON import NotifyJSON
svc = NotifyJSON(host="192.168.1.10", optional=True)
apobj.add(svc)
# Les trois options produisent le même comportement : les échecs sont silencieusement absorbés.
result = apobj.notify(body="Le dîner est prêt")
print(result) # True même si chaque point de terminaison était inaccessible

Journal de débogage non critique associé à un canal d’alerte requis :

# Requis : l'ingénieur d'astreinte doit toujours être averti.
alerts=slack://tokenA/tokenB/tokenC
# Optionnel : enregistrer dans le canal de diagnostic interne s'il est disponible,
# mais ne jamais considérer son absence comme un échec.
alerts=json://diagnostics.internal/api/events?optional=yes
# Retourne True si Slack a réussi, quel que soit l'état du journal JSON.
apobj.notify(body="Base de données inaccessible", tag="alerts")

Groupe de tags entièrement optionnel — diffusion au mieux :

# Notifier les trois écrans s'ils sont accessibles ; ne jamais échouer s'ils ne le sont pas.
affichages=kodi://192.168.1.10?optional=yes
affichages=kodi://192.168.1.11?optional=yes
affichages=kodi://192.168.1.12?optional=yes
# Le résultat reste considéré comme vrai, quel que soit le nombre d'écrans ayant répondu.
apobj.notify(body="Mouvement détecté", tag="affichages")

Si vous souhaitez que chaque service d’une session Apprise partage les mêmes valeurs par défaut de tentatives, d’attente et de délai sans modifier chaque URL, définissez-les sur AppriseAsset :

from apprise import Apprise, AppriseAsset
asset = AppriseAsset(
default_service_retry=2, # jusqu'à 2 nouvelles tentatives en cas d'échec avant d'abandonner
default_service_wait=3.0, # attendre 3 secondes entre les tentatives
service_timeout=10, # abandonner un service bloqué après 10 secondes
)
apobj = Apprise(asset=asset)
apobj.add("slack://tokenA/tokenB/tokenC")
apobj.add("discord://webhook_id/webhook_token")
apobj.notify(body="Valeurs par défaut appliquées à chaque service")

Les valeurs par URL (?retry= / ?wait= ou clés YAML) remplacent les valeurs par défaut de l’asset pour ce service spécifique uniquement. Tous les autres services utilisent toujours les valeurs par défaut de l’asset.


Un :N en fin de valeur de filtre de tag remplace le nombre de tentatives pour chaque service correspondant, pour cet appel uniquement. La configuration permanente du service n’est pas modifiée.

# Réessayer chaque service correspondant jusqu'à 5 fois pour cet appel uniquement
apobj.notify(body="Alerte critique", tag="alerts:5")
# Correspondance exclusive priorité 2 ET jusqu'à 3 tentatives par service
apobj.notify(body="Priorité 2 uniquement", tag="2:alerts:3")
Fenêtre de terminal
# Équivalents CLI
apprise --config=config.yml --tag="alerts:5" --body="Alerte critique"
apprise --config=config.yml --tag="2:alerts:3" --body="Priorité 2 uniquement"

Le suffixe :N suit les mêmes règles de validation que le paramètre URL retry. Sa spécification n’affecte pas wait — chaque service utilise toujours sa valeur d’attente configurée entre les tentatives.


Passez timeout= à notify() pour limiter la durée de cet appel précis, du début à la fin — pour tous les services qu’il notifie. Contrairement à service_timeout (un budget par service défini sur AppriseAsset), timeout est un plafond pour l’appel entier.

# Limite cet appel entier à 30 secondes, quel que soit le service_timeout
# propre à l'asset
apobj.notify(body="Alerte urgente", timeout=30)
Fenêtre de terminal
# Équivalent CLI -- --limit (-L) s'applique à l'ensemble de l'exécution
apprise --limit 30 --body "Alerte urgente" \
"slack://tokenA/tokenB/tokenC"

Lorsque les deux sont définis, le délai le plus court l’emporte pour chaque service — timeout= ne prolonge jamais un service_timeout plus strict, et un service_timeout généreux ne remplace jamais un timeout= plus serré.

from apprise import Apprise, AppriseAsset
# Chaque service dispose normalement de 60 secondes (valeur par défaut
# d'AppriseAsset)...
apobj = Apprise(asset=AppriseAsset())
apobj.add("slack://tokenA/tokenB/tokenC")
# ...mais cet appel coupe chaque service à 5 secondes à la place.
apobj.notify(body="Abandonner rapidement cette fois", timeout=5)

timeout vaut 0 par défaut, ce qui signifie “aucun plafond au niveau de l’appel” — seul le service_timeout de AppriseAsset s’applique. Les valeurs négatives déclenchent une erreur, tout comme un service_timeout invalide.

Apprise supporte deux façons de combiner des noms de tags dans un seul appel à notify.

MotifSignificationExemple
OR — plusieurs valeurs de tag séparéesCorrespond aux services portant l’un ou l’autre des tags listésPlusieurs indicateurs --tag dans la CLI ; une liste de chaînes de tags en Python
AND — plusieurs noms de tags dans une seule entrée de filtreCorrespond uniquement aux services portant tous les tags listésUn seul --tag "a, b" dans la CLI ; une liste imbriquée en Python

Avec OR, chaque nom de tag forme une chaîne d’escalade indépendante. Toutes les chaînes doivent trouver un groupe de priorité entièrement réussi avant que le résultat soit considéré comme vrai. Un suffixe :N sur un token ne s’applique qu’aux services correspondant à ce token ; les autres ne sont pas affectés.

# OR -- les services devops obtiennent retry=3 ; management obtient retry=2.
# Chaque chaîne d'escalade de tag s'exécute indépendamment.
apobj.notify(body="Alerte équipe", tag=["devops:3", "management:2"])
Fenêtre de terminal
# OR -- plusieurs indicateurs --tag ; chacun est une chaîne indépendante.
apprise --config=config.yml \
--tag="devops:3" \
--tag="management:2" \
--body="Alerte équipe"

Si tous les services devops réussissent dans leur groupe de priorité le plus bas, Apprise envoie quand même la chaîne management — les deux chaînes sont indépendantes l’une de l’autre.

Avec AND, les services doivent porter tous les tags du groupe pour être sélectionnés. Les services correspondant à un filtre AND partagent une seule chaîne d’escalade.

# AND -- le service doit porter "devops" ET "management" pour correspondre.
apobj.notify(body="Alerte combinée", tag=[["devops", "management"]])
Fenêtre de terminal
# AND -- valeurs séparées par des virgules dans un seul indicateur --tag.
apprise --config=config.yml --tag="devops, management" --body="Alerte combinée"

L’exemple suivant combine les trois fonctionnalités : escalade par priorité, tentatives/attente par service, et remplacement du nombre de tentatives par appel.

# --- Canaux principaux (priorité 1) ---
# jusqu'à 2 nouvelles tentatives, 2 secondes d'attente entre chacune
1:alerts=slack://tokenA/tokenB/tokenC?retry=2&wait=2
1:alerts=discord://webhook_id/webhook_token?retry=2&wait=2
# --- Canal secondaire (priorité 5 -- secours) ---
# Une seule tentative suffit ; les canaux principaux ont déjà réessayé
5:alerts=mailto://user:pass@pagerduty.example.com

Les exemples ci-dessous illustrent des schémas de notification courants.

Tag unique avec deux niveaux d’escalade — les valeurs 2 et 50 montrent que les numéros sont arbitraires ; seul l’ordre importe.

# Premier essai (numéro inférieur = urgence supérieure)
2:alertes=slack://tokenA/tokenB/tokenC
# Secours ; s'exécute uniquement si la priorité 2 échoue
50:alertes=mailto://user:pass@example.com
# Chaîne d'escalade : Slack d'abord, e-mail seulement en cas d'échec
apobj.notify(body="Alerte", tag="alertes")
Fenêtre de terminal
apprise --config=config.yml --tag="alertes" --body="Alerte"

FonctionnalitéOù le configurerPortée
Priorité de tagN:nomdetag dans le fichier de configuration (TEXT ou YAML)Par définition de service
Nombre de tentatives par serviceParamètre URL ?retry=N ou clé YAML retry:Par définition de service
Délai inter-tentative par serviceParamètre URL ?wait=S ou clé YAML wait:Par définition de service
Service optionnelParamètre URL ?optional=yes ou clé YAML optional: yesPar définition de service
Délai par serviceAppriseAsset(service_timeout=N) ou --service-limit NPar définition de service
Valeurs par défaut globalesAppriseAsset(default_service_retry=N, default_service_wait=S)Tous les services de la session
Envoi en escaladenotify(tag="nomdetag") ou --tag nomdetag (sans préfixe de priorité)Un appel notify() / CLI
Envoi exclusifnotify(tag="N:nomdetag") ou --tag N:nomdetagUn appel notify() / CLI
Remplacement des tentativesnotify(tag="nomdetag:N") ou --tag nomdetag:NUn appel notify() / CLI
Remplacement du délainotify(timeout=N) ou --limit NUn appel notify() / CLI
Envoi OR multi-tagsnotify(tag=["tagA", "tagB"]) ou --tag tagA --tag tagBUn appel notify() / CLI
Envoi AND multi-tagsnotify(tag=[["tagA", "tagB"]]) ou --tag "tagA, tagB"Un appel notify() / CLI
Envoi OR multi-tags avec tentatives par tagnotify(tag=["tagA:N", "tagB:M"]) ou --tag tagA:N --tag tagB:MUn appel notify() / CLI
Arrêt anticipé sur échec de chaîneAppriseAsset(abort_on_chain_failure=True)Tous les services de la session
Questions ou commentaires ?

Documentation

Vous avez repéré une faute de frappe ou une erreur ?

Problèmes Techniques

Vous rencontrez un problème avec le code ? Ouvrez un ticket sur GitHub :

Conçu avec amour depuis le Canada