TracCronPlugin de nouveau maintenu

J’avais réalisé il y a plusieurs mois un plugin Trac pour lancer des tâches planfiées. Ce plugin, TracCronPlugin, a évolué quelque temps en fonction de mes besoins puis je n’y ai plus apporté de modifications malgrès les requêtes des utilisateurs.

Comme TracCronPlugin est en licence libre, je comptais bien sur une reprise du projet par un autre. C’est maintenant chose faite ! Après avoir marqué le projet que disponible à l’adoption, un utilisateur s’est manifesté. Je lui ai ouvert les droits en écriture sur PIPY ainsi que les droits sur TracHacks et voici TracCronPlugin de nouveau actif.

Imaginez vous Microsoft vous donner les sources de Windows 95 dont il ne se sert plus (pour gagner de l’argent) alors que vous aimeriez bien le (corriger) modifier ? Le libre c’est bien !

Exemple de plugin pour Trac

Trac, outil de gestion de projet de développement, est conçu pour recevoir de nouvelles fonctionnalités par ajout de plugin. Ce billet montre quelques techniques pour réaliser des plugins eux-mêmes extensibles.

Le schema suivant montre les éléments essentiels du modèle de plugin que Trac offre.

Pour créer un plugin il suffit de créer une classe qui hérite de Component. Ainsi Trac va charger la classe au démarrage.

class Core(Component):
 """
 Main class of the Trac Cron Plugin. This is the entry point
 for Trac plugin architecture
 """
 pass

Un composant est un objet passif qui est appelé lorsque Trac est activé. Comme c’est une application web, c’est sur requête du client que tout s’active. Pour répondre à ces sollicitations le plugin doit implémenter une interface connue que Trac va appeler.

Par exemple le plugin peut définit sa propre interface d’administration. Pour cela il doit implémenter IAdminPanelProvider er ITemplateProvider

class Core(Component):
 """
 Main class of the Trac Cron Plugin. This is the entry point
 for Trac plugin architecture
 """    

 implements(IAdminPanelProvider, ITemplateProvider)

 # IAdminProviderPanel

 def get_admin_panels(self, req):
  if ('TRAC_ADMIN' in req.perm) :
   yield ('tracini', 'trac.ini', 'cron_admin', u'Trac Cron')

 def render_admin_panel(self, req, category, page, path_info):
  req.perm.assert_permission('TRAC_ADMIN')
  data = {}
  return 'cron_admin.html', data

 # ITemplateProvider

 def get_htdocs_dirs(self):
  return []

 def get_templates_dirs(self):
  from pkg_resources import resource_filename
  return [resource_filename(__name__, 'templates')]

L’implémentation au sens Trac consiste pour un plugin à s’enregistrer auprès de Trac comme fournisseur des interfaces par le biais de l’appel à la fonction

implements(*args)

en passant les interfaces voulues.

Dans l’exemple ci-dessous Trac va appeler la fonction get_admin_panel() quand il voudra créer la liste des panneau de configuration et render_admin_panel() quand il voudra afficher la panneau de configuration du plugin donné.

Mais on peut créer des plugins actifs qui initient des traitements périodiquement. Pour cela Trac ne propose rien, il faut faire soit même un plugin qui crée une Thread lors de son instanciation.

class Core(Component):
  def__init__(self,*args,**kwargs):
    Component.__init__(self, *args, **kwargs)
    Timer(60 , self.wake_up).start()

  def wake_up(self):
    print "Hello, one minute again"
    Timer(60 , self.wake_up).start()

Il existe un plugin qui se charge de toute la mécanique d’ordonnancement laissant le développeur se concentrer sur la tâche à faire. Il s’agit de TracCronPlugin.

Cela nous amène à expliquer comment écrire des plugin extensibles. La règle d’or est de ne pas lier son plugin avec du code interne ou même des class internes. Il faut toujours penser une fonctionnalité comme une interface.

La première étape est de bien isoler l’interface de la fonctionnalité. Par exemple on peut isoler l’interface d’un tâche comme devant avoir une méthode pour être lancée, une méthode pour s’identifier et une méthode de description. Alors l’interface d’une tâche peut se définir ainsi:

class ICronTask(Interface):
 """
 Interface for component task
 """

 def wake_up(self, *args):
  """
  Call by the scheduler when the task need to be executed
  """
  raise NotImplementedError

 def getId(self):
  """
  Return the key to use in trac.ini to cinfigure this task
  """
  raise NotImplementedError

 def getDescription(self):
  """
  Return the description of this task to be used in the admin panel.
  """
  raise NotImplementedError

