Una catena di montaggio per Plone…con progetti, rulli e lavoratori.
Ho deciso di recensire il prodotto collective.transmogrifier perchè l’ho trovato geniale.
Questo, ormai famoso, prodotto fa molto poco in realtà…anzi non fa proprio niente!
Allora perchè è diventato così famoso? Semplice, perchè basandosi su di esso si può fare tutto.
Racconterò questo prodotto come tutti, attraverso la metafora della catena di montaggio, aggiungendo dettagli della mia esperienza.
Credevo fosse stupida come metafora prima di conoscere il prodotto, ma in effetti è l’unica che rende l’idea.
WHO: I primi contributori sono stati i norvegesi di Jarn . Tra gli altri contributori c’è anche Martin Aspeli (autore della bibbia di Plone).
WHEN: Il progetto è stato rilasciato per la prima volta nel Agosto del 2009.
WHAT: In pratica l’intuizione geniale che sta dietro al prodotto è:
- io transmogrifier sono la fabbrica e ti metto a disposizione una catena di montaggio
- mi assicuro di mettere i lavoratori al loro posto e di ternerli in fila
- mi assicuro che lavorino!
- mi prendo carico di passare gli oggetti sui rulli da un lavoratore all’altro
- te li restituisco alla fine della catena.
Il bello è che transmogrifier non si interessa minimamente di cosa debbano fare i singoli lavoratori. L’unico vincolo, per essere un buon lavoratore è che una volta preso un oggetto dalla catena, lo modifichi e poi lo rimetti giù (e chi rompe paga
)
Ci sono alcune convenzioni date più dal buon senso:
- ci si aspetta che i primi lavoratori (1 o n) producano gli oggetti (sorgenti) prendendoli da qualche parte (es. pagine web, file locali, databases..etc);
- ci si aspetta, ma non è necessario, che i lavoratori centrali modifichino gli oggetti;
- ci si aspetta che gli ultimi lavoratori vadano a posizionare gli oggetti nelle posizioni finali (es. filesystem, contenuti plone..)
Questa è la fabbrica. Ora manca un progetto da realizzare e i lavoratori per realizzarlo.
Qui entrano in gioco rispettivamente le pipeline e i blueprints.
Le pipeline sono in tutto e per tutto simili ai file di configurazione *.cfg di un buildout. C’è una stanza principale transmogrifier:
[transmogrifier]
pipeline =
...
source
...
output
dove è definita la sola variabile pipeline. La variabile pipeline indica l’ordine e il tipo di “lavoratori” che dovranno interagire. Ogni lavoratore viene definito in una stanza apposita:
[transmogrifier]
pipeline =
...
source
getpage
...
output
[getpage]
blueprint = miopacchetto.transmogrifier.getpage
Ogni stanza-lavoratore deve avere una variabile blueprint corrispondente ad una named-utility, più altri parametri che verranno automaticamente passati al lavoratore in un comodo array.
Nel file configure.zcml, per registrare la mia nuova pipeline aggiungo:
La named utility di ogni lavoratore va definita sempre nel file configure.zcml così:
dove component fa riferimento alla classe python che implementa il lavoratore e name è il name univoco che assegno all’utility per essere trovata.
Infine vado a implementare il mio lavoratore, nel file blueprints.py (come dichiarato nel component della utility):
from zope.interface import classProvides, implements from collective.transmogrifier.interfaces import ISectionBlueprint from collective.transmogrifier.interfaces import ISection class GetPage(object): classProvides(ISectionBlueprint) implements(ISection) def __init__(self, transmogrifier, name, options, previous): self.previous = previous def __iter__(self): for item in self.previous: item['attributo']) = "Ciao" yield item |
Questa è per la maggior parte una boilerplate. Cioè la prendi, la copi/incolli e cambi solo le righe fra l’inizio del ciclo for e l’istruzione yield.
Approposito di yield! Io onestamente non conoscievo l’uso di questa parolina magica, ma è la base di tutto il meccanismo. La funzione di questa keyword infatti è interrompere l’iterazione corrente, restituire l’oggetto e salvare lo stato attuale. Nella metafora, il lavoratore ha finito di modificare l’oggetto, lo rimette sul rullo, si tira indietro e aspetta l’arrivo del prossimo oggetto.
Nel metodo __init__ c’è un parametro options: è quel famoso array automagicamente creato dai parametri dichiarati nella stanza corrispondente nella pipeline (vedi sopra).
Direi che ora i pezzi ci sono tutti. L’albero dei file del nostro pacchetto dovrebbe essere più o meno così:
miopacchetto.transmogrifier ├── docs │ └── HISTORY.txt ├── miopacchetto │ ├── __init__.py │ └── transmogrifier │ ├── blueprints.py │ ├── configure.zcml │ ├── miopacchetto_view.py │ ├── __init__.py │ ├── interfaces.py │ ├── pipeline.cfg │ └── profiles │ └── default │ ├── browserlayer.xml │ └── metadata.xml ├── README.txt ├── setup.cfg └── setup.py
I file direttamente coinvolti sono:
pipeline.cfg: dove definisco la mia pipeline
configure.zcml: dove registro la pipeline e dove dichiaro le named-utility
blueprints.py: dove implemento le classi dei lavoratori
Cosa manca? Un punto di lancio! Una bella browser view, nel configure.zcml aggiungiamo:
E nel file miopacchetto_view.py ci sarà:
from Products.Five import BrowserView from collective.transmogrifier.transmogrifier import Transmogrifier class MiopacchettoView(BrowserView): def __init__(self, context, request): self.context = context self.request = request def __call__(self): transmogrifier = Transmogrifier(self.context) transmogrifier(u"miopacchetto.exampleconfig") # la stringa deve corrispondere al nome che ho dato # nella registrazione della pipeline return "Transmogrificazione completata!" |
Fatto!
Un nota negativa che non ho trovato scritto da nessuna parte in internet ma che è importante: i lavoratori quando rimettono l’oggetto sul rullo si dimenticano di come era fatto e non sanno quali e quanti verranno dopo. Tradotto, se si ha necessità di correlare gli oggetti tra loro non c’è modo con una singola pipeline. Soluzione: fare 2 pipeline in serie!
La prima produce gli oggetti e li posiziona, la seconda li prende dove sono stati messi e li correla tra loro.
FINAL NOTES:
- si lo sò….transmogrifier è un nome orrendo!
- le named utility possono essere anche dichiarate direttamente nel file python dove sono le classi:
>from zope.component import provideUtility provideUtility(GetPage, name=u"miopacchetto.transmogrifier.getpage") |
Un prodotto davvero divertente e flessibile. Provare per credere!