Aller au contenu

Développement de Plugins

Les plugins Apprise, aussi appelés services, sont des classes Python qui héritent de NotifyBase. La façon la plus rapide d’en créer un nouveau consiste à partir d’un plugin fonctionnel proche de votre intégration cible, puis à adapter sa forme d’URL, son parsing et sa logique d’envoi.

Ce modèle est idéal pour :

  • écrire vers une destination locale (stdout, fichier, syslog) ;
  • prototyper un schéma d’URL avant d’ajouter la couche réseau.
apprise/plugins/demo.py
from .base import NotifyBase
from ..common import NotifyType
from ..locale import gettext_lazy as _
class NotifyDemo(NotifyBase):
service_name = _("Apprise Demo Notification")
protocol = "demo"
setup_url = "https://appriseit.com/services/demo/"
# Disable throttling for a purely local plugin
request_rate_per_sec = 0
templates = (
"{schema}://",
)
def url(self, *args, **kwargs):
params = self.url_parameters(*args, **kwargs)
return "{schema}://?{params}".format(
schema=self.protocol,
params=self.urlencode(params),
)
def send(self, body, title="", notify_type=NotifyType.INFO, **kwargs):
self.throttle()
print(f"{notify_type} - {title} - {body}")
return True
@staticmethod
def parse_url(url):
# demo:// has no host, so verify_host must be False
return NotifyBase.parse_url(url, verify_host=False)

Apprise détermine d’abord le schema:// qui lui a été fourni, puis appelle la méthode YourPlugin.parse_url() du plugin de notification pour effectuer le reste du traitement.

En général, le premier appel effectué dans votre fonction parse_url() consiste à appeler la méthode parente (issue de NotifyBase). Cela permet à Apprise d’extraire les éléments d’URL communs comme schema, host, port, user, password et fullpath. Votre parse_url() doit se concentrer sur les éléments spécifiques à votre service, puis renvoyer un dict dont les clés correspondent à la signature de votre __init__().

Une règle importante, vérifiée par les tests unitaires, est que les métadonnées de vos templates doivent correspondre proprement aux arguments de __init__(), soit directement (même nom), soit via map_to.

Limitation de Débit, Vérification, Redirections et Timeouts

Section intitulée « Limitation de Débit, Vérification, Redirections et Timeouts »

URLBase définit la vérification SSL, le comportement des redirections et les timeouts socket communs en tant qu’arguments d’URL. Passez toujours les attributs correspondants dans chaque appel requests.* de votre plugin :

  • verify contrôle verify_certificate — à passer via verify=self.verify_certificate
  • redirect contrôle redirects — à passer via allow_redirects=self.redirects
  • rto contrôle le timeout de lecture socket
  • cto contrôle le timeout de connexion socket

La propriété url_identifier sert à distinguer une configuration d’une autre de manière unique, afin que les données en cache ou persistantes n’écrasent pas des configurations différentes. C’est aussi ainsi qu’Apprise génère un identifiant unique cohérent et stable à partir d’une URL.

  • Incluez le schéma ou protocole, les identifiants et l’identité de connexion amont.
  • Excluez les cibles (canaux, destinataires, endpoints).
  • Excluez la plupart des paramètres GET. N’incluez un paramètre GET que s’il change fondamentalement le comportement de la communication amont.

Si deux URL décrivent la même configuration effective, elles devraient générer le même url_identifier, et donc le même url_id().

Notez également l’interrupteur de stockage :

  • Si store=no est défini, url_id() renvoie None, et url() doit conserver store=no dans sa sortie.

Exemple :

@property
def url_identifier(self):
return (
self.secure_protocol if self.secure else self.protocol,
self.user,
self.password,
self.host,
self.port if self.port else (443 if self.secure else 80),
)

Si votre plugin effectue des consultations répétées en amont (jetons OAuth, appels de découverte, identifiants résolus, vérifications de capacités), vous devriez envisager d’activer le stockage persistant. Cela vous évitera de refaire ces vérifications plus tard et accélérera le plugin.

Pour l’activer, définissez un storage_mode au niveau de la classe :