Supposons que dans Trac il y ait des plugin qui implémentent cette interface (nous avons vu plus haut comment faire), il reste alors au plugin utilisateur d’utiliser un point d’extension en instanciant la classe ExtensionPoint comme ceci:

class Core(Component):
 task_event_list = ExtensionPoint(ITaskEventListener)

 def do_something_with_task(self):
  for task in self.cron_tack_list:
   # do something with a task
   task.wake_up()

On voit qu’un point d’extension est une séquence qui permet d’itérer sur les composants (plugin) qui implémentent l’interface passé en paramètre du constructeur de  ExtensionPoint

L’exemple complet peut être trouver en regardant le code source de TracCronPlugin

Planificateur de tâches dans Trac

wikipedia:Trac icon, modified for the Open Ico...
Image via Wikipedia

Trac est un outils de gestion de projet pour lequel il existe de nombreuses extensions que l’on trouve sur le site trac-hacks . Mais il existe un plugin qui manquait à cette liste: un planificateur de tâche comme Cron.

Pourquoi aurait-on besoin d’un cron? Certaines tâches récurrentes comme la création périodique de ticket de type tâche comme par exemple “revue de code” pourraient ne plus avoir besoin d’être créées à la main. Dans mon cas, j’ai une version personnalisée de Trac qui possède une fonction de synchronisation des utilisateurs natifs Trac avec une table spécifique mise à jour journalièrement. La synchronisation est codée dans une méthode appelée par un bouton dans l’interface d’administration. Cette synchronisation ne peut être appelée que depuis Trac car elle fait usage de l’API de Trac.

Pour parvenir à nos fins nous pouvons utiliser le planificateur de l’OS ( Cron unix ou AT de Windows) mais cette solution manque d’élégance car on va ajouter un adhérence avec l’OS. De plus même si on enrichie la commande trac-admin pour bénéficier de l’environnement de Trac, il restera à gérer la concurrence avec le processus trac. C’est pour ses raisons que le choix d’un ordonnanceur de tâche intégré à Trac a fait son chemin.

Comment développer un tel planificateur?

La documentation de Trac nous montre qu’il est possible de définir des “composant” et des “extension”. Un composant est un plugin Trac qui est instancié par Trac. Une extension est un composant qui implémente une interface. Dans Trac il existe des interface standard comme ITicketValidator qui indique que le composant sait valider les champs d’un ticket. Chaque composant peut utiliser des extensions.

Pour notre besoin il nous suffit de :

  1. définir une interface de tâche ICronTask
  2. définir un composant Corequi utilisera les extensions de type ICronTask
  3. Le composant Core doit se charger de gérer l’ordonnanceur par une thread dédiée
  4. La thread dédiée doit se lancer dès le démarrage de Trac: pour cela on va intercepter l’instanciation du composant parce que Trac ne définit par d’interface pour capter son démarrage

Tels sont les éléments clés à mettre en œuvre auxquels s’ajoutent les point suivants pour une plus grande utilisabilité:

  1. Paramétrage dans trac.ini et par interface d’administration
  2. État de l’ordonnanceur : marche/arrêt paramétrable
  3. précision de l’ordonnanceur paramétrable
  4. plusieurs planification pour une même tâche
  5. planification dans l’interface d’administration

Désormais ce plugin existe, il est disponible sur Trac-Hacks, il s’agit de TracCronPlugin

Retour d’expérience sur la personnalisation de Trac

 

wikipedia:Trac icon, modified for the Open Ico...
Image via Wikipedia

 

Il y a quelques jours que j’ai commencé à personnaliser Trac pour les besoins de mon employeur. L’objectif était de fournir aux utilisateurs qui sont en grande majorité non informaticiens, un outils de saisie de demande. Le choix de Trac comme base peut paraitre incongru puisque Trac est avant tout destiné à gérer les projets de développement logiciel. Certes, mais Trac n’en demeure pas moins un outils de traitement de bug, de ticket et pourquoi pas de demandes?

D’autres arguments m’ont poussé à choisir Trac. Il est gratuit, open-source et libre. De plus il dispose d’une large base d’extension permettant d’ajouter sans codage (codage en python) de nouvelles fonctionnalités voire d’en modifier des existantes. Ce billet montre comment nous sommes parvenus avec un minimum de code à obtenir une application répondant aux besoins et couvre la version 0.12 de Trac.

