Décorateur de Notification Personnalisé
Apprise prend en charge des gestionnaires de notification légers, basés sur des fichiers via le décorateur @notify().
C’est idéal lorsque vous voulez une intégration rapide sans créer puis publier un plugin Apprise complet.
Le système de décorateurs fonctionne en important un module Python et en collectant toute fonction enregistrée avec @notify().
Ces fonctions sont ensuite exposées comme des schémas Apprise.
Quand utiliser un décorateur
Section intitulée « Quand utiliser un décorateur »Utilisez un gestionnaire basé sur décorateur lorsque :
- vous avez besoin d’une intégration rapide et locale dans un environnement personnel ou maîtrisé ;
- vous voulez relier Apprise à une automatisation locale, à des scripts ou à un service privé ;
- vous ne voulez pas maintenir le cycle de vie complet d’un plugin (tests, packaging, cadence de publication).
Évitez cette approche lorsque :
- vous voulez intégrer le service directement dans Apprise (contribution upstream) ;
- vous avez besoin d’un parsing strict, de modèles, de reconstruction native d’URL, de batch ou d’une logique de retry robuste ;
- vous avez besoin d’une stabilité à long terme, d’une distribution publique ou d’un usage tiers.
Note de sécurité critique
Section intitulée « Note de sécurité critique »Le système de décorateur nécessite d’importer un module Python pour découvrir les fonctions décorées. Importer un module exécute du code au moment de l’import.
Cela signifie :
- tout code défini à la portée du module sera exécuté, pas seulement la fonction décorée ;
- tous les imports internes au module seront exécutés ;
- des effets de bord, accès fichiers, appels réseau ou sous-processus peuvent se produire au chargement.
N’utilisez des gestionnaires basés sur décorateur qu’avec du code de confiance dans des environnements contrôlés. Ne pointez pas Apprise vers un emplacement partagé ou non fiable sur le système de fichiers.
Décorateur vs plugin complet
Section intitulée « Décorateur vs plugin complet »Voici la règle pratique.
| Capacité | Décorateur @notify() | Plugin Apprise complet |
|---|---|---|
| Temps d’implémentation | Minutes | Heures à jours |
| Packaging et distribution | Fichier local | PyPI, paquets système, contribution amont |
| Modèles d’URL et validation | Minimal | Patterns intégrés (jetons, args et parsing strict) |
Confidentialité dans url() | Manuel | Standardisée (PrivacyMode.Secret) |
| Reconstruction native d’URL | Rare | Attendue quand c’est faisable |
| Support des pièces jointes | À implémenter vous-même | Patterns intégrés (AppriseAttachment) |
| Limitation de débit / retries | À implémenter vous-même | Standardisé (throttle(), helpers de requêtes) |
| Testabilité | Ad hoc | Patterns intégrés (AppriseURLTester, erreurs standardisées) |
| Idéal pour | Automatisation privée | Services amont et usage public |
Si votre gestionnaire a vocation à être réutilisé, devient critique pour le métier ou requiert un parsing d’URL solide, un vrai plugin Apprise est généralement plus adapté.
Utilisation de base
Section intitulée « Utilisation de base »Au minimum, vous devez définir un schéma unique et une fonction Python.
from apprise.decorators import notify
# Associe 'foobar://' à cette fonction et écrit sur stdout@notify(on="foobar")def my_wrapper(body, title, notify_type, *args, **kwargs): print(f"{notify_type}: {title} - {body}")Une fois chargé, Apprise peut déclencher cette fonction avec :
apprise -b "Hello world" foobar://Valeurs de retour
Section intitulée « Valeurs de retour »Une fonction décorée doit renvoyer :
Truepour indiquer un succès ;Falsepour indiquer un échec ;None(ou aucunreturn), ce qui est traité comme un succès.
Les échecs remontent à Apprise et affectent le statut global de la notification.
Signature de fonction
Section intitulée « Signature de fonction »Votre fonction wrapper peut accepter les paramètres suivants.
| Paramètre | Requis | Description |
|---|---|---|
body | Oui | Corps de la notification |
title | Non | Titre de la notification |
notify_type | Non | L’une des valeurs info, success, warning, failure |
body_format | Non | text, html ou markdown |
meta | Non | Métadonnées d’URL analysées et fusionnées |
attach | Non | Liste d’objets AppriseAttachmentVoir Pièces jointes pour lire, valider et transmettre des fichiers depuis un décorateur. |
*args | Oui | Requis pour la compatibilité future |
**kwargs | Oui | Requis pour la compatibilité future |
Incluez toujours *args et **kwargs afin de rester compatible avec les futures versions d’Apprise.
Un wrapper minimal peut ressembler à ceci :
from apprise.decorators import notify
@notify(on="foobar")def my_wrapper(body, *args, **kwargs): print(body)Le dictionnaire meta
Section intitulée « Le dictionnaire meta »Le paramètre meta fournit une vue entièrement analysée et fusionnée de :
- l’URL de déclaration du décorateur ;
- l’URL d’initialisation fournie par l’utilisateur.
Exemple de structure :
{ "schema": "foobar", "url": "foobar://user:pass@host:80/path?key=value", "host": "host", "user": "user", "password": "pass", "port": 80, "path": "/", "fullpath": "/path", "query": "key=value", "qsd": {"key": "value"}, "asset": AppriseAsset(), "tag": set(),}Seuls les champs présents dans l’URL sont inclus. Au minimum, schema, url, asset et tag sont toujours présents.
Déclarations complexes
Section intitulée « Déclarations complexes »Le décorateur peut précharger des valeurs par défaut en spécifiant une URL complète.
@notify(on="foobar://localhost:234?notify_on_complete=0")def my_wrapper(body, meta, *args, **kwargs): passLes utilisateurs peuvent surcharger ces valeurs à l’exécution :
apprise -b "override" foobar://example.com?notify_on_complete=1Résultat fusionné :
{ "schema": "foobar", "url": "foobar://example.com:234?notify_on_complete=1", "host": "example.com", "port": 234, "qsd": { "notify_on_complete": "1" }}Exemples de plugins
Section intitulée « Exemples de plugins »Cet exemple ajoute une ligne horodatée à la fin d’un fichier lorsqu’il est appelé.
Utilisation : demo://
from apprise.decorators import notifyimport logging
@notify(on="demo")def my_wrapper(body, title, notify_type, *args, **kwargs):
# Configurer le système de logs logging.basicConfig( filename='/tmp/my-demo.log', level=logging.DEBUG, filemode='w', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# Créer une instance de logger logger = logging.getLogger(__name__)
# Journaliser un message simple au niveau INFO logger.info("f{title} - {body}")Cet exemple envoie une charge utile JSON à un serveur distant. Il montre comment analyser des jetons d’URL (comme apikey).
Utilisation : myapi://hostname/apikey
from apprise.decorators import notifyimport requests
@notify(on="demo://localhost/")def my_wrapper(body, title, notify_type, *args, **kwargs): print(f"{notify_type}: {title} - {body}") # Récupérer l'hôte et préparer l'URL de destination host = meta.get('host') url = f'https://{host}/api/v1/send'
# Récupérer la clé API ou abandonner apikey = meta.get('path', '').split('/')[0] if not apikey: return False
# Préparer la charge utile à envoyer payload = {'key': apikey, 'msg': body}
try: requests.post(url, json=payload)
except (requests.RequestException) as e: return False
return Truefrom __future__ import annotations
import subprocessfrom apprise.decorators import notify
# N'autoriser qu'un très petit ensemble de commandes.# Ne construisez jamais ces valeurs depuis une entrée utilisateur.ALLOWED = { "puppet": ["/usr/bin/puppet", "agent", "-t"], "reload-nginx": ["/usr/bin/systemctl", "reload", "nginx"],}
@notify(schema="ops")def ops_notify( body: str, title: str = "", *, meta: dict | None = None, **kwargs,) -> bool: """ Exemple d'utilisation : ops://?allow=yes
Motif de déclenchement : title: "run: puppet" -> exécute puppet agent -t title: "run: reload-nginx" -> recharge nginx """ meta = meta or {} qsd = meta.get("qsd") or {}
# Garde-fou : exiger un opt-in explicite dans l'URL. allow = str(qsd.get("allow", "")).lower() in ("1", "yes", "true", "on") if not allow: return True
t = (title or "").strip().lower() if not t.startswith("run:"): return True
key = t.split(":", 1)[1].strip() cmd = ALLOWED.get(key) if not cmd: return False
try: proc = subprocess.run( cmd, check=False, capture_output=True, text=True, timeout=60, ) return proc.returncode == 0
except (OSError, subprocess.TimeoutExpired): return Falsefrom __future__ import annotations
import reimport subprocess
from apprise.decorators import notify
# Notes de sécurité :# - Préférez des rôles SQL au moindre privilège.# - Évitez d'exécuter du SQL arbitraire depuis le corps de notification.# - Utilisez un mappage strict de mots-clés vers des requêtes prédéfinies.# - Cet exemple appelle `psql` pour éviter d'ajouter une dépendance Python.
SQL = { "vacuum": "VACUUM (ANALYZE);", "health": "SELECT 1;", "locks": "SELECT count(*) FROM pg_locks;",}
_KEYWORD = re.compile(r"^db:\s*(?P<key>[a-z0-9_-]+)\s*$", re.IGNORECASE)
# Initialise pg://@notify(on="pg://pgsql.mydomain.com")def pg_hook(body, title, notify_type, meta, *args, **kwargs): m = _KEYWORD.match((title or "").strip()) if not m: return True
key = m.group("key").lower() sql = SQL.get(key) if not sql: return False
# Construire notre URL postgresql:// à partir de celle en pg:// dsn = 'postgresql://' + meta.get('url')[len('pg://'):]
# Appeler psql proprement, sans shell argv = [ "psql", dsn, "-v", "ON_ERROR_STOP=1", "-X", "-q", "-c", sql, ] proc = subprocess.run(argv, check=False, text=True, capture_output=True, timeout=60)
if proc.stdout: print(proc.stdout.strip()) if proc.stderr: print(proc.stderr.strip())
return proc.returncode == 0Les valeurs par défaut définies dans le décorateur persistent tant qu’elles ne sont pas explicitement surchargées.
Comportement d’enregistrement du plugin
Section intitulée « Comportement d’enregistrement du plugin »En interne, le décorateur :
- enregistre un wrapper dynamique
NotifyBase; - lie votre fonction au schéma ;
- impose l’unicité du schéma ;
- prend automatiquement en charge les pièces jointes et le stockage persistant.
Si un schéma existe déjà, Apprise journalise un avertissement et ignore l’enregistrement.
Charger des décorateurs personnalisés
Section intitulée « Charger des décorateurs personnalisés »CLI Apprise
Section intitulée « CLI Apprise »Par défaut, Apprise scanne :
- Linux et macOS :
~/.apprise/plugins~/.config/apprise/plugins
- Windows :
%APPDATA%/Apprise/plugins%LOCALAPPDATA%/Apprise/plugins
Surcharge possible avec :
apprise -P /custom/plugin/path -b "test" foobar://API Python
Section intitulée « API Python »Fournissez les chemins de plugins via AppriseAsset :
from apprise import Apprise, AppriseAsset
asset = AppriseAsset(plugin_paths=[ "/path/to/plugins", "/path/to/plugin.py",])
aobj = Apprise(asset=asset)aobj.add("foobar://")aobj.notify("Hello")Règles de scan des répertoires :
- les fichiers et répertoires cachés sont ignorés ;
- un répertoire avec
__init__.pycharge uniquement ce fichier ; - un répertoire sans
__init__.pycharge tous les fichiers.pyà ce niveau ; - le scan n’est pas récursif.
Restrictions et bonnes pratiques
Section intitulée « Restrictions et bonnes pratiques »- les schémas doivent être uniques ;
- gardez les modules sûrs à importer ; n’effectuez pas d’appels réseau ni de traitements longs au moment de l’import ;
- validez et nettoyez les valeurs
metaavant de les utiliser ; - préférez les listes blanches à l’exécution dynamique ;
- placez les secrets dans des variables d’environnement ou des coffres à secrets, pas dans les URL ;
- si vous avez besoin d’un parsing fort, de batch, de pièces jointes ou d’une contribution amont, écrivez un plugin complet à la place.
L’API de décorateur Apprise offre un moyen rapide et flexible d’étendre Apprise avec une logique personnalisée tout en conservant l’interface familière en schema://. Elle convient parfaitement aux workflows internes, aux hooks d’automatisation et aux systèmes fortement intégrés.
Questions ou commentaires ?
Documentation
Vous avez repéré une faute de frappe ou une erreur ? Signalez-la ou proposez une correction .
Problèmes Techniques
Vous rencontrez un problème avec le code ? Ouvrez un ticket sur GitHub :