from apprise.common import PersistentStoreMode
class NotifyMyService(NotifyBase):
# ...
storage_mode = PersistentStoreMode.AUTO

Recommandations :

  • Utilisez PersistentStoreMode.AUTO pour la plupart des plugins.
  • Utilisez PersistentStoreMode.FLUSH uniquement lorsque les données mises en cache sont coûteuses à recalculer et que vous voulez une meilleure durabilité.
  • Conservez le comportement par défaut (mémoire uniquement) si votre plugin ne doit jamais persister d’état, ou si le cache apporte peu de bénéfice.

Combiné à un url_identifier bien formé (identité de configuration uniquement), cela permet à plusieurs instances visant des destinataires différents de réutiliser le même cache pour une même configuration amont.

__len__() permet aux auteurs et aux outils de savoir combien de cibles sont chargées dans un plugin.

  • Si vous ne la redéfinissez pas, l’implémentation de base renvoie 1.
  • Redéfinissez-la dans les plugins qui prennent en charge plusieurs cibles, généralement en renvoyant len(self.targets) avec un minimum de 1.

Si votre plugin a besoin de paquets supplémentaires, déclarez-les dans requirements afin qu’Apprise puisse indiquer ce qui est requis ou recommandé.

from ..locale import gettext_lazy as _
requirements = {
"details": _("This plugin requires cryptography for message signing."),
"packages_required": ["cryptography>=42"],
"packages_recommended": ["orjson>=3"],
}

Si votre plugin importe conditionnellement une bibliothèque tierce lourde, redéfinissez aussi runtime_deps() pour indiquer au gestionnaire de plugins quels noms de paquets de premier niveau ont été chargés.

class NotifyMyService(NotifyBase):
# ...
@staticmethod
def runtime_deps():
return ("mylibrary",)

La valeur renvoyée est un tuple de noms de paquets de premier niveau importables : la même chaîne que vous passeriez à import, et non le nom du paquet pip (par exemple "paho" pour paho-mqtt).

Le gestionnaire de notifications maintient un compteur de références pour chaque bibliothèque déclarée. Lorsque tous les plugins qui utilisent une bibliothèque donnée sont désactivés et que N_MGR.evict_on_disable vaut True, le gestionnaire retire cette bibliothèque, ainsi que tous ses sous-modules, de sys.modules, libérant ainsi les objets Python associés en mémoire.

  • Renvoyez la valeur par défaut de la classe de base () (tuple vide) si votre plugin n’a pas de dépendances runtime optionnelles justifiant une éviction.
  • Les sous-modules sont gérés automatiquement ; ne déclarez que le nom de premier niveau.
  • Cela est distinct de requirements, qui nomme des paquets pip à des fins de reporting humainement lisible. runtime_deps() nomme les paquets importables pour la gestion mémoire à l’exécution.

La section suivante récapitule les valeurs par défaut des variables définies automatiquement dans votre plugin, sauf surcharge explicite.

NotifyBase hérite de l’objet URLBase, qui définit les valeurs par défaut suivantes :

AttributDéfautRôleQuand le Surcharger
request_rate_per_sec0Intervalle de limitation de débit de base (secondes). 0 désactive la limitation au niveau de URLBase.À surcharger en général dans NotifyBase ou votre plugin, pas dans URLBase.
socket_connect_timeout4.0Timeout de connexion par défaut, utilisé quand cto n’est pas fourni.Si votre service nécessite régulièrement des handshakes TCP plus longs.
socket_read_timeout4.0Timeout de lecture par défaut, utilisé quand rto n’est pas fourni.Si votre service répond lentement ou diffuse des réponses en streaming.
verify_certificateTrueVérification des certificats SSL, utilisée quand verify n’est pas fourni.Seulement si votre service tourne dans des réseaux contrôlés avec certificats auto-signés et que verify=no vous convient.
redirectsTrueSuivi des redirections HTTP, utilisé quand redirect n’est pas fourni. Se rabat sur asset.http_redirects.Définir à False sur l’asset pour désactiver les redirections globalement si votre environnement l’exige.
templates()Templates de documentation.Presque toujours, afin que les outils et la documentation décrivent correctement le plugin.
template_tokens{}Métadonnées décrivant les jetons du chemin URL.Presque toujours, pour les schémas non triviaux.
template_args{ verify, redirect, rto, cto }Arguments d’URL communs et leurs valeurs par défaut.En général, on les étend plutôt que de les remplacer.
template_kwargs{}Métadonnées pour les arguments clé/valeur préfixés.Si vous prenez en charge +headers, :payload, -params, etc.