En premier lieu on s’aperçoit rapidement qu’il y a plusieurs niveaux d’intervention. En effet Trac est conçu comme un conteneur de projet. L’outil finale sera donc constitué de Trac plus un projet. Dans ce cas on voit clairement les niveaux d’interventions suivants:

  1. Par ajout de plugin
  2. Par paramétrage dans le projet dédié
  3. Par développement dans le projet dédié
  4. Par développement dans Trac lui-même
  5. Par développement dans les plugin

Ajout de plugin

Les liens suivants contiennent une très longue liste de plugin: le site de trac et le site des plugin.

Les plugins sont soit des fichiers py (module python) soit des fichiers egg (livrable ou librairie python). Si le plugin est disponible uniquement sous forme de source alors il faudra le compiler pour générer un egg avec la commande:

python setup.py bdist_egg

Pour installer un plugin il est préférable de le mettre dans le répertoire du projet. Cela permet de faire cohabiter sur le même système plusieurs versions d’une librairie (chacune dans un processus python distinct). Par exemple si le projet s’appelle gesdem il faut placer les plugin dans

gesdem/plugins

Toutefois si Trac est en cours d’éxécution, on peut passer par l’interface d’administration d’un projet donné pour ajouter le plugin.

Paramétrage dans le projet dédié

Le paramétrage dans le projet dédié consiste à modifier les paramétres dans le fichier trac.ini du prjet. Soit dans le cas du projet qui s’appelle gesdem:

gesdem/conf/trac.ini

Le paramétrage du projet se fait également par la page d’administration. Ce paramétrage va mettre à jour le fichier trac.ini mais également la base de données pour certains plugins.

Développement dans le projet dédié

Nous entendons par développement tout simplement la modification des templates. Trac possède une notion d’héritage de configuration ainsi on peut mettre des templates dans le projet dédié qui vont écraser ceux par défaut.

Développement dans Trac lui-même

Si les sources de Trac sont dans un répertoire nommé trac-0.12 alors il faut se rendre dans le répertoire suivant pour trouver les modules python et les templates à modifier:

trac-0.12trac

Développement dans les plugin

Les plugins peuvent nécessiter des ajustements. Dans ce cas il faut télécharger les sources et prendre soin de au moins créer/modifier le fichier setup.cfg pour ajouter un suffixe remarquable puisque nous allons faire un fork. Voici un setup.cfg minimal:

[egg_info]

tag_build = monTagAMoi
tag_svn_revision = true

Remplacer monTagAMoi par le nom de votre organisation par exemple !

Personnalisation de Trac: gestion des optgroup

Trac est outils de gestion de projet de développement logiciel qui permet de suivre non seulement les bug et evolutions mais aussi d’avoir un wiki et un blog. Écrit en python il dispose de nombreux plugins.

La version officielle courante 0.12 ne gère malheureusement pas les groupements des éléments dans les listes déroulantes. Or il est bien pratique de grouper par thème les éléments de la liste “component” par exemple.

En regardant attentivement le code de ticket.html on s’aperçoit que le code script Genshi semble prévoir la génération de optgroup mais le bean de vue qui est dans la page n’est jamais généré pour ce cas. Pour y remédier il faut modifier les fichiers model.py et api.py qui se trouvent dans le répertoire

/trac/ticket

La fichier model.py devra être modifié pour pouvoir générer les “value object” correspondant aux éléments de la liste component, en y ajoutant l’information de groupement. Dans la classe Component il suffit de faire une jointure avec la table enum sur une nouvelle colonne de la table component. Dans mon cas particulier il s’agit de la colonne type_dem (voir le fichier diff joint)

Modification trac pour gérer les optgroup

Ensuite dans le fichier api.py il faut modifier la méthode fields de la classe TicketSystem afin de gérer la création du bean de vue adéquat i.e. incluant les informations d’optgroup. En fait il faut distinguer dans le cas d’un champ de type select, le cas particulier de la combo des Components. Si le module model.py est entièrement revisité pour renvoyer pour tous les champs une information de groupement alors la distinction n’est pas nécessaire.

Enfin la version 0.12 de Trac utilise le champ options pour contenir les valeurs posssible d’une liste déroulante. Or si on remplit également ce champ, ticket.html va produire à la fois des options classiques et des optgroup… Pour faire simple j’ai choisi de supprimer le test des valeurs possible dans web_ui.py

Avec un minimum de connaissance python, il est possible d’apporter de petites modifications à Trac qui se révèle être un outils qui gagne à être connu.