Agregar assets a PyInstaller, resolver rutas y empaquetar

Pyinstaller parte 2: agregar assets, imágenes y archivos a ejecutable de Python

Ya ha pasado algún tiempo desde que escribí cómo empaquetar un archivo de Python en un ejecutable. No pensé que tuviera el impacto que tuvo, pero me da gusto al final de todo.

Agregar assets a PyInstaller, resolver rutas y empaquetar
Agregar assets a PyInstaller, resolver rutas y empaquetar

En fin, ahora aquí está la parte 2 en donde muestro cómo agregar assets a nuestro paquete. Por ejemplo, a veces es necesario agregar imágenes, canciones o cualquier archivo extra al empaquetar con pyinstaller.

Cabe mencionar que no fue nada fácil, ya que casi no encontré documentación sobre eso excepto en el sitio oficial.

Lo que vamos a hacer

Será simple. Aprovechando que acabamos de ver cómo leer un archivo de texto, haremos que ese archivo de texto sea empaquetado junto con el ejecutable. Que sea simple no significa que no funcione, ya que con el ejemplo que pondré se verá cómo agregar más assets de todo tipo.

Requisitos y recomendaciones

Recuerda mirar el tutorial anterior, estás forzado a ello. Aparte de eso, aquí puedes ver más tutoriales de Python.

Paso 1: crear y modificar el archivo spec

Cuando ya tengas listo tu código, empaquétalo normalmente como quieras. En mi caso tengo el archivo main.py con el siguiente código:

Hola, soy un archivo de texto para demostrar algunos tutoriales de parzibyte.me. Puedo contener cualquier tipo de contenido, por ejemplo, saltos,
    tabulaciones
y cualquier

otra


cosa.
"""
Ejemplo simple de cómo leer un archivo
línea por línea en Python 3

@author parzibyte
"""
nombre_archivo = "archivo.txt"
with open(nombre_archivo, "r") as archivo:
    for linea in archivo:
        print("Aquí hay una línea: ", linea)

Así que para empaquetarlo en un .exe ejecuto lo siguiente:

pyinstaller --onefile main.py

Luego de esperar un momento, se creará lo necesario. Voy a omitir las cosas básicas porque ya las puse en el tutorial anterior. Ahora, lo importante es un archivo con extensión .spec que se ha creado. Su nombre debe ser el mismo que nuestro script principal, así que en mi caso se llama main.spec.

Archivo spec creado por pyinstaller
Archivo spec creado por pyinstaller

Ahora vamos a agregar código (porque es código de Python) de manera que se añadan datos al valor datas de a. Así:

a.datas += [("ruta_absoluta_al_archivo", "ruta_al_archivo_que_usamos_en_el_código", "DATA")]

Dentro del arreglo [] podemos agregar las tuplas necesarias, con 3 elementos en cada una de ellas… una tupla por cada archivo.

De manera que solamente cambian las dos primeras cosas, ya que DATA se queda intacto. En mi caso agregaré el archivo archivo.txt así que mi spec queda así:

a.datas += [("./archivo.txt", "archivo.txt", "DATA")]

Si fuera necesario, agregaría más tuplas separadas por coma; siempre dentro del arreglo. Para que quede más claro, ahora mi archivo spec se ve así:

Modificar datas en PyInstaller para agregar assets
Modificar datas en PyInstaller para agregar assets

Ahora sí ya estamos listos. Por cierto, ya no ejecutes pyinstaller con el script, porque va a sobrescribir el archivo spec. Déjalo intacto, sólo si haces cambios entonces genera de nuevo y modifica.

Paso 2: crear función que resuelve rutas

Lo que sigue es crear una función que resuelva las rutas dependiendo del entorno. Si estamos en desarrollo normalmente, resolverá la ruta normal. Pero si ya está empaquetado, resolverá la ruta en donde se desempaquetan los assets.

import os, sys
def resolver_ruta(ruta_relativa):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, ruta_relativa)
    return os.path.join(os.path.abspath('.'), ruta_relativa)

Gracias a _MEIPASS sabremos si ya estamos “empaquetados” o todavía no. Esta ruta de _MEIPASS es en donde PyInstaller coloca los assets que le indicamos.

