Les composants Zope 3
Ici vous allez apprendre les bases de l'architecture base sur les composants de Zope 3. Les mots clés : adaptateur, utilitaire.
Ce texte est une traduction libre d'une partie du livre "Zope Guide" écrit par une communauté de développeur Zope. L'originial est disponible sur : http://kpug.zwiki.org/ZopeGuide. Conformément à la licence du texte original (je déteste la CC pour ça...), je dois citer l'auteur, qui est : "Zope 3 Distilled Contributors (http://kpug.zwiki.org/ZopeGuideContributors)". Notons que malgrès l'attribution de la licence, ce texte reviens vraiment à Baiju M. qui en est le véritable auteur.
La page originale du texte (en Anglais) ce trouve sur : http://kpug.zwiki.org/ZopeGuideComponents
| Author: | Zope 3 Distilled Contributors (http://kpug.zwiki.org/ZopeGuideContributors) |
| Version: | 0.1.7 |
| Date: | 19 septembre 2006 |
| Copyright: | Creative Commons Attribution-ShareAlike 2.5 License |
| Traduction: | Benjamin Poulain < ikipou AT (@) gmail.com > |
Introduction
Les composants sont des objets réutilisables, ils assurent de ce fait une grande cohésion et un couplage lâche. L'idée de base est la même que pour les types de données, un composant fournit une interface. L'architecture de composants de Zope 3 se structure autour de deux types de composants de base, les utilitaires et les adaptateurs.
Utilitaire
Un composant utilitaire peut être retrouvé par son interface ou par son nom. Si aucun nom n'est fournit, la recherche par défaut se fait avec une chaîne vide.
Regardez à cette interface IHost (une version modifiée du chapitre précédent):
>>> from zope import interface
>>> class IHost(interface.Interface):
... """Un objet hôte"""
...
... def goodmorning():
... """Dit bonjour"""
Et voici l'implémentation de hôte :
>>> class Host(object):
... interface.implements(IHost)
...
... def __init__(self, guest=u''):
... self.guest = guest
...
... def goodmorning(self):
... """Dit bonjour"""
...
... return "Bonjour, %s!" % self.guest
Ensuite enregistez une instance de cette classe à l'aide de zope.component.provideUtility:
>>> from zope import component
>>> jack = Host('Jack')
>>> component.provideUtility(jack)
Maintenant vous avez enregistré un composant utilitaire. C'est un simple utilitaire avec une méthode goodmorning. Quand vous appelez goodmorning, vous obtenez en retour "Bonjour, Jack!". Maintenant vous pouvez cherchez tous les utilitaires fournissant l'interface IHost. Il y a deux fonctions pour la recherche d'utilitaire : component.queryUtility et component.getUtility. Vous pouvez rechercher les composants utilitaire de cette façon :
>>> host = component.queryUtility(IHost)
>>> host.goodmorning()
'Bonjour, Jack!'
Il est aussi possible de spécifier une interface et un nom lors de l'enregistrement d'un utilitaire.
>>> jill = Host('Jill')
>>> component.provideUtility(jill, IHost, 'seulement jill')
>>> component.queryUtility(IHost, 'seulement jill').goodmorning()
'Bonjour, Jill!'
Il y a aussi la fonction getUtility qui lève l'exception ComponentLookupError lorsque la recherche n'aboutit pas. Voyez l'exemple suivant pour comparer les deux méthodes :
>>> component.queryUtility(IHost, 'anonymous')
>>> component.queryUtility(IHost, 'anonymous', 'not found')
'not found'
>>> component.getUtility(IHost, 'anonymous')
... # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ComponentLookupError: (<InterfaceClass ...IHost>, 'anonymous')
Si un troisième argument est fournit, queryUtility le retourne si la recherche échoue, sinon la fonction retourne None. Il n'y a pas de troisième argument à getUtility, il échoue si la recherche échoue en levant une exception.
Dans Zope 3 vous utiliserez généralement ZCML pour l'enregistrement des utilitaires plutôt que d'utiliser component.provideUtility. Dans cette exemple, nous supposerons qu'il y a un package zguide et qu'il y a un module interfaces.py pour la définition de IHost et un module host.py pour son implémentation.
La directive de configure.zcml s'écrit comme ceci :
<utility
component="zguide.host.Host"
provides="zguide.interfaces.IHost"
name="seulement jill"
/>
Adaptateurs
Les adaptateurs peuvent être considérés commes les composants "colle" de l'architecture. Les adaptateurs de Zope 3 sont une utilisation directe du design pattern adaptateur, il sert à connecter une interface avec une autre ce qui rend possible l'utilisation de technique avancé de programmation tel que la programmation orienté aspect. Un adaptateur prend un composant présentant une interface et l'utilise pour fournir une seconde interface. Ce mécanisme permet au développeur de diviser les fonctionnalités en petit morceaux d'API et ainsi de garder le contrôle de la complexité.
Nous allons de nouveau utilisé notre interface IHost :
>>> class IHost(interface.Interface):
... """Un objet hôte"""
...
... def goodmorning():
... """Dit bonjour"""
Et voici une interface IGuest et une implémentation de IHost:
>>> class IGuest(interface.Interface):
... name = interface.Attribute("Nom")
>>> class Host(object):
...
... component.adapts(IGuest)
... interface.implements(IHost)
...
... def __init__(self, guest):
... self.guest = guest
...
... def goodmorning(self):
... return "Bonjour, %s!" % self.guest.name
Remarquez que la fonction component.adapts fait de cette classe un adapteur. Ici Host implémente l'interface IHost et adapte l'interface IGuest. Donc si vous avez un objet fournissant l'interface IGuest, vous pouvez demander un adapteur qui fournit IHost. Pour faire une tel demande dans Zope vous devez enregistrer la classe Host à l'aide de la fonction component.provideAdapter :
>>> component.provideAdapter(Host)
Maintenant, créons une implémentation de IGuest et instancions le :
>>> class Guest(object):Maintenant vous pouvez obtenir un adapteur pour l'interface IHost :
... interface.implements(IGuest)
...
... def __init__(self, name):
... self.name = name
>>> jack = Guest("Jack")
>>> component.queryAdapter(jack, IHost).goodmorning()
'Bonjour, Jack!'
Ou directement:
>>> IHost(jack).goodmorning()
'Bonjour, Jack!'
Vous voyez que on peut diviser les méthodes sur un composant selon sont utilisation grâce aux adapteurs.
Maintenant voyons une autre implémentation de IHost, c'est un autre type d'hôte qui dit bonjour d'une autre façon:
>>> class NiceHost(object):
... interface.implements(IHost)
...
... def __init__(self, guest):
... self.guest = guest
...
... def goodmorning(self):
... return "Salut %s! Bonne journée!" % self.guest.name
>>> component.provideAdapter(NiceHost, [IGuest], IHost, 'gentil')
On voit que le processus de recherche des adapteurs s'apparente à celle des utilitaire:
>>> component.queryAdapter(jack, IHost).goodmorning()
'Bonjour, Jack!'
>>> component.queryAdapter(jack, IHost, '').goodmorning()
'Bonjour, Jack!'
Mais comme pour les utilitaires il est possible de spécifier le nom explicitement:
>>> component.queryAdapter(jack, IHost, 'nice').goodmorning()
'Salut Jack! Bonne journée!'
>>> component.getAdapter(jack, IHost, 'nice').goodmorning()
'Salut Jack! Bonne journée!'
Un adapteur peut aussi adapter plus d'une interface:
>>> class IRoom(interface.Interface):
... number = interface.Attribute("Numéro de chambre")
>>> class SuperHost(object):
... component.adapts(IGuest, IRoom)
... interface.implements(IHost)
...
... def __init__(self, guest, room):
... self.guest = guest
... self.room = room
...
... def goodmorning(self):
... return "Bonjour, %s! Votre numéro de chambre est %s" % (self.guest.name,
... self.room.number)
>>> component.provideAdapter(SuperHost)
>>> class Room(object):
... interface.implements(IRoom)
...
... def __init__(self, number):
... self.number = number
>>> room14 = Room(14)
>>> component.queryMultiAdapter((jack, room14),
... IHost).goodmorning()
'Bonjour, Jack! Votre numéro de chambre est 14'
Plus sur les adapteurs
Adaptateur d'inscription:
>>> class IGuard(interface.Interface):
...
... def check():
... """Vérifie les visiteurs"""
>>> class SecurityGuard(object):
... component.adapts(IGuest)
... interface.implements(IGuard)
...
... def __init__(self, guest):
... self.guest = guest
...
... def check(self):
... return "%s, montrez moi votre carte s'il vous plaît" % self.guest.name
>>> component.provideSubscriptionAdapter(SecurityGuard)
>>> component.subscribers([jack], IGuard)[0].check()
'Jack, montrez moi votre carte s'il vous plaît'
Inscription aux évenements:
>>> import datetime
>>> class IGuestArrived(interface.Interface):
... guest = interface.Attribute("L'invité arrivé")
>>> class GuestArrived(object):
... interface.implements(IGuestArrived)
...
... def __init__(self, guest):
... self.guest = guest
>>> @component.adapter(IGuestArrived)
... def guestArrived(event):
... event.guest.arrived = datetime.datetime.utcnow()
>>> component.provideHandler(guestArrived)
>>> component.handle(GuestArrived(jack))
>>> jack.arrived.__class__.__name__
'datetime'

