Patrón de complemento para la aplicación genérica de Python -- python campo con plugin camp codereview Relacionados El problema

Plugin Pattern for Generic Python Application


5
vote

problema

Español

Resumen

Estoy experimentando con un patrón de complemento para una aplicación genérica de Python (no necesariamente web, escritorio o consola) que permitiría que los paquetes se reduzcan en una carpeta de plugin que se utilizarán de acuerdo con el contrato que deberían seguir. En mi caso, este contrato es simplemente tener una función llamada do_plugin_stuff() . Me gustaría que el patrón tenga sentido para un sistema que vende complementos como la tienda de plugin en WordPress.

Mecanismo mínimo de plugin de Python es una pregunta decente (a pesar de tener 4 años) Con una muy buena discusión sobre Django (que no he usado) y cómo permite que se instale un complemento en cualquier lugar a través de PIP. Vería que como una fase dos, porque parece que un patrón de complemento a base de PIP es (la generalización de barrido probablemente no siempre es cierta) más valiosa para la tienda de plugin puramente gratis (como en dinero). Si los complementos gratuitos (como en código abierto) se venden por dinero en una tienda, si parece que PIP sería una mala elección para la instalación porque las personas pueden pagar por algo que son para obtener la fuente Código para y use libremente / Redistribuir, es poco probable que paguen / donan por algo que ya han instalado.

Estructura del proyecto

Plugin Project Arche Tree

Código

es también en github bajo mi mismo nombre de usuario ( palumacil ) y hice una Etiqueta de liberación de V1.0.0 para congelar el repo en el código que se muestra a continuación.

aplicación / complementos / blog / __ init __. Py

  def do_plugin_stuff():     print("I'm a blog!")   

aplicación / complementos / tostadora / __ init __. Py

  def do_plugin_stuff():     print("I'm a toaster!")   

app / plugins / __ init __. Py

  (empty)   

app / __ init __. Py

  from importlib import import_module from os import path, listdir   def create_app():     app = Application()     plugin_dir = path.join(path.dirname(__file__), 'plugins')      import_string_list = [''.join(['.plugins.', d]) for d                           in listdir(plugin_dir)                           if path.isdir(path.join(plugin_dir, d))                           and not d.startswith('__')]      print(str(len(import_string_list)) + " imports to do...")      for import_string in import_string_list:         module = import_module(import_string, __package__)         app.plugins.update({module.__name__.split('.')[2]: module})      print(str(len(app.plugins)) + " plugins in the app")     return app   class Application:     def __init__(self):         self.plugins = {}   

La línea not d.startswith('__') Eliminé mi Pychache Dir de Pycharm.

run.py

  from app import create_app from pprint import PrettyPrinter   app = create_app()  app.plugins['toaster'].do_plugin_stuff()  printer = PrettyPrinter(indent=4) printer.pprint(app.plugins.__repr__())   

Puntos de revisión

