Cómo crear un «plugin» - UltraTesla/UTesla GitHub Wiki
El proyecto Ultra Tesla cuenta con una herramienta llamada UTeslaCLI que está pensada para aumentar la eficiencia tanto en el mantenimiento como en la creación de la infraestructura de la red. Al igual que UTesla con los servicios, UTeslaCLI cuenta con «plugins» que permiten ampliarlo.
El siguiente «plugin» es una prueba para demostrar lo fácil que puede ser crearlos.
modules/Cmd/hello_world/hello_world-config.py
information = {
"description" : "Mostrar texto por pantalla",
"commands" : [
{
"positionals" : [
{
"args" : ("text",),
"help" : "El texto a mostrar"
}
]
}
],
"version" : "1.0.0"
}
modules/Cmd/hello_world/hello_world.py
def MainParser(args):
print("Texto:", args.text)
Ejecución:
python3.8 ./UTeslaCLI hello_world 'Hello World!'
Texto: Hello World!
Antes de hacer nada es necesario crear una carpeta con el nombre del «plugin» en la carpeta raíz donde residan los «plugins» (modules/Cmd, por defecto, según config/UTesla.ini).
mkdir 'modules/Cmd/<Nombre del plugin>'
Ejemplo:
mkdir modules/Cmd/hello_world
Ahora se deben crear dos archivos de Python dentro de esa carpeta con el mismo nombre de ella.
touch 'modules/Cmd/<Nombre del plugin>/<Nombre del plugin>-config.py'
touch 'modules/Cmd/<Nombre del plugin>/<Nombre del plugin>.py'
El primero, es un archivo de configuración que indica los parámetros y opciones generales; el segundo, es el «plugin» en sí.
La razón por la que se crean archivos separados es:
- Como se hacen cosas diferentes, se separan.
- Disminuye el uso de memoria y se usa el requerido, además que la carga es muy eficiente de esa manera, ya que al principio se carga sólo el archivo de configuración y cuando en verdad se requiere, el código del «plugin».
Pero aún no ha concluido con la operación.
Vamos a escribir primero el archivo de configuración:
modules/Cmd/<Nombre del plugin>/<Nombre del plugin>-config.py
information = {}
En realidad esa es la única información que se necesita para que UTeslaCLI lo pueda aceptar con un «plugin» válido, pero si queremos interacción con el usuario necesitamos definir un poco más de información como una descripcion, las opciones aceptadas y opcionalmente el número de la versión del «plugin» y si se requiere, alguna que otra opción que UTeslaCLI procese.
information = {
"Description" : "<Descripción de lo que hace el plugin>",
"commands" : [
{
"Nombre del grupo" : [
{
# Parámetros de argparse
}
]
}
],
"version" : "Versión del plugin"
}
Internamente en UTeslaCLI se usa argparse para procesar todas las opciones en la clave commands por lo que es recomendable leer la documentación de él para explotar todo el potencial: https://docs.python.org/3/library/argparse.html
Ahora necesitaremos el código del mismísimo «plugin».
def MainParser(args):
pass
Debe contener una función llamada MainParser y debe recibir un argumento; el valor del argumento serán los argumentos según la configuración y según lo que el usuario haya enviado, además de las opciones generales de UTeslaCLI.
Otras opciones que tal vez puedan sonar interesantes o puedan necesitarse en determinadas situaciones pueden ser las siguientes:
- persist: Acepta un booleano o un número entre 0 y 1. Le indica a UTeslaCLI si debería ejecutar un bucle de eventos hasta que se presione CTRL-C; útil cuando se usan aplicativos que requieren de un uso extendido como el «plugin» shareKeys que ejecuta un servidor HTTP.
- workspaces: Acepta una lista. Le indica a UTeslaCLI los espacios de trabajos a usar durante la ejecución del plugin.
Si sé es observador se podrá notar que la mayoría de veces hemos separado muchas partes para trabajar de forma independiente, los espacios de trabajo son otra manera de separar el código, en este caso, módulos.
Aquí se hace referencia a cualquier módulo o librería de Python que se pueda importar de la siguiente manera:
import <Módulo o librería>
Simplemente con una importación debería bastar, pero para que eso funcione debemos indicarle a UTeslaCLI qué carpetas usar como espacios de trabajos. Hay tres maneras de hacerlo y cada una de ellas tendrá su prioridad:
- Creando una carpeta con sufijo "-workspace": En la creación del «plugin» tenemos la opción de agregar dentro de la carpeta en donde se encuentra una carpeta llamada igual al «plugin» más un sufijo con el nombre "-workspace". P. ej: modules/Cmd/hello_world/hello_world-workspace.
- Indicando en la configuración del «plugin»: Colocando cuantas rutas deseemos en la clave workspaces podemos indicarle a UTeslaCLI los espacios de trabajos
- Usando la opción --path en UTeslaCLI: Ajustando tantas veces la opción --path podemos indicarle a UTeslaCLI los espacios de trabajo que deseamos que se incluyan en el PATH de Python.
En la sección anterior se pudo observar cómo crear espacios de trabajo de una manera sencilla, pero hay que saber la prioridad que tiene cada uno al agregarse al PATH de Python.
Primero UTeslaCLI toma en cuenta los espacios de trabajo configurados en el archivo de configuración del «plugin» porque puede ser algo más intencional que crear una carpeta con el mismo nombre que el «plugin» más un sufijo (-workspace) o en otras palabras, un desarrollador de un «plugin» debe tener más control sobre éste a la hora de crearlo y puede que la intención real sean los archivos de configuración.
information = {"workspaces" : ["<Ruta del espacio de trabajo>", "<Otro espacio de trabajo>", "<...>"]}
En este punto UTeslaCLI tomaría en cuenta las carpetas llamadas con el mismo nombre que el «plugin» sumando el sujifo "-workspaces".
mkdir modules/Cmd/<Nombre del plugin>/<Nombre del plugin>-workspace
Ahora como puede no ser tan prioritario se usa la opción "--path" en UTeslaCLI para indicar cuáles espacios de trabajo usar. Este es de prioridad baja porque podría volver inestable al «plugin» en caso de importar un módulo o librería que no contiene funciones, clases y demás cosas para poder funcionar correctamente.
python3.8 ./UTeslaCLI --path "<Ruta del espacio de trabajo>" --path "<Otro espacio de trabajo>" --path "<...>" <Nombre del plugin> <Opciones del plugin>
Dependiendo de los archivos que contengan esos espacios de trabajo es lo que podrá importar el «plugin». Veamos un ejemplo.
Siguiendo con el ejemplo hello_world de la primera sección vamos a agregar una funcionalidad interesante y así le sacamos provecho a los espacios de trabajos.
Primero agregamos la funcionalidad al «plugin»:
modules/Cmd/hello_world/hello_world.py
import printing
def MainParser(args):
print("Texto:", printing.transform(args.text))
/tmp/printing.py
def transform(text):
"""Convierte cada letra en mayúsculas y minúsculas"""
buff = []
flag = True
for i in text:
if (flag):
flag = False
buff.append(i.upper())
else:
flag = True
buff.append(i.lower())
return ''.join(buff)
Si ejecutamos el plugin UTeslaCLI indicaría que no pudo encontrar el módulo que intentas importar mostrando una excepción, pero no hay de que preocuparse porque hay solución.
Usando la opcion "--path":
Como se indicó en anteriores secciones usando la opción "--path" podremos usar los espacios de trabajo:
python3.8 ./UTeslaCLI --path /tmp hello_world 'Hello World!'
Se obtendría:
Texto: HeLlO WoRlD!
Usando la carpeta más el sufijo "-workspace":
Esta es otra manera de hacer lo mismo, pero a diferencia de la anterior tiene más prioridad.
Vamos a crear nuevamente el módulo "printing" pero con un sutil cambio:
def transform(text):
"""Convierte cada letra en mayúsculas y minúsculas"""
buff = []
flag = False
for i in text:
if (flag):
flag = False
buff.append(i.upper())
else:
flag = True
buff.append(i.lower())
return ''.join(buff)
Simplemente se le cambió el valor a la variable flag.
Ahora cuando ejecutamos de nuevo el «plugin» pero con la carpeta ya creada y a la misma vez usando la opción "--path" en UTeslaCLI veamos qué obtenemos:
python3.8 ./UTeslaCLI --path /tmp hello_world 'Hello World!'
Texto: hElLo wOrLd!
Una salida diferente, es porque no toma en cuenta el printing que se encuentra en "/tmp" dado por la opción --path.
Usando la clave "workspaces" en el archivo de configuración:
Esta es la prioridad más alta que puede haber. Vamos a usar las tres opciones juntas. Primero creamos otra carpeta donde estará otro módulo con el mismo nombre pero con una funcionalidad agregada:
mkdir /tmp/other
Y allí agregamos nuestro nuevo módulo:
def transform(text):
"""Convierte cada letra en mayúsculas y minúsculas y le agrega un espacio"""
buff = []
flag = True
for i in text:
if (flag):
flag = False
buff.append(i.upper())
else:
flag = True
buff.append(i.lower())
buff.append(" ")
return ''.join(buff)
Hace lo mismo que el de la anterior sección pero le agrega un espacio a cada letra.
Ahora lo agregamos en el archivo de configuración:
information = {
"description" : "Mostrar texto por pantalla",
"commands" : [
{
"positionals" : [
{
"args" : ("text",),
"help" : "El texto a mostrar"
}
]
}
],
"version" : "1.0.0",
"workspaces" : ["/tmp/other"]
}
Y ejecutamos:
python3.8 ./UTeslaCLI --path /tmp hello_world 'Hello World!'
Texto: H e L l O W o R l D !
Hay que tener las prioridades en cuenta a la hora de crear un «plugin». Es recomendable:
- Usar la opción --path para probar funcionalidades que son nuevas y quizá no vayan a ser usadas en la versión actual del «plugin».
- Usar la carpeta más el sufijo "-workspace" cuando los módulos o librerías a usar sean exclusivos de ese «plugin».
- Usar la clave workspaces cuando se requiera compartir código con otros «plugins» usando carpetas compartidas.
Hay una pequeña funcionalidad en UTeslaCLI pero que es más semántica que otra cosa en sí. Las carpetas compartidas con usadas para usar módulos o librerías de otros «plugins» con el fin de no repetir un código una y otra vez o simplemente usar funcionalidades iguales en contextos diferentes.
Las carpetas compartidas tienen como sufijo "-shared" y se encuentran en "modules/Cmd/-shared". Al contrario de una carpeta corriente, UTeslaCLI ignora este tipo de carpetas.
Son muy útiles cuando se combinan con la clave "workspaces" en el archivo de configuración, de hecho, se pueden ver ejemplos en los «plugins» pre-fabricados.
Hay un nombre especial para un diccionario que es pasado junto con los argumentos en MainParser: __OPTIONS__; el cual es usado para dar un poco más dinamismo al «plugin». Por ejemplo, un «plugin» puede usar también los espacios de trabajo para leer un archivo HTML, como un índice de una página, sin embargo, éste hace uso de la opción __OPTIONS__ para darle más control al usuario de sí mismo.
Usando el último ejemplo del «plugin» vamos a modificarlo para imprimir todos los espacios de trabajos que se usan:
def MainParser(args):
print(args.__OPTIONS__["workspaces"])
Esto imprimiría:
python3.8 ./UTeslaCLI --path /tmp hello_world 'Hello World!'
['/tmp/other', 'modules/Cmd/hello_world/hello_world-workspace', '/tmp']
Entonces el «plugin» puede decir qué carpeta usar, pero como esto puede ser algo difícil de decidir se usa un índice:
def MainParser(args):
workspaces = args.__OPTIONS__["workspaces"]
index = args.__OPTIONS__["index"]
print(workspaces[index])
Al ejecutar:
python3.8 ./UTeslaCLI --path /tmp hello_world 'Hello World!'
/tmp/other
Se imprime la carpeta de trabajo, pero... «¿quién decide qué carpeta usar?»: El usuario.
Usando la opción --index (que por defecto tiene 0 como valor) se puede decir qué carpeta usar, como por ejemplo:
python3.8 ./UTeslaCLI --index 0 --path /tmp hello_world 'Hello World!'
/tmp/other
python3.8 ./UTeslaCLI --index 1 --path /tmp hello_world 'Hello World!'
modules/Cmd/hello_world/hello_world-workspace
python3.8 ./UTeslaCLI --index 2 --path /tmp hello_world 'Hello World!'
/tmp
Como se puede observar ahora hay más control en el «plugin», pero... «¿qué pasaría si se selecciona un índice inválido?»:
python3.8 ./UTeslaCLI --index -1 --path /tmp hello_world 'Hello World!'
/tmp/other
python3.8 ./UTeslaCLI --index -10 --path /tmp hello_world 'Hello World!'
/tmp/other
python3.8 ./UTeslaCLI --index -100 --path /tmp hello_world 'Hello World!'
/tmp/other
python3.8 ./UTeslaCLI --index 3 --path /tmp hello_world 'Hello World!'
/tmp
python3.8 ./UTeslaCLI --index 100 --path /tmp hello_world 'Hello World!'
/tmp
UTeslaCLI se daría cuenta de lo que trata de hacer el usuario y tomaría medidas en contra de ello. Si se usa un índice menor a los que están configurados, se usa el índice 0 y si se usa un índice mayor, se usa el último espacio de trabajo.
Un «plugin» que hace uso de estas funcionalidades es shareKeys.