--- author: '' category: '' date: 2010/10/01 15:12 description: '' link: '' priority: '' slug: BB923 tags: open source, programming, pyqt, pyqtbyexample, python, qt title: 'Modularizando tu aplicación: Yapsy' type: text updated: 2010/10/01 15:12 url_type: '' --- Una manera (si estás programando en Python) es usar `Yapsy `_.. Yapsy es asombroso. También, carece completamente de documentación entendible. Veamos si este post arregla un poco esa parte y deja sólo lo asombroso. **Update:** No había visto la documentación nueva de Yapsy. Es mucho mejor que la que había antes :-) Esta es la idea general con yapsy: * Creás un Plugin Manager que puede encontrar y cargar plugins de una lista de lugares (por ejemplo, de ["/usr/share/appname/plugins", "~/.appname/plugins"]). * Una categoría de plugins es una clase. * Hay un mapeo entre nombres de categoría y clases de categoría. * Un plugin es un módulo y un archivo de metadata. El módulo define una clase que hereda de una clase de categoría, y pertenece a esa categoría. El archivo de metadata tiene cosas como el nombre del plugin, la descripción, la URL, versión, etc. Una de las mejores cosas de Yapsy es que *no especifica demasiado*. Un plugin va a ser simplemente un objeto Python, podés poner lo que quieras ahí, o lo podés limitar definiendo la intefaz en la clase de categoría. De hecho, lo que vengo haciendo con las clases de categoría es: * Arranco con una clase vacía * Implemento dos plugins de esa categoría * Los pedazos en común los muevo dentro de la categoría. Pero créanme, esto va a ser mucho más claro con un ejemplo :-) Lo voy a hacer con una aplicación gráfica en PyQt, pero Yapsy funciona igual de bien para aplicaciones "headless" o para líneas de comando. Comencemos con algo simple: un editor HTML con un widget preview. .. figure:: //ralsina.me/static/yapsy/editor1.jpeg Un editor simple con preview Este es el código de la aplicación, que es realmente simple (no puede guardar archivos ni nada interesante, es sólo un ejemplo): .. topic:: editor1.py .. code-block:: python from PyQt4 import QtCore, QtGui, QtWebKit import os, sys class Main(QtGui.QWidget): def __init__(self): QtGui.QWidget.__init__(self) self.layout = QtGui.QVBoxLayout() self.editor = QtGui.QPlainTextEdit() self.preview = QtWebKit.QWebView() self.layout.addWidget(self.editor) self.layout.addWidget(self.preview) self.editor.textChanged.connect(self.updatePreview) self.setLayout(self.layout) def updatePreview(self): self.preview.setHtml(self.editor.toPlainText()) def main(): # Again, this is boilerplate, it's going to be the same on # almost every app you write app = QtGui.QApplication(sys.argv) window=Main() window.show() # It's exec_ because exec is a reserved word in Python sys.exit(app.exec_()) if __name__ == "__main__": main() .. Admonition:: Nota De ahora en más los listados no incluyen la función main porque no cambia. Pero esta aplicación tiene un obvio límite: hay que escribir HTML! Por qué no escribir python y que lo muestre resaltado en HTML? O markup de Wiki! O reStructured text! Uno podría, en principio, implementar todos esos modos, pero estás asumiendo la responsabilidad de soportar cada cosa-que-se-convierte-en-HTML. Tu aplicación sería un monolito. Ahí entra Yapsy. Creemos entonces una categoría de plugins, llamada "Formatter" que toma texto plano y devuelve HTML. Después agreguemos cosas en la UI para que el usuario pueda elegir que formatter usar, e implementemos un par. Esta es la clase de categoría de plugins: .. topic:: categories.py .. code-block:: python class Formatter(object): """Plugins of this class convert plain text to HTML""" name = "No Format" def formatText(self, text): """Takes plain text, returns HTML""" return text Por supuesto que no sirve de nada sin plugins! Asi que creemos un par. Primero, un plugin qye toma código python y devuelve HTML, usando pygments. .. topic:: plugins/pygmentize.py .. code-block:: python from pygments import highlight from pygments.lexers import PythonLexer from pygments.formatters import HtmlFormatter from categories import Formatter class Pygmentizer(Formatter): name = "Python Code" def formatText(self, text): return highlight(text, PythonLexer(), HtmlFormatter(full=True)) Como ven, eso va en una carpeta plugins. Después le decimos a Yapsy que busque los plugins ahi adentro. Para ser reconocido como un plugin, necesita metadata: .. topic:: plugins/pygmentize.yapsy-plugin .. code-block:: ini [Core] Name = Python Code Module = pygmentize [Documentation] Author = Roberto Alsina Version = 0.1 Website = //ralsina.me Description = Highlights Python Code Y realmente, eso es **todo** lo que hay que hacer para hacer un plugin. Acá hay otro para comparar, que usa docutils para formatear reStructured Text: .. topic:: plugins/rest.py .. code-block:: python from categories import Formatter import docutils.core import docutils.io class Rest(Formatter): name = "Restructured Text" def formatText(self, text): output = docutils.core.publish_string( text, writer_name = 'html' ) return output .. topic:: plugins/rest.yapsy-plugin .. code-block:: ini [Core] Name = Restructured Text Module = rest [Documentation] Author = Roberto Alsina Version = 0.1 Website = //ralsina.me Description = Formats restructured text Y acá están en acción: .. figure:: //ralsina.me/static/yapsy/editor2.jpeg reSt mode .. figure:: //ralsina.me/static/yapsy/editor3.jpeg Python mode Of course using categories you can do things like a "Tools" category, where the plugins get added to a Tools menu, too. Este es el código del lado de la aplicación: .. topic:: editor2.py .. code-block:: python from categories import Formatter from yapsy.PluginManager import PluginManager class Main(QtGui.QWidget): def __init__(self): QtGui.QWidget.__init__(self) self.layout = QtGui.QVBoxLayout() self.formattersCombo = QtGui.QComboBox() self.editor = QtGui.QPlainTextEdit() self.preview = QtWebKit.QWebView() self.layout.addWidget(self.formattersCombo) self.layout.addWidget(self.editor) self.layout.addWidget(self.preview) self.editor.textChanged.connect(self.updatePreview) self.setLayout(self.layout) # Create plugin manager self.manager = PluginManager(categories_filter={ "Formatters": Formatter}) self.manager.setPluginPlaces(["plugins"]) # Load plugins self.manager.locatePlugins() self.manager.loadPlugins() # A do-nothing formatter by default self.formattersCombo.addItem("None") self.formatters = {} print self.manager.getPluginsOfCategory("Formatters") for plugin in self.manager.getPluginsOfCategory("Formatters"): print "XXX" # plugin.plugin_object is an instance of the plugin self.formattersCombo.addItem(plugin.plugin_object.name) self.formatters[plugin.plugin_object.name] = plugin.plugin_object def updatePreview(self): # Check what the current formatter is name = unicode(self.formattersCombo.currentText()) text = unicode(self.editor.toPlainText()) if name in self.formatters: text = self.formatters[name].formatText(text) self.preview.setHtml(text) Resumiendo: es fácil, y te lleva a mejorar la estructura interna de tu aplicación y terminás con *mejor* código. `Código fuente de todo `_.