Últimamente he empezado a utilizar la herramienta py2exe para poder mejorar la distribución de mis desarrollos en Python en sistemas Windows, mediante la generación de un ejecutable .exe autocontenido que evite tener que pedir al usuario que instale Python y las correspondientes librerías.
La herramienta py2exe puede ser instalada de manera sencilla con el comando:
pip install py2exe
Y puedes compilar rápidamente cualquier programa en Python realizando un fichero setup.py
simple como este:
from distutils.core import setup import py2exe setup(console=['mi_programa.py'])
El cual deberemos ejecutar desde una consola de comandos de la siguiente forma:
python setup.py install
Al ejecutarlo nos resolverá todas las dependencias y dejará la versión autcontenida con el ejecutable mi_programa.exe
en la carpeta dist
.
Si trabajas con servicios o mínimamente con conexiones seguras haciendo peticiones, seguro que acabas usando la librería requests
o puede que la librería con la que trabajes, la use como base para hacer conexiones seguras con SSL. Si intentas usar estas librerías que trabajan con certificados SSL te encontrarás con el siguiente error cuando ejecutas tu binario .exe:
requests.exceptions.SSLError: [Errno 2] No such file or directory
El cual viene dado porque cuando se empaqueta todo, el certificado de la Autoridad de Certificación no se incluye al ser un fichero que no es de Python. A causa de esto, cuando se empaquetan todas la librerías y se llama de manera relativa al certificado de la librería desde nuestro empaquetado con nuestro binario .exe de py2exe, éste no se encuentra porque ninguna parte.
Para solucionarlo es tan sencillo como proporcionar un certificado válido del tipo cacert.pem
en la variable de entorno de Python REQUESTS_CA_BUNDLE
de nuestro programa. Pero para resolverlo, vamos a hacerlo de manera elegante parcheando dicha variable sólo si es necesario, para poder seguir tirando de los certificados de las propias librería mientras desarrollamos.
Para ello vamos a instalar certifi, una librería que nos facilita una serie de Certificados Raíz que nos van a permitir validar la integridad de certificados, tanto de SSL, como de TSL de los servicios a los que nos conectemos.
pip install py2exe
Ahora vamos a modificar un poco nuestro setup.py
:
from distutils.core import setup import py2exe import certifi setup(console=['mi_programa.py'], data_files=[certifi.where()])
Hemos añadido únicamente el import
de certifi
y en los parámetros de setup hemos añadido el fichero de certificado que nos devuelve certifi
gracias al método where
. Este fichero se copiará en dist
al mismo nivel que nuestro .exe.
Por último añadiremos en mi_programa.py
al inicio de nuestro programa el siguiente código:
cacert_path = os.path.join(os.getcwd(), 'cacert.pem') if os.path.exists(cacert_path): os.environ['REQUESTS_CA_BUNDLE'] = cacert_path
El código genera la ruta hasta certificado cacert.pem
, usando el directorio de trabajo que será dist
. Esa ruta se busca si existe entre las rutas que maneja Python para resolver las librerías y dependencias. Si se ejecuta desde el entorno de desarrollo, encontrará el de la propia librería que lo este usando, sino parcheará añadirá la ruta para que coja certificado que hemos copiado en dist
.
De esta forma no sólo se soluciona el problema del certificado de la Autoridad de Certificación, sino que el parche se aplica selectivamente copiando fichero de certificado necesario. Esto es importante porque los certificados pueden cambiar y basta con volver a generar los binarios .exe con py2exe, teniendo las librerías actualizadas y con los certificados en regla, para que el ejecutable creado también los tenga.