Soy lo suficientemente nuevo para Python (muy nuevo, pero proveniente de un fondo de C # decente, y leí PEP8 antes de intentar esto) que nunca he escrito una solicitud de Python 2. Creo que mi método de importación requiere Python 3.3 o 3.4, aunque no estoy seguro. Comentario sobre esto podría ser agradable. Las formas de hacer que este código sea accesible a versiones anteriores de Python parecen estar desordenadas; Involucran importaciones condicionales y tales, que son verbosos y feos. Si hay un truco o dos que harían mi código mejor para diferentes versiones de Python con Minimal Cruft, eso sería genial verlo.

¡Estoy perdiendo cualquier cosa que haga que mi código sea mucho más verboso de lo que debería ser? Por ejemplo, estoy iterando dos veces a través de los directorios, una vez para hacer una lista de los paquetes, y nuevamente para hacer de mi diccionario. ¿Sería más limpio hacer ambas partes un bucle? La alternativa de un bucle parece verbosa, pero podría haber mejoras adicionales, tal vez:

  # Alternative to current code which uses a single loop: for d in listdir(plugin_dir):     if path.isdir(path.join(plugin_dir, d)) and not d.startswith('__'):         module = import_module(''.join(['.plugins.', d]), __package__)         app.plugins.update({module.__name__.split('.')[2]: module})   

es module.__name__.split('.')[2] Una forma frágil de obtener el valor para mi diccionario de plugin? [-1] sería un mejor índice para usar en el resultado de la división?

Estoy teniendo problemas para entender por qué podría elegir usar pkgutil.iter_modules en lugar de mi enfoque, pero me pregunto si podría haber algún beneficio. Parece que se basa en def do_plugin_stuff(): print("I'm a blog!") 0 desde Python 3.3 ( PEP 302 ). ¿Sería la única diferencia que no tiraría una carpeta que no tenga un 998877766555443311

Original en ingles

Summary

I am experimenting with a plugin pattern for a generic Python application (not necessarily web, desktop, or console) which would allow packages dropped into a plugin folder to be used according to the contract they would need to follow. In my case, this contract is simply to have a function called do_plugin_stuff(). I'd like the pattern to make sense for a system that sells plugins like the plugin store in Wordpress.

Minimal Python plugin mechanism is a decent question (despite being 4 years old) with some very good discussion about Django (which I haven't used) and how it allows for a plugin to be installed anywhere via pip. I'd see that as a phase two, because it seems like a pip-based plugin pattern is (sweeping generalization probably not always true) most valuable for purely free (as in money) plugin store. If free (as in open source) plugins are sold for money in a store, if seems that pip would be a poor choice for installation because which people might pay for something they're about to get the source code for and use freely / redistribute, they might be unlikely to pay / donate for something they've already installed.

Project Structure

plugin project file tree

Code

It's also on GitHub under my same username (PaluMacil) and I made a release tag of v1.0.0 to freeze the the repo at the code shown below.

app/plugins/blog/__init__.py

def do_plugin_stuff():     print("I'm a blog!") 

app/plugins/toaster/__init__.py

def do_plugin_stuff():     print("I'm a toaster!") 

app/plugins/__init__.py

(empty) 

app/__init__.py

from importlib import import_module from os import path, listdir   def create_app():     app = Application()     plugin_dir = path.join(path.dirname(__file__), 'plugins')      import_string_list = [''.join(['.plugins.', d]) for d                           in listdir(plugin_dir)                           if path.isdir(path.join(plugin_dir, d))                           and not d.startswith('__')]      print(str(len(import_string_list)) + " imports to do...")      for import_string in import_string_list:         module = import_module(import_string, __package__)         app.plugins.update({module.__name__.split('.')[2]: module})      print(str(len(app.plugins)) + " plugins in the app")     return app   class Application:     def __init__(self):         self.plugins = {} 

The line not d.startswith('__') eliminated my pychache dir from Pycharm.

run.py

from app import create_app from pprint import PrettyPrinter   app = create_app()  app.plugins['toaster'].do_plugin_stuff()  printer = PrettyPrinter(indent=4) printer.pprint(app.plugins.__repr__()) 

Points for Review

I'm new enough to Python (very new but coming from a decent C# background, and I read PEP8 before attempting this) that I've never written a Python 2 application. I think my method of importing requires Python 3.3 or 3.4, though I'm not certain. Commentary on this might be nice. Ways of making this code accessible to earlier versions of Python seem to be messy; they involve conditional imports and such, which are verbose and ugly. If there is a trick or two that would make my code better for different versions of Python with minimal cruft, that would be great to see.

Am I missing anything that makes my code much more verbose than it should be? For instance, I'm iterating twice through the directories--once to make a list of packages, and again to make my dictionary. Would it be cleaner to make both parts one loop? The one-loop alternative seems verbose, but there could be further improvements, perhaps:

# Alternative to current code which uses a single loop: for d in listdir(plugin_dir):     if path.isdir(path.join(plugin_dir, d)) and not d.startswith('__'):         module = import_module(''.join(['.plugins.', d]), __package__)         app.plugins.update({module.__name__.split('.')[2]: module}) 

Is module.__name__.split('.')[2] a fragile way to get the value for my plugin dictionary? Would [-1] be a better index to use on the result of the split?

I'm having trouble understanding why I might chose to use pkgutil.iter_modules instead of my approach, but I'm wondering if there might be some benefit. It seems to be based on importlib since Python 3.3 (PEP 302). Would the only difference be that I wouldn't pull in a folder that doesn't have an __init__.py inside it to make it a package?

     
     
     

Lista de respuestas

2
 
vote
vote
La mejor respuesta
 

No debe llamar str en el int devuelto de len , en su lugar, use str.format .

  "{} plugins in the app".format(len(app.plugins))   

Formato coincidirá con la cadena INT a una cadena implícita y se lea más.

También está llamando repr hacia atrás. El punto completo de un objeto que tiene una función 998877665554433665544336 permite que se pase un objeto a repr() . Para que pueda cambiar app.plugins.__repr__() a repr(app.plugins) .

 

You shouldn't call str on the int returned from len, instead use str.format.

"{} plugins in the app".format(len(app.plugins)) 

Format will coerce the int to a string implicitly and is neater to read.

Also you're calling repr backwards. The whole point of an object having a __repr__ function is that it allows an object to be passed to repr(). So you could change app.plugins.__repr__() to repr(app.plugins).

 
 

Relacionados problema

12  Escanear un directorio para los complementos y cargarlos  ( Scanning a directory for plugins and loading them ) 
Estoy trabajando en una herramienta de diccionario simple, con una clase base que puede extenderse por complementos para representar diferentes diccionarios. ...

2  Revisa mi primer complemento de control deslizante jquery  ( Review my first jquery slider plugin ) 
Este es mi primer plugin jQuery. Es un deslizador simple que requiere muy poco marcas en HTML. Funciona para mis propósitos, pero no soy un experto en jquery ...

2  COMPLEMENTO DE DISCUMACIÓN DE JQUERY  ( Jquery highlightnavigation plugin ) 
He escrito mi primer complemento de jquery usando uno de los patrones de diseño sugeridos en el sitio de jquery: http: // docs.jquery.com/plugins/authoring ....

2  Generando dinámicamente las clases / código de deserialización XML: Parte I, Lectura  ( Dynamically generating xml deserialization classes code part i reading ) 
Estoy construyendo un complemento IntelliJ que permitirá a una persona seleccionar un archivo XML (e incluso un fragmento de XML en el archivo, si lo les gust...

6  Girery plugin para administrar los formularios  ( Jquery plugin to manage forms ) 
Acabo de crear mi primer plugin jquery. Estoy buscando sugerencias sobre cómo mejorarlo, lo que lleva a una optimización de código. $("#par01par01").click(...

2  Mi primer plugin jquery - HeftyBox  ( My first jquery plugin heftybox ) 
Estoy empezando con mi primer complemento de jquery oficial, y solo estoy girando ahora buscando la entrada. Quiero saber lo que estoy haciendo mal o bien has...

3  Complemento Dropdown Jquery  ( Jquery dropdown plugin ) 
Estoy buscando consejos o consejos de OOP. $db = new db; $db->query(array( 'select' => 1, 'from' => 'table', 'where' => array('id' => 1, 'name' => 'char...

5  Creando un plugin de pestañas  ( Creating a tab plugin ) 
Estoy creando un plugin de pestañas. Quiero saber si hay una mejor manera de hacer esto o si lo que tengo es bueno. Funciona bien, pero puede haber algunos at...

1  Paginación con datos estáticos  ( Pagination with static data ) 
Estoy usando el siguiente código para implementar la funcionalidad de paginación. Todos los artículos están procesados ​​en la carga de la página y seconds::...

12  Extjs Plugin de cuadrícula  ( Extjs grid plugin ) 
El código a continuación es un complemento que escribí para Ext.grid.GridPanel , que básicamente le permite tener un poco más de control sobre cómo las filas...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos