-
Auto-instalar dependencias de python al ejecutar un script
Muchas veces, al ejecutar un script de Python, este pretende importar paquetes que puede que no estén instalados previamente en el entorno virtual o en el sistema. Con este truco, al poner en la misma carpeta el script depinstall.py que os dejo a continuación, se pueden declarar las dependencias en el script principal de manera que el propio script principal use este otro script para instalar esas dependencias.
Este es el código del script depinstall.py:
#!/usr/bin/env python3 import importlib.util import subprocess import sys # Paquete apt necesario para instalar paquetes python cNombreDelPaqueteApt = "python3-pip" def fPaqueteAptEstaInstalado(pNombreDelPaqueteApt): """Verifica si un paquete apt está instalado.""" vResultado = subprocess.run( ["dpkg", "-s", pNombreDelPaqueteApt], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) return vResultado.returncode == 0 def fInstalarPaqueteApt(pNombreDelPaqueteApt): """Instala un paquete mediante apt.""" print(f"[*] Instalando paquete apt: {pNombreDelPaqueteApt}") try: subprocess.run( ["sudo", "apt-get", "-y", "update"], check=True ) subprocess.run( ["sudo", "apt-get", "-y", "install", pNombreDelPaqueteApt], check=True ) print(f"[✓] {pNombreDelPaqueteApt} instalado correctamente") except subprocess.CalledProcessError as e: print(f"[✗] Error instalando {pNombreDelPaqueteApt}: {e}") sys.exit(1) def fModuloPythonEstaInstalado(pNombreDelModulo): """Verifica si un módulo Python está disponible.""" return importlib.util.find_spec(pNombreDelModulo) is not None def fInstalarPaquetePython(pNombreDelPaquete): """Instala un paquete Python mediante pip.""" print(f"[*] Instalando paquete Python: {pNombreDelPaquete}") try: subprocess.run( [ sys.executable, "-m", "pip", "install", pNombreDelPaquete, "--break-system-packages" ], check=True ) print(f"[✓] {pNombreDelPaquete} instalado correctamente") except subprocess.CalledProcessError as e: print(f"[✗] Error instalando {pNombreDelPaquete}: {e}") return False return True def fComprobarEInstalarPaquetes(pdPaquetesPython): """Comprueba e instala los paquetes Python necesarios.""" aErrores = [] for vNombrePip, vNombreModulo in pdPaquetesPython.items(): if fModuloPythonEstaInstalado(vNombreModulo): print(f"[✓] {vNombrePip} ya está instalado") else: if not fInstalarPaquetePython(vNombrePip): aErrores.append(vNombrePip) return aErrores def fInstalarDependencias(pdPaquetesPython): """ Función principal para instalar dependencias. Args: pdPaquetesPython: Diccionario {nombre_pip: nombre_modulo} Returns: True si todo OK, False si hubo errores """ print("=== Comprobando dependencias ===\n") # Verificar pip if not fPaqueteAptEstaInstalado(cNombreDelPaqueteApt): fInstalarPaqueteApt(cNombreDelPaqueteApt) else: print(f"[✓] {cNombreDelPaqueteApt} ya está instalado") print() # Instalar paquetes Python aErrores = fComprobarEInstalarPaquetes(pdPaquetesPython) print("\n=== Resumen ===") if aErrores: print(f"[!] Paquetes con errores: {', '.join(aErrores)}") return False else: print("[✓] Todas las dependencias instaladas correctamente") return True # Solo para pruebas directas del módulo if __name__ == "__main__": dPaquetesEjemplo = { "flask": "flask", "requests": "requests", } if not fInstalarDependencias(dPaquetesEjemplo): sys.exit(1)Poner ese script en la misma carpeta en la que tenemos el script principal nos permite hacer lo siguiente en el script principal, justo después del shebang:
# -- Instalación de dependencias Python -- from depinstall import fInstalarDependencias dDependencias = { "flask": "flask", "requests": "requests", "Pillow": "PIL", "python-nmap": "nmap", } fInstalarDependencias(dDependencias) # -- Fin de la instalación de dependencias pythonDe esta forma, cada vez que se ejecute el script principal, comprobará que los paquetes necesarios para que el script se ejecute correctamente, estén instalados. Y, si no lo están, los instalará.
También se puede hacer de otra forma: podemos poner los paquetes de python en el archivo requirements-python.txt y los paquetes de debian en requirements-debian.txt y que el siguiente código:
#!/usr/bin/env python3 import importlib.util import subprocess import sys from pathlib import Path cArchivoRequirementsPython = "requirements-python.txt" cArchivoRequirementsDebian = "requirements-debian.txt" cNombreDelPaqueteApt = "python3-pip" def fPaqueteAptEstaInstalado(pNombreDelPaqueteApt): vResultado = subprocess.run( ["dpkg", "-s", pNombreDelPaqueteApt], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) return vResultado.returncode == 0 def fInstalarPaqueteApt(pNombreDelPaqueteApt): print(f"[*] Instalando paquete apt: {pNombreDelPaqueteApt}") try: subprocess.run( ["sudo", "apt-get", "-y", "update"], check=True ) subprocess.run( ["sudo", "apt-get", "-y", "install", pNombreDelPaqueteApt], check=True ) print(f"[✓] {pNombreDelPaqueteApt} instalado correctamente") except subprocess.CalledProcessError as e: print(f"[✗] Error instalando {pNombreDelPaqueteApt}: {e}") sys.exit(1) def fModuloPythonEstaInstalado(pNombreDelModulo): return importlib.util.find_spec(pNombreDelModulo) is not None def fInstalarPaquetePython(pNombreDelPaquete): print(f"[*] Instalando paquete Python: {pNombreDelPaquete}") try: subprocess.run( [ sys.executable, "-m", "pip", "install", pNombreDelPaquete, "--break-system-packages" ], check=True ) print(f"[✓] {pNombreDelPaquete} instalado correctamente") except subprocess.CalledProcessError as e: print(f"[✗] Error instalando {pNombreDelPaquete}: {e}") return False return True def fLeerArchivoRequisitos(pRutaArchivo): vRuta = Path(pRutaArchivo) if not vRuta.exists(): print(f"[!] Archivo no encontrado: {pRutaArchivo}") return [] aLineas = [] with vRuta.open("r", encoding="utf-8") as vFichero: for vLinea in vFichero: vLinea = vLinea.strip() if vLinea and not vLinea.startswith("#"): aLineas.append(vLinea) return aLineas def fInstalarPaquetesDebianDesdeArchivo(pArchivo): print("=== Comprobando paquetes Debian ===\n") aPaquetes = fLeerArchivoRequisitos(pArchivo) aErrores = [] for vPaquete in aPaquetes: if fPaqueteAptEstaInstalado(vPaquete): print(f"[✓] {vPaquete} ya está instalado") else: try: fInstalarPaqueteApt(vPaquete) except SystemExit: aErrores.append(vPaquete) return aErrores def fInstalarPaquetesPythonDesdeArchivo(pArchivo): print("\n=== Comprobando paquetes Python ===\n") aPaquetes = fLeerArchivoRequisitos(pArchivo) aErrores = [] for vPaquete in aPaquetes: vNombreModulo = vPaquete.split("==")[0].split(">=")[0].split("<=")[0] if fModuloPythonEstaInstalado(vNombreModulo): print(f"[✓] {vPaquete} ya está instalado") else: if not fInstalarPaquetePython(vPaquete): aErrores.append(vPaquete) return aErrores def fInstalarDependencias(): print("=== Iniciando instalación de dependencias ===\n") if not fPaqueteAptEstaInstalado(cNombreDelPaqueteApt): fInstalarPaqueteApt(cNombreDelPaqueteApt) else: print(f"[✓] {cNombreDelPaqueteApt} ya está instalado") aErroresDebian = fInstalarPaquetesDebianDesdeArchivo(cArchivoRequirementsDebian) aErroresPython = fInstalarPaquetesPythonDesdeArchivo(cArchivoRequirementsPython) print("\n=== Resumen ===") if aErroresDebian: print(f"[!] Errores en paquetes Debian: {', '.join(aErroresDebian)}") if aErroresPython: print(f"[!] Errores en paquetes Python: {', '.join(aErroresPython)}") if not aErroresDebian and not aErroresPython: print("[✓] Todas las dependencias instaladas correctamente") return True return False if __name__ == "__main__": if not fInstalarDependencias(): sys.exit(1)…busque e instale los paquetes que aparecen en esos archivos de forma que, ponemos los tres archivos en la misma carpeta del código de nuestra app de python, podemos llamarlo directamente desde el script de la app haciendo esto:
import depinstall if not depinstall.fInstalarDependencias(): raise SystemExit(1)
El contenido de requirements-debian.txt podría ser
python3-nmap python3-requests
Y el contenido de requirements-python.txt podría ser
flask x