En caso de que no estén empaquetados, la ruta se resuelve normalmente. Por cierto, la función necesita que exista os y sys, por eso es que se importan al inicio.

Paso 3: cambiar rutas

En cada lugar en donde se invoque a un archivo, vamos a cambiarlo para que ahora su ruta sea lo que devuelve resolver_ruta. Por ejemplo, el código para leer el archivo indica así la ruta:

nombre_archivo = "archivo.txt"

Pero ahora lo cambiaré para que sea lo siguiente:

nombre_archivo = resolver_ruta("archivo.txt")

En tu caso cámbialo por cada imagen, canción o lo que sea. Antes de empaquetar todo, prueba tu código normalmente para ver si realmente se resuelven las rutas.

Paso 4: poniendo todo junto

Como mi script es sencillo, puse la función que resuelve rutas dentro del mismo. Por cierto, agregué un mensaje de “presiona Enter para salir” (usando input) al final para que al ejecutarlo no se cierre inmediatamente. Todo el código queda así:

Hola, soy un archivo de texto para demostrar algunos tutoriales de parzibyte.me. Puedo contener cualquier tipo de contenido, por ejemplo, saltos,
    tabulaciones
y cualquier

otra


cosa.
"""
Agregar assets con PyInstaller. Ejemplo de
cómo empaquetar un archivo de texto junto 
con el código fuente. El script lee un archivo
línea a línea

@author parzibyte
"""
import os, sys


def resolver_ruta(ruta_relativa):
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, ruta_relativa)
    return os.path.join(os.path.abspath('.'), ruta_relativa)

nombre_archivo = resolver_ruta("archivo.txt")
with open(nombre_archivo, "r") as archivo:
    for linea in archivo:
        print("Aquí hay una línea: ", linea)
input("Presiona Enter para salir")
# -*- mode: python -*-

block_cipher = None


a = Analysis(['main.py'],
             pathex=['D:\\desarrollo\\ejercicios'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
a.datas += [("./archivo.txt", "archivo.txt", "DATA")]
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='main',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          runtime_tmpdir=None,
          console=True )

También agregué mi main.spec para que quede de referencia.

Paso 5: hora de empaquetar

Este paso es el más importante. Ahora no haremos esto:

pyinstaller --onefile main.py

Sino que vamos a pasar el nombre del spec, así:

pyinstaller --onefile main.spec

Si no lo haces así, se va a sobescribir el main.spec.

Después de eso podemos ir a la carpeta dist en donde ejecutamos el comando, y estará un ejecutable con el nombre de nuestro script, en mi caso main.exe.

¿No crees que funcione? te invito a descargar el ejecutable. Si se lee el archivo, significa que sí pude empaquetar el asset.

Vídeo

Aquí hay un vídeo con todos los pasos que se explican en el post:

Conclusiones

PyInstaller y el archivo spec permiten hacer más cosas. En este caso vimos cómo agregar archivos al ejecutable, pero se pueden hacer más cosas.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

5 comentarios en “Pyinstaller parte 2: agregar assets, imágenes y archivos a ejecutable de Python”

  1. pyinstaller –onefile –icon=./nombre_archivo.ico maestro_descarga4.py

    para crear un archivo ico pone en googl e busca “crear ico online” y sube una imagen (la que deseas) y la conviertes y bajaras esa pero ahora .ico

    Saludos M.R.

  2. Rafael Contrerad

    Saludos. He probado tu tutorial en Linux tal cual pero no me funciono pero di con la solución a partir de tu trabajo.

    Partimos del punto de que tenemos el archivo.txt y el main.py tal cual como lo publicas aqui. Lo que hay que hacer es decirle en la terminal:

    pyinstaller –onefile main.py –add- data archivo.txt:/

    Si el asset que quieres agregar esta dentro de una carpeta entonces le dices el nombre de la carpeta

    pyinstaller –onefile main.py –add-data nombrecarpeta/archivo.txt/nombrecarpeta

  3. Pingback: Empaquetando scripts de Python en un .exe o ejecutable utilizando PyInstaller - Parzibyte's blog

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *