proyectos ejemplos python architecture plugins

ejemplos - Construyendo una arquitectura mínima de plugins en Python



django (15)

Ampliando la respuesta de @ edomaur, puedo sugerir que eche un vistazo a simple_plugins (plug desvergonzado), que es un marco de plugin simple inspirado en el trabajo de Marty Alchin .

Un breve ejemplo de uso basado en el README del proyecto:

# All plugin info >>> BaseHttpResponse.plugins.keys() [''valid_ids'', ''instances_sorted_by_id'', ''id_to_class'', ''instances'', ''classes'', ''class_to_id'', ''id_to_instance''] # Plugin info can be accessed using either dict... >>> BaseHttpResponse.plugins[''valid_ids''] set([304, 400, 404, 200, 301]) # ... or object notation >>> BaseHttpResponse.plugins.valid_ids set([304, 400, 404, 200, 301]) >>> BaseHttpResponse.plugins.classes set([<class ''__main__.NotFound''>, <class ''__main__.OK''>, <class ''__main__.NotModified''>, <class ''__main__.BadRequest''>, <class ''__main__.MovedPermanently''>]) >>> BaseHttpResponse.plugins.id_to_class[200] <class ''__main__.OK''> >>> BaseHttpResponse.plugins.id_to_instance[200] <OK: 200> >>> BaseHttpResponse.plugins.instances_sorted_by_id [<OK: 200>, <MovedPermanently: 301>, <NotModified: 304>, <BadRequest: 400>, <NotFound: 404>] # Coerce the passed value into the right instance >>> BaseHttpResponse.coerce(200) <OK: 200>

Tengo una aplicación, escrita en Python, que es utilizada por un público bastante técnico (científicos).

Estoy buscando una buena manera de hacer que la aplicación sea extensible por los usuarios, es decir, una arquitectura de scripting / plugin.

Estoy buscando algo extremadamente liviano . La mayoría de las secuencias de comandos, o complementos, no van a ser desarrolladas y distribuidas por un tercero e instaladas, sino que serán algo acelerado por un usuario en unos pocos minutos para automatizar una tarea repetitiva, agregar soporte para un formato de archivo, etc. Por lo tanto, los complementos deben tener el código repetitivo mínimo absoluto, y no requieren ''instalación'' más que copiar en una carpeta (por lo que algo como setuptools puntos de entrada, o la arquitectura del complemento Zope parece demasiado).

¿Ya hay sistemas como este, o cualquier proyecto que implemente un esquema similar al que debería buscar ideas / inspiración?


Como otro acercamiento al sistema de complementos, puede verificar el proyecto Extenderme .

Por ejemplo, definamos la clase simple y su extensión

# Define base class for extensions (mount point) class MyCoolClass(Extensible): my_attr_1 = 25 def my_method1(self, arg1): print(''Hello, %s'' % arg1) # Define extension, which implements some aditional logic # or modifies existing logic of base class (MyCoolClass) # Also any extension class maby be placed in any module You like, # It just needs to be imported at start of app class MyCoolClassExtension1(MyCoolClass): def my_method1(self, arg1): super(MyCoolClassExtension1, self).my_method1(arg1.upper()) def my_method2(self, arg1): print("Good by, %s" % arg1)

Y trata de usarlo:

>>> my_cool_obj = MyCoolClass() >>> print(my_cool_obj.my_attr_1) 25 >>> my_cool_obj.my_method1(''World'') Hello, WORLD >>> my_cool_obj.my_method2(''World'') Good by, World

Y muestra lo que está escondido detrás de la escena:

>>> my_cool_obj.__class__.__bases__ [MyCoolClassExtension1, MyCoolClass]

La biblioteca extend_me manipula el proceso de creación de clase a través de metaclases, por lo tanto en el ejemplo anterior, cuando creamos una nueva instancia de MyCoolClass obtuvimos una instancia de nueva clase que es la subclase de ambas MyCoolClassExtension y MyCoolClass tienen la funcionalidad de ambas, gracias a la herencia múltiple de Python

Para un mejor control sobre la creación de clase, hay pocas metaclases definidas en esta lib:

  • ExtensibleType - permite una extensibilidad simple mediante subclases

  • ExtensibleByHashType : similar a ExtensibleType, pero que tiene la capacidad de crear versiones especializadas de clase, lo que permite la extensión global de la clase base y la extensión de versiones especializadas de la clase

¡Esta lib se usa en OpenERP Proxy Project , y parece estar funcionando lo suficientemente bien!

Para un ejemplo real de uso, busque en la extensión ''field_datetime'' de OpenERP Proxy :

from ..orm.record import Record import datetime class RecordDateTime(Record): """ Provides auto conversion of datetime fields from string got from server to comparable datetime objects """ def _get_field(self, ftype, name): res = super(RecordDateTime, self)._get_field(ftype, name) if res and ftype == ''date'': return datetime.datetime.strptime(res, ''%Y-%m-%d'').date() elif res and ftype == ''datetime'': return datetime.datetime.strptime(res, ''%Y-%m-%d %H:%M:%S'') return res

Record aquí es un objeto extesible. RecordDateTime es extensión.

Para habilitar la extensión, solo importa el módulo que contiene la clase de extensión y (en el caso anterior) todos los objetos de Record creados después tendrán clase de extensión en las clases base, teniendo así toda su funcionalidad.

La principal ventaja de esta biblioteca es que, el código que opera objetos extensibles, no necesita saber acerca de la extensión y las extensiones podrían cambiar todo en objetos extensibles.


Cuando busco decoradores de Python, encontré un fragmento de código simple pero útil. Puede no encajar en tus necesidades pero muy inspirador.

Sistema de registro de complementos Scipy Advanced Python #

class TextProcessor(object): PLUGINS = [] def process(self, text, plugins=()): if plugins is (): for plugin in self.PLUGINS: text = plugin().process(text) else: for plugin in plugins: text = plugin().process(text) return text @classmethod def plugin(cls, plugin): cls.PLUGINS.append(plugin) return plugin @TextProcessor.plugin class CleanMarkdownBolds(object): def process(self, text): return text.replace(''**'', '''')

Uso:

processor = TextProcessor() processed = processor.process(text="**foo bar**, plugins=(CleanMarkdownBolds, )) processed = processor.process(text="**foo bar**")


Disfruté de la agradable discusión sobre diferentes arquitecturas de complementos dada por el Dr. Andre Roberge en Pycon 2009. Ofrece una buena visión general de las diferentes formas de implementar complementos, comenzando desde algo realmente simple.

Está disponible como un podcast (segunda parte después de una explicación de parche de mono) acompañado de una serie de seis entradas de blog .

Recomiendo darle una escucha rápida antes de tomar una decisión.



El mío es, básicamente, un directorio llamado "complementos" que la aplicación principal puede sondear y luego usar imp.load_module para buscar archivos, buscar un punto de entrada conocido, posiblemente con parámetros de configuración a nivel de módulo, e ir desde allí. Utilizo material de monitoreo de archivos para una cierta cantidad de dinamismo en el que los complementos están activos, pero eso es bueno tenerlo.

Por supuesto, cualquier requisito que se presente al decir "No necesito [algo grande y complicado] X, solo quiero algo ligero" corre el riesgo de volver a implementar X un requisito descubierto a la vez. Pero eso no quiere decir que no puedas divertirte haciéndolo de todos modos :)


En realidad, setuptools funciona con un "directorio de complementos", como el siguiente ejemplo tomado de la documentación del proyecto: http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

Ejemplo de uso:

plugin_dirs = [''foo/plugins''] + sys.path env = Environment(plugin_dirs) distributions, errors = working_set.find_plugins(env) map(working_set.add, distributions) # add plugins+libs to sys.path print("Couldn''t load plugins due to: %s" % errors)

A largo plazo, setuptools es una opción mucho más segura ya que puede cargar complementos sin conflictos o sin requisitos.

Otra ventaja es que los complementos pueden extenderse utilizando el mismo mecanismo, sin que las aplicaciones originales tengan que preocuparse por ello.


He dedicado tiempo a leer este hilo mientras buscaba un framework de complemento en Python de vez en cuando. He usado algunos pero había defectos con ellos. Esto es lo que se me ocurre para su análisis en 2017, un sistema de administración de complementos sin acoplamiento y sin acoplamiento: cárguenme más tarde . Aquí hay tutorials sobre cómo usarlo.


He pasado mucho tiempo tratando de encontrar un sistema de complemento pequeño para Python, que se ajuste a mis necesidades. Pero luego pensé: si ya hay una herencia, que es natural y flexible, ¿por qué no usarla?

El único problema con el uso de herencia para complementos es que no se sabe cuáles son las clases de plugins más específicas (la más baja en el árbol de herencia).

Pero esto podría resolverse con metaclass, que realiza un seguimiento de la herencia de la clase base, y posiblemente podría crear una clase, que hereda de la mayoría de los complementos específicos (''Root extended'' en la figura siguiente)

Entonces vine con una solución codificando una metaclase de este tipo:

class PluginBaseMeta(type): def __new__(mcls, name, bases, namespace): cls = super(PluginBaseMeta, mcls).__new__(mcls, name, bases, namespace) if not hasattr(cls, ''__pluginextensions__''): # parent class cls.__pluginextensions__ = {cls} # set reflects lowest plugins cls.__pluginroot__ = cls cls.__pluginiscachevalid__ = False else: # subclass assert not set(namespace) & {''__pluginextensions__'', ''__pluginroot__''} # only in parent exts = cls.__pluginextensions__ exts.difference_update(set(bases)) # remove parents exts.add(cls) # and add current cls.__pluginroot__.__pluginiscachevalid__ = False return cls @property def PluginExtended(cls): # After PluginExtended creation we''ll have only 1 item in set # so this is used for caching, mainly not to create same PluginExtended if cls.__pluginroot__.__pluginiscachevalid__: return next(iter(cls.__pluginextensions__)) # only 1 item in set else: name = cls.__pluginroot__.__name__ + ''PluginExtended'' extended = type(name, tuple(cls.__pluginextensions__), {}) cls.__pluginroot__.__pluginiscachevalid__ = True return extended

Por lo tanto, cuando tenga la base Root, hecha con metaclass y tenga un árbol de complementos que herede de ella, podría obtener automáticamente la clase, que hereda de los complementos más específicos mediante la subclasificación:

class RootExtended(RootBase.PluginExtended): ... your code here ...

La base de código es bastante pequeña (~ 30 líneas de código puro) y tan flexible como lo permita la herencia.

Si está interesado, involúcrese @ https://github.com/thodnev/pluginlib


Llegué aquí en busca de una arquitectura de complementos mínima y encontré muchas cosas que me parecieron exageradas. Por lo tanto, he implementado complementos de Super Simple Python . Para usarlo, puede crear uno o más directorios y colocar un archivo especial __init__.py en cada uno. La importación de esos directorios hará que todos los demás archivos de Python se carguen como submódulos, y sus nombres se colocarán en la lista __all__ . Entonces depende de usted validar / inicializar / registrar esos módulos. Hay un ejemplo en el archivo README.


Si bien esa pregunta es realmente interesante, creo que es bastante difícil de responder, sin más detalles. ¿Qué tipo de aplicación es esta? ¿Tiene una GUI? ¿Es una herramienta de línea de comandos? Un conjunto de scripts? Un programa con un punto de entrada único, etc.

Dada la poca información que tengo, responderé de manera muy genérica.

¿Qué significa que tienes que agregar complementos?

  • Probablemente tengas que agregar un archivo de configuración, que listará las rutas / directorios para cargar.
  • Otra forma sería decir "se cargarán los archivos en ese plugin / directorio", pero tiene el inconveniente de requerir que los usuarios muevan los archivos.
  • Una última opción intermedia sería requerir que todos los complementos estén en el mismo complemento / carpeta, y luego activarlos / desactivarlos usando rutas relativas en un archivo de configuración.

En un código puro / práctica de diseño, tendrá que determinar claramente qué comportamiento / acciones específicas desea que extiendan sus usuarios. Identifique el punto de entrada común / un conjunto de funcionalidades que siempre serán anuladas, y determine los grupos dentro de estas acciones. Una vez hecho esto, debería ser fácil extender su aplicación,

Ejemplo de uso de ganchos , inspirado en MediaWiki (PHP, pero, ¿el lenguaje realmente importa?):

import hooks # In your core code, on key points, you allow user to run actions: def compute(...): try: hooks.runHook(hooks.registered.beforeCompute) except hooks.hookException: print(''Error while executing plugin'') # [compute main code] ... try: hooks.runHook(hooks.registered.afterCompute) except hooks.hookException: print(''Error while executing plugin'') # The idea is to insert possibilities for users to extend the behavior # where it matters. # If you need to, pass context parameters to runHook. Remember that # runHook can be defined as a runHook(*args, **kwargs) function, not # requiring you to define a common interface for *all* hooks. Quite flexible :) # -------------------- # And in the plugin code: # [...] plugin magic def doStuff(): # .... # and register the functionalities in hooks # doStuff will be called at the end of each core.compute() call hooks.registered.afterCompute.append(doStuff)

Otro ejemplo, inspirado en mercurial. Aquí, las extensiones solo agregan comandos al ejecutable de línea de comandos de hg , extendiendo el comportamiento.

def doStuff(ui, repo, *args, **kwargs): # when called, a extension function always receives: # * an ui object (user interface, prints, warnings, etc) # * a repository object (main object from which most operations are doable) # * command-line arguments that were not used by the core program doMoreMagicStuff() obj = maybeCreateSomeObjects() # each extension defines a commands dictionary in the main extension file commands = { ''newcommand'': doStuff }

Para ambos enfoques, es posible que necesite una inicialización y finalización comunes para su extensión. Puede usar una interfaz común que toda su extensión deberá implementar (se ajusta mejor con el segundo enfoque, mercurial utiliza un repositorio (ui, repo) que se llama para todas las extensiones), o usar un enfoque tipo gancho, con un hooks.setup hook.

Pero, de nuevo, si quieres respuestas más útiles, tendrás que limitar tu pregunta;)


Soy un biólogo retirado que se ocupó de micrograficos digitales y se vio obligado a escribir un paquete de procesamiento y análisis de imágenes (no técnicamente una biblioteca) para ejecutar en una máquina SGi. Escribí el código en C y usé Tcl para el lenguaje de scripting. La GUI, tal como estaba, se hizo usando Tk. Los comandos que aparecían en Tcl eran de la forma "nombre-extensión-mandatoNombre arg0-arg1 ... param0-param1 ...", es decir, palabras y números simples separados por espacios. Cuando Tcl vio la subcadena "extensionName", el control pasó al paquete C. Eso a su vez ejecutaba el comando a través de un lexer / analizador (hecho en lex / yacc) y luego llamaba a las rutinas C según fuera necesario.

Los comandos para operar el paquete se podían ejecutar uno por uno a través de una ventana en la GUI, pero los trabajos por lotes se realizaban editando archivos de texto que eran scripts Tcl válidos; usted elegiría la plantilla que hizo el tipo de operación a nivel de archivo que quería hacer y luego editaría una copia para contener el directorio real y los nombres de archivo más los comandos del paquete. Funcionó a las mil maravillas. Hasta ...

1) El mundo recurrió a las PC y 2) los scripts obtuvieron más de 500 líneas, cuando las capacidades organizacionales dudosas de Tcl comenzaron a convertirse en un inconveniente real. El tiempo pasó ...

Me retiré, Python se inventó y parecía el sucesor perfecto de Tcl. Ahora, nunca he hecho el puerto, porque nunca he enfrentado los desafíos de compilar (bastante grandes) programas C en una PC, extender Python con un paquete C y hacer GUI en Python / Gt? / Tk? /? ?. Sin embargo, la vieja idea de tener scripts de plantillas editables parece factible. Además, no debería ser una gran carga para ingresar comandos de paquete en un formulario nativo de Python, por ejemplo:

packageName.command (arg0, arg1, ..., param0, param1, ...)

Algunos puntos adicionales, parens y comas, pero esos no son increíbles.

Recuerdo haber visto que alguien había hecho versiones de Lex y Yacc en Python (prueba: http://www.dabeaz.com/ply/ ), por lo que si aún se necesitan, están cerca.

El punto de este divagamiento es que me ha parecido que Python es el extremo delantero "liviano" deseado por los científicos. Tengo curiosidad por saber por qué piensas que no es así, y lo digo en serio.

agregado más tarde: La aplicación gedit anticipa que se agregarán complementos y su sitio tiene la explicación más clara de un procedimiento de complemento simple que he encontrado en unos minutos de mirar alrededor. Tratar:

https://wiki.gnome.org/Apps/Gedit/PythonPluginHowToOld

Todavía me gustaría entender mejor tu pregunta. No estoy seguro de si 1) quiere que los científicos puedan usar su aplicación (Python) simplemente de varias maneras o 2) desea permitir que los científicos agreguen nuevas capacidades a su aplicación. La opción n. ° 1 es la situación que enfrentamos con las imágenes y que nos llevó a usar scripts genéricos que modificamos para adaptarlos a la necesidad del momento. ¿Es la Elección n. ° 2 lo que te lleva a la idea de los complementos, o es algún aspecto de tu aplicación lo que hace que la emisión de comandos sea impracticable?



setuptools tiene un EntryPoint :

Los puntos de entrada son una forma sencilla para que las distribuciones "anuncien" objetos de Python (como funciones o clases) para el uso de otras distribuciones. Las aplicaciones y marcos extensibles pueden buscar puntos de entrada con un nombre o grupo particular, ya sea de una distribución específica o de todas las distribuciones activas en sys.path, y luego inspeccionar o cargar los objetos publicitados a voluntad.

AFAIK este paquete está siempre disponible si usas pip o virtualenv.


module_example.py :

def plugin_main(*args, **kwargs): print args, kwargs

loader.py :

def load_plugin(name): mod = __import__("module_%s" % name) return mod def call_plugin(name, *args, **kwargs): plugin = load_plugin(name) plugin.plugin_main(*args, **kwargs) call_plugin("example", 1234)

Sin duda es "mínima", no tiene ningún error de comprobación, probablemente innumerables problemas de seguridad, no es muy flexible, pero debe mostrar lo simple que puede ser un sistema de complemento en Python.

También es probable que desee examinar el módulo imp , aunque puede hacer mucho con __import__ , os.listdir y algunas manipulaciones de cadena.