martes, 19 de noviembre de 2024

Python Ejecutar un fichero sh (bash de linux) que utilice un entorno virtual y ejecute un programa python. Ojo con los parámetros tipo "regex pattern"!!!

1. Introducción

Pasos:

  1. Desde el programa python creamos un array que contenga la ruta del script (bash de linux) como primer componente. Cada componente restante estará formada por la cadena "clave=valor", donde las comillas dobles forman parte del parámetro. Ojo los espacios en blanco pueden dar problemas, para ello se pueden sustituir por algúna cadena , en mi caso he utilizado "¬spc¬"(sin colillas dobles)
  2. Desde el script de linux verificamos que recibimos obligatoriamente los parámetros "folder", "module","funcion" y "venv". Siendo "folder" la carpeta donde tenemos que situarnos, "venv" la carpeta dentro de folder donde está el entrono virtual, "module" el módulo a llamar y "function" la función del módulo a ejecutar. En el script cambiamos a la carpeta definida en la variable "folder" y activamos el entorno virtual de la variable "venv" y ejecutamos  pyton xmexec.py "module=miModulo" "function=miFuncion" ..... 
  3. El módulo xmexec.py que se encuentra en la carpeta "folder" se encarga de recibir los parámetros, arreglar los mismos sustituyendo "¬spc¬" por espacios en blanco, cargando el módulo indicado en el parámetro "module" y ejecutando la función indicada  "function".

2. Llamada al script desde python

Para ello supongamos que tenemos un diccionario con el nombre del script, del módulo, función a ejecutar, entorno virtual y otros parámetros.
Sustituimos los espacios en blanco por "¬spc¬" y construimos el array para llamar a "subproces.run".
Observar que el modulo se puede definir como:
  • "carpeta.modulo" ó
  • "carpeta/modulo.py"
siendo "carpeta" la carpeta que contien a "modulo.py" que vamos a llamar

El script a llamar está en la carpeta "static/scripts" y se llama "execShell.sh"


import subprocess

# Diccionario que contiene los parámetros PARA EL SHELL SCRIPT
execDict={
	'shell':'static/scripts/execShell.sh',
	'folder':'../02.llibreries',
	'venv':'/static/scripts/llibreries.sh',
	'module':'eni/xmexpeni.py',
	#'module':'eni.xmexpeni',
	'function':'expedientENI',
	'param1':'1814787N',
	'param2':12,
	'param3':'TD02',
	'param4':'INDEX_ACTES_PLE',
	'param5':'ENIExpTemplate.xml',
}

if __name__ == '__main__':
	# Recogemos la ruta del shell y lo quitamos del diccionario
	myShell=execDict['shell']
	del execDict['shell']
	execProgs=[myShell]
	
	#Creamos la cadena con los parámetros paraejecuar
	for key,value in execDict.items():
		value1=value.replace(" ","¬spc¬")
		st=f'"{key}={value1}"'
		execProgs.append(st) #Exec progs.

	print(str(subprocess.run(execProgs, capture_output=True)))


3. Llamada al programa xmexec.

Desde el script vamos a llamar al programa pyhon xmexec, paa ello:
  1. Verificamos que recibimos los parámetros "folder", "venv", module" y "function"
  2. Nos situamos a la carpeta del parámetro "folder"
  3. Activamos el entorno virtual de la carpeta indicada en el parámetro "venv"
  4. Ejecutamos "python" al que pasamos el propgrama xmexec.py y el resto de parámetros (excepto "venv" y "folder") 
#!/bin/bash

#--------------------------
# 0. Exemple de crida
# ./static/scripts/llibreries.sh \
#  "folder=../02.llibreries" "venv=venv02" \
#  "module=eni.xmexpeni" "function=expedientENI" \
#  "docsFolderPath=docs/actes2022/" \
#  "templatesPath=templates/ENI/" \
#  "filter=*ord*aprov*sio*_signed.pdf" \
#  "anyPle=2022" \
#  "expCodi=1814787N" \
#  "organo=L01462384" \
#  "tDocu=TD02" \
#  "prefPle=INDEX_ACTES_PLE" \
#  "plantillaXML=ENIExpTemplate.xml" \
#  "docxIndex=indexTemplate.docx"
#
#
# O també podriem executar en background
# Run the Python script in the background
#  nohup python myscript.py > /dev/null 2>&1 &
#
#---------------------------

remove_quotes() {
    local arg="$1"
	# Remove leading double quote if present
    arg="${arg#\"}"
	# Remove leading double quote if present
    arg="${arg%\"}"
    echo "$arg"
}

#------------------------------------
# 1. Recogemos los parametros
#------------------------------------

# Inicialize variables
folder_value=""
venv_value=""
module_value=""
other_params=""

# Función para mostrar uso del script
usage() {
  echo "Uso: $0 'folder=RUTA'  'venv=carpeta entorno virtual' 'module=carpeta.modulo.py'  ... otros_parametros=VALOR ..."
  echo "Ejemplo:"
  echo "  $0 folder=/mi/carpeta env=producción venv=venv02 module=eni.xmexpeni config=ejemplo version=1.0"
  exit 1
}

# Verificar si se proporcionaron argumentos
if [ $# -lt 3 ]; then
  usage
fi

# Procesar cada argumento
for argIni in "$@"; do
  # Remover comillas
  arg=$(remove_quotes "$argIni")
  
  # Verificar si el argumento contiene un '='
  if [[ "$arg" == *=* ]]; then
    # Dividir el argumento en clave y valor
    key="${arg%%=*}"
    value="${arg#*=}"
	#echo "$key    AAAA   $value  AAAAA  $arg"
	
    case "$key" in
      folder)
        folder_value="$value"
        ;;
      venv)
        venv_value="$value"
        ;;
	  module) 
        module_value="$arg"
		;;
      *)
        # Concatenar otros parámetros
        other_params+=" \"$arg\""
        ;;
    esac
	#echo "$key    BBBBB   $value"
  else
    echo "Argumento inválido: $arg"
    usage
  fi
done

#echo "FOLDER=  $folder_value"
#echo "VENV= $venv_value"
#echo "MODULE= $module_value"

# Verificar que 'folder' y 'env' hayan sido proporcionados
if [[ -z "$folder_value" || -z "$venv_value" || -z "$module_value" ]]; then
  echo "Error: Se requieren al menos un argumento 'folder' 'venv'y 'module'."
  usage
fi


#--------------------------------------
# 2. Nos cambiamos a la carpeta del modulo
#-------------------------------------
pwd
cd "$folder_value"
pwd

#--------------------------------------
# 3. Activamos el entorno virtual
#-------------------------------------
source "$venv_value"/bin/activate


# Crear la cadena concatenada
final_string1="python xmexec.py \"${module_value}\" ${other_params}"
echo "--------------------------------------"
echo "FINAL STRING=$final_string1"
echo "--------------------------------------"
python xmexec.py \"${module_value}\" ${other_params}


4. Verificar que la carpeta del entrono virtual "venv" está creado dentro de la carpteta "folder"


5. Crear el programa python "xmexec.py" en la crpeta folder.

Este programa:
  1. Recoge los argumentos y cambia "¬spc¬ por espacio " "
  2. Carga el módulo "module" y
  3. Ejecuta la función con el diccionario que recoge todos los parámetrso menos "module" y "function" 
'''
Execute a function from a module
paramenters:
	"module= module name"
	"function= function name"		
	"param1 = value1"
	"param2 = value2"
	"param_n = value_n"

'''
import importlib
import sys

requiredProps=['module','function']

def arreglaArg(arg:str)->str:
	if arg.startswith('"') or arg.startswith("'"):
		arg=arg[1:] 
	if arg.endswith('"') or arg.endswith("'"): 
		arg=arg[:-1]
	arg=arg.replace('¬spc¬',' ')	
	return arg

if __name__ == '__main__':
	argDict={}
	for i, arg1 in enumerate (sys.argv):
		arg=arreglaArg(arg1)
		#print(i, "--", arg1, '===', arg)
		if i>0:
			aArg=arg.split('=')
			#print(aArg)
			argDict[aArg[0]]=aArg[1]
			

	keys=argDict.keys()
	for prop in requiredProps:
		if prop not in keys:
			raise Exception (prop + ' not in arguments')
		
	moduleName=argDict['module'].replace('.py','').replace("/",".")
	#print (moduleName)
	#module = __import__(argDict['module']) # No acaba d'anar be
	module = importlib.import_module(moduleName)
	#print (module)
	aFunc=argDict['function']
	func = getattr(module, aFunc)
	
	# Remove module and function props
	for prop in requiredProps: del argDict[prop]


	# Execute the function with arguments
	#print ("executing the function "+ aFunc)
	#print (argDict)
	#print ("=======================================================")
	func (**argDict)
	

6. Verificaciones

Verificar que :
  1. Exista el módulo dentro de la carpeta correcta
  2. Que el módulo tenga la funcion que vamos a llamar
  3. Que los parámetros de la fucnión tengan los mismos nombres y sean del mismo tipo que los parámetros que le pasamos.

7. Parámetros regex pattern


Si los parámetros "regex pattern" pueden estar definidos en un fichero u¡yaml o dentro del programa python

Por ejemplo en este fichero yaml hay 2 elementos tipo pattern.

pattern1: '^Ple \d{2} ord \d{2}-\d{2}-\d{4} .* \d{2} \d{2} \d{4}(?:_|\.)signed(?:_|\.)signed\.pdf$'

params: [1, '^Acta d{2} .* \d{2} \d{2} \d{4}.pdf$', 'otro valor']

Para el caso de der definidos en un progama python vale la pena  utilizar un string "rau" que se hace  anteponiendo r al string

pattern = r'^Acta d{2} .* \d{2} \d{2} \d{4}.pdf$'


Los posibles problemas que pueden aparecer son:
  1. Las interrogaciones "?"
  2. Los espacios en blanco
Las interrogaciones pueden dar problemas al convertirlas en una cadena dela URL, mientras que los espacios en blanco pueden dar problemas al pasarlos a un script como parámetro, pues los trocea. 

Para el caso de las interrogaciones, se puede pasar el parámetro codificado en base64 y a veces aún así puede fallar. A un caso se pueden cambiar por "¬qm¬" y luego deshacer el cambio. Para el caso de los espacios hemos optado por sustituirlos por "¬spc¬".
 



No hay comentarios :

Publicar un comentario