Votre plugin devrait hériter de NotifyBase, ce qui lui donne les valeurs par défaut suivantes :

AttributDéfautRôleQuand le Surcharger
enabledTrueSi False, le plugin n’est pas utilisé.À désactiver pour des raisons spécifiques à la plateforme ou aux dépendances.
category"native"Classe l’origine du plugin (native vs custom).En général, laissez tel quel.
requirements.detailsNoneTexte de requirements lisible humainement.Si vous devez expliquer des paquets optionnels ou requis.
requirements.packages_required[]Paquets requis pour le fonctionnement complet.Si votre plugin nécessite des bibliothèques supplémentaires.
requirements.packages_recommended[]Paquets optionnels qui améliorent le fonctionnement.Si le plugin peut tourner sans eux, mais en tire bénéfice.
service_urlNoneURL du fournisseur ou du produit amont.Pour les services publics, définissez-la.
setup_urlNonePage de configuration Apprise pour votre service.Définissez-la vers votre page appriseit.com/services/<service>/.
request_rate_per_sec5.5Intervalle de limitation de débit par défaut (secondes).Ajustez-le selon les limites du fournisseur, ou mettez 0 en local.
image_sizeNoneTaille d’image préférée, pour le pré-redimensionnement.À définir si votre service attend une taille précise.
body_maxlen32768Nombre maximal de caractères du corps avant troncature.À régler selon les contraintes amont.
title_maxlen250Nombre maximal de caractères du titre. Mettez 0 si les titres ne sont pas pris en charge.Mettez 0 pour les endpoints sans titre, ou ajustez selon le fournisseur.
body_max_line_count0Nombre maximal de lignes conservées. 0 désactive la troncature par lignes.Si le service amont est sensible au nombre de lignes.
persistent_storageTrueAutorise l’utilisation du stockage persistant.Si votre plugin ne doit jamais stocker d’identifiants ni d’état.
storage_modememoryMode de stockage persistant par défaut.Rare, mais ajustable pour des comportements particuliers.
timezoneNoneUtilise le fuseau détecté par le serveur quand None.Si votre service doit toujours fonctionner dans un fuseau précis.
notify_formattextFormat de message par défaut.Si votre service privilégie Markdown ou HTML.
overflow_modeupstreamStratégie de dépassement par défaut.Si vous voulez qu’Apprise découpe, tronque ou modifie le dépassement.
interpret_emojisFalseInterprétation des emojis.Si le service amont bénéficie d’une conversion des emojis.
attachment_supportFalseActivation des pièces jointes.Définissez True si vous acceptez les pièces jointes.
default_html_tag_id"b"Utilisé pour injecter le titre dans le corps pour les services sans titre.Rare, sauf si vous souhaitez un formatage différent.

Apprise valide les types à l’aide d’un motif strict : ((choice|list):)?(string|bool|int|float).

TypeUtilisé dansSignificationDirectives requisesNotes
stringtokens, argsUne seule chaîneaucuneCourant pour les noms d’hôte, jetons et noms.
inttokens, argsUn seul entieraucuneUtilisez min et max pour borner les ports, comptes, etc.
floattokens, argsUn seul flottantaucuneUtilisez min et max pour fixer des bornes.
booltokens, argsUn booléendefault (requis pour args)Les args booléens doivent fournir une valeur par défaut.
choice:stringtokens, argsUne valeur parmi un ensemble fixevaluesLes entrées choice doivent fournir values, et default doit en faire partie s’il est défini.
choice:inttokens, argsUn entier parmi un ensemble fixevaluesUtile pour des sélecteurs de mode ou des énumérations.
choice:floattokens, argsUn flottant parmi un ensemble fixevaluesRare, mais pris en charge.
list:stringtokens, argsUne liste de chaînesdelimLes entrées de liste doivent fournir des délimiteurs.
list:inttokens, argsUne liste d’entiersdelimDécoupe puis conversion en entier.
list:floattokens, argsUne liste de flottantsdelimDécoupe puis conversion en flottant.

Autres règles importantes :

  • choice:bool n’est pas autorisé ; utilisez bool à la place.
  • regex doit être un 2-tuple (pattern, option), et les motifs doivent commencer par ^ et se terminer par $.
  • Si required ou private ne sont pas fournis, ils valent False par défaut.
  • Si values est un dictionnaire, il est converti en liste de clés.

Ces exemples se concentrent uniquement sur la manière dont les métadonnées de template, __init__() et parse_url() s’articulent.

Aucun jeton n’est requis lorsque les templates n’incluent pas d’entrées personnalisées {token}.

class MyPlugin(NotifyBase):
## Plugin variables here (intentionally omitted)
secure_protocol = "foobar"
templates = ("{schema}://",)
def __init__(self, *args, **kwargs):
# Rest of code here
super().__init__(*args, **kwargs)

Les tests unitaires imposent les clés autorisées et les contraintes de type.

DirectiveUtilisée dansSignification
nametokens, args, kwargsLibellé lisible humainement, généralement enveloppé dans gettext_lazy().
typetokens, argsType de valeur, validé par l’expression régulière stricte des types.
requiredtokens, argsMarque une entrée comme obligatoire. Si omis, vaut False.
privatetokens, argsMarque une entrée comme sensible. Si omis, vaut False.
defaultargsValeur par défaut utilisée quand l’URL ne précise rien. Requise pour les args bool.
valuestypes choiceValeurs autorisées pour les types choice, requises pour tout type choice:*.
min, maxint, floatBornes pour les types numériques.
regextokens, argsRegex de validation, toujours sous la forme (pattern, option) avec ancrage ^...$.
delimtypes listDélimiteurs utilisés pour découper les listes, requis pour les types list:*.
prefixkwargsRequis pour les kwargs, doit être l’un de :, + ou -.
map_totokens, args, kwargsAssocie une clé à un autre nom d’argument __init__(). Les tests vérifient qu’il s’agit d’un argument valide ou d’un mot-clé reconnu.
alias_ofargs, kwargsDéclare un alias pour un token ou argument existant, souvent pour faciliter la configuration YAML.
grouptokensUtilisé pour regrouper plusieurs jetons mappés dans une entrée de type liste.

Les cibles map_to reconnues par le framework incluent les champs d’URL courants et les arguments partagés, même s’ils ne figurent pas dans le __init__() de votre plugin, comme user, password, host, port, schema, fullpath, format, overflow, emojis, tz, verify, cto, rto et store.

Apprise utilise les métadonnées de vos templates comme un contrat, et les tests en garantissent la cohérence :

  • Chaque élément de template_tokens doit correspondre à un véritable argument de __init__(), soit directement (même clé), soit via map_to.
  • Les alias (alias_of) sont autorisés dans les args et kwargs, mais pas dans les tokens.
  • Chaque alias_of doit pointer vers un vrai token ou arg, et ne peut pas simplement se pointer lui-même, sauf s’il existe aussi dans les tokens.
  • Pour les entrées kwargs, prefix est obligatoire et doit être l’un de :, + ou -.

Les entrées alias ne doivent jamais redéclarer des propriétés héritées de leur cible. Les clés suivantes sont interdites sur toute entrée contenant alias_of :

private, type, required, regex, default, min, max, map_to, prefix, group

Ces propriétés sont résolues à l’exécution en suivant la chaîne d’alias. Des consommateurs comme le générateur d’URL les lisent depuis la cible, et non depuis l’alias lui-même. Les seuls ajouts autorisés aux côtés de alias_of sont :

Clé supplémentaireQuand elle est autorisée
nameRequise lorsque alias_of liste plusieurs cibles, afin de fournir un libellé de regroupement pour l’interface.
delimAutorisée lorsque la cible aliasée est de type list:*, afin de surcharger le ou les délimiteurs.

Approches valides :

  1. Alias simple : alias_of uniquement

    "t": {"alias_of": "token"},
  2. Alias multiples : alias_of + name pour le libellé de regroupement

    "tok": {
    "name": _("Token"),
    "alias_of": ("access_token", "token_a", "token_b"),
    },
  3. Alias de liste avec surcharge du délimiteur

    "to": {
    "alias_of": "targets",
    "delim": (",", " "),
    },

Des propriétés supplémentaires en dehors de celles identifiées ci-dessus ne peuvent pas être utilisées. Par exemple, ceci n’est pas valide :

# do NOT redeclare inherited properties
"t": {
"alias_of": "token",
"private": True, # <-- This should have been defined in what is being inherited
},

Deux fonctions doivent travailler ensemble :

  • parse_url() doit extraire chaque argument exposé via template_args et template_kwargs.
  • url() doit émettre une URL capable de recréer le même objet, et doit générer le même url_identifier lorsqu’il est réinstancié.

Ce tableau documente les directives les plus courantes prises en charge par template_tokens, template_args et template_kwargs.

DirectiveUtilisée dansSignification
nametokens, args, kwargsLibellé lisible humainement, généralement enveloppé dans gettext_lazy().
typetokens, argsType de valeur, comme string, int, bool, choice:string, list:string.
requiredtokens, argsMarque une entrée comme obligatoire pour l’initialisation ou la validation.
defaultargsValeur par défaut utilisée quand l’URL ne précise rien. Requise pour les types bool.
privatetokens, argsMarque une valeur comme sensible et destinée à être masquée dans les vues de confidentialité.
regextokens, argsUn 2-tuple (pattern, flags) utilisé pour valider les valeurs. Les motifs doivent être ancrés avec ^ et $.
valuestypes choiceValeurs autorisées pour les types choice:*. Si un default est fourni, il doit appartenir à values.
delimtypes listDélimiteurs autorisés pour les types list:*.
prefixkwargsRequis pour les entrées kwargs ; définit le préfixe d’injection :, + ou -.
map_totokens, args, kwargsAssocie la clé de directive à un autre nom d’argument __init__().
alias_ofargs, kwargsFournit un nom alternatif se comportant exactement comme un autre token ou argument.
  • Un tuple de motifs d’URL (chaînes) montrant les formes valides.
  • Utilisez {schema} dans les templates, et gardez des jetons cohérents d’un template à l’autre.

Il s’agit à la fois de documentation et de métadonnées structurées pouvant être utilisées par les outils pour décrire les formes d’URL prises en charge.

Variables issues de la structure centrale de l’URL, comme :

schema://credentials/direction/?options=
| |
| variables here |

Les args décrivent les arguments de query string. NotifyBase fournit déjà format, overflow, emojis, store et tz. URLBase fournit déjà verify, rto et cto.

Modèles fréquents :

  • utilisez alias_of pour ajouter des synonymes ;
  • utilisez map_to pour faire correspondre des noms d’arguments orientés utilisateur avec les paramètres de votre __init__() ;
  • utilisez default pour documenter clairement le comportement.

Les kwargs servent aux arguments préfixés pouvant apparaître plusieurs fois, typiquement pour l’injection clé/valeur :

  • + est souvent utilisé pour les headers ;
  • - est souvent utilisé pour les paramètres d’URL ;
  • : est souvent utilisé pour les champs supplémentaires du payload.

Chaque entrée template_kwargs exige un prefix et un name, et ces informations sont aussi exposées dans le générateur d’URL sous la forme d’un tableau Paramètres Personnalisés où les utilisateurs peuvent ajouter autant de lignes clé/valeur qu’ils le souhaitent.

template_kwargs = dict(
NotifyBase.template_kwargs,
**{
"headers": {
"name": _("HTTP Header"),
"prefix": "+",
},
"params": {
"name": _("GET Parameter"),
"prefix": "-",
},
"payload": {
"name": _("Payload Extra"),
"prefix": ":",
},
},
)

Dans une URL, cela ressemble à : ?+X-Custom-Header=foo&-debug=1&:extra=bar

Si votre plugin nécessite des paquets supplémentaires, déclarez-les dans requirements afin qu’Apprise puisse indiquer ce qui est requis ou recommandé.

Exemple :

from ..locale import gettext_lazy as _
requirements = {
"details": _("This plugin requires cryptography for message signing."),
"packages_required": ["cryptography>=42"],
"packages_recommended": ["orjson>=3"],
}

Les plugins qui prennent en charge les pièces jointes doivent déclarer explicitement cette prise en charge en définissant l’attribut de classe suivant :

class NotifyExample(NotifyBase):
attachment_support = True

Une fois activée, les pièces jointes sont fournies au plugin sous la forme d’une liste d’objets AppriseAttachment. Ces objets abstraient l’accès aux fichiers et peuvent reposer sur des fichiers locaux, des données en mémoire ou des ressources HTTP distantes.

Les détails complets sur la gestion des pièces jointes, le cycle de vie, les limites de taille et les modèles d’implémentation courants sont documentés ici :

Il est fortement recommandé aux auteurs de consulter cette documentation avant d’implémenter cette prise en charge, car une gestion incorrecte peut facilement entraîner une consommation mémoire excessive ou des envois échoués.

Le générateur d’URL Apprise construit son formulaire entièrement à partir des métadonnées exportées par votre plugin. Des métadonnées bien formées offrent une expérience plus propre et conviviale.

Checklist de Qualité pour le Générateur d’URL

Section intitulée « Checklist de Qualité pour le Générateur d’URL »
Élément à définirPourquoi c’est important
service_nameAffiché comme nom du service dans les résultats de recherche.
service_urlAlimente le lien “Site du service” à côté du formulaire.
setup_urlAlimente le lien “Guide de configuration” pour les instructions pas à pas.
name sur chaque token/argApparaît comme libellé du champ du formulaire. Sans lui, la clé brute est affichée.
private: True sur les secretsRend le champ comme un mot de passe avec bouton d’affichage, et empêche la valeur de fuiter dans la barre d’adresse du navigateur.
required: True sur les tokens obligatoiresMarque les champs d’un astérisque (*) et bloque la génération d’URL tant qu’ils ne sont pas remplis.
regex sur les tokens validésFournit des indications de format côté client (non imposées dans le navigateur, mais exposées dans les métadonnées du schéma).
default sur les args optionnelsAffiché comme texte indicatif dans les listes déroulantes et les champs numériques.
templates ordonnés simple → complexeLe générateur d’URL utilise par défaut le template le plus complexe afin que tous les champs soient visibles dès le départ.

Le générateur d’URL choisit par défaut le template le plus complexe (celui qui contient le plus de champs {token}). À mesure que l’utilisateur remplit des champs, il bascule vers le template le plus simple qui couvre tous les jetons renseignés.

Ordonnez votre tuple templates du plus simple au plus complexe :

templates = (
"{schema}://{token}", # simplest — no targets
"{schema}://{token}/{targets}", # adds optional targets
"{schema}://{botname}@{token}/{targets}", # most complex
)

Marquez chaque clé d’API, jeton, mot de passe ou secret avec "private": True dans template_tokens comme dans template_args. Le générateur d’URL :

  1. rend le champ comme un mot de passe avec un bouton d’affichage ;
  2. affiche •••••• dans l’URL assemblée à la place de la vraie valeur ;
  3. exclut la valeur de la barre d’adresse du navigateur afin que les liens partagés n’exposent jamais les secrets.

Les entrées alias (alias_of) héritent du flag private de leur cible : ne le redéclarez pas sur l’alias.

AttributRôle
service_urlLien vers la page d’accueil du fournisseur ou du produit amont (par exemple https://discord.com/). Affiché comme “Site du service” dans le générateur d’URL.
setup_urlLien vers le guide d’intégration spécifique à Apprise sur appriseit.com (par exemple https://appriseit.com/services/discord/). Affiché comme “Guide de configuration”.

Les deux devraient toujours être définis. Les plugins génériques ou purement locaux (par exemple MQTT, notifications Windows) peuvent omettre service_url s’il n’existe pas d’URL amont pertinente.

Notez également que NotifyBase fournit des drapeaux de comportement au niveau de l’URL, comme format, overflow, emojis et le stockage persistant (store).

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 :

Conçu avec amour depuis le Canada