Cómo empaquetar un proyecto de Python con todas sus dependencias para la instalación fuera de línea.

Estoy escribiendo esta publicación para mostrar la forma en que creo que es la forma correcta de instalar y empaquetar aplicaciones de Python con todas sus dependencias.

A menudo, debe tener un paquete que pueda usar para instalar la aplicación y sus dependencias sin llegar a Internet, podría ser un servidor remoto con solo acceso a la red interna por razones de seguridad, y podría ser esa una de sus dependencias está alojado en un repositorio git privado y no desea codificar el token git para instalarlo a través de https, por ejemplo, un contenedor docker.

Mi enfoque es combinar SetupTools, DistUtils y pip Wheel para un formato tar.gz final.

Supongo que ya está familiarizado con los conceptos básicos del embalaje:
https://python-packaging.readthedocs.io/en/latest/minimal.html

El empaquetado básico de Python requiere el mínimo de un archivo setup.py en la raíz de su proyecto que luego puede llamar a python setup.py sdist

setup.py gist

El cmdclass es una extensión de setuptools y es la clase que empaquetará el código para su distribución, pero también recopila todos los paquetes de require.txt en el archivo.
El siguiente script (package.py) es una extensión del comando setuptools para crear una distribución desde un proyecto.
La configuración normal de Python sdist solo empacará archivos y carpetas, mientras que este script va sobre
requerimientos.txt hará una rueda
(zip like archive) del requisito de ser almacenado en la carpeta de la timonera y empacado junto con
el código como un solo archivo tar.gz.

Advertencia de embalaje:
Este script recopila los requisitos instalados (compilados) desde donde se ejecuta, esto significa que si su require.txt contiene algún requisito que se base en extensiones C como psycopg, por ejemplo, se recopilará el paquete compilado.
Cuando desempaqueta el archivo en el destino si el sistema es diferente al sistema del que recolectó el paquete, no funcionará según lo previsto e incluso se bloqueará.

déjame repetir esto:
ubuntu! = centos! = macOS
python2.6! = ​​python2.7

Sin embargo, puede empaquetar en ubuntu cuando el sistema de destino es Debian ya que son la misma familia.

paquete.py gist

Este script solo puede ejecutarse desde la misma familia debido a las extensiones C que deben compilarse en
plataforma (es decir, psycopg, etc.).

Lo que hace este script:

Este script está eliminando y recreando la carpeta de la timonera en la raíz del directorio de su proyecto.

Luego llama a pip wheel -r require.txt, que recopila los paquetes instalados del entorno actual en el que están instalados en los archivos .whl en la timonera, luego el script crea un archivo local require.txt sin ningún enlace http, solo el paquete local. nombre como está almacenado en la caseta del timón, la razón de esto es la forma en que funciona pip.

Después de desempaquetar el paquete en el destino de destino, los requisitos se pueden instalar localmente y sin conexión desde la carpeta de la caseta del timón utilizando la opción --no-index en la instalación de pip que ignora el índice del paquete (solo mirando las URL --find-links en su lugar).
--find-links busca el archivo desde la url o ruta.

Dado que el require.txt original puede tener enlaces a un repositorio no pip como Github (https) pip analizará los enlaces para el archivo desde una url y no desde la caseta del timón, dando como resultado una solicitud http.

Esta función crea un nuevo require.txt (hará una copia de seguridad de los requisitos originales .txt en require.orig y se restaurará después del empaquetado) con t solo el nombre y la versión de cada uno de los paquetes, eliminando así la necesidad de buscar / analizar enlaces de fuentes http e instalar todos los archivos completamente fuera de línea desde la caseta del timón

Finalmente, el script llama a setuptools sdist, por lo que puede cambiarlo al formato que desee que sea compatible con setuptools.

MANIFEST.in
La última parte para empaquetar la caseta del timón necesitamos decirle a setuptools que incluya esta carpeta en el archivo usando la caseta del injerto:

MANIFEST.in gist

Tenga en cuenta que yo podo (borrar / saltar del archivo) la carpeta git en sí.

Poniendolo todo junto:
Cree los archivos en la raíz de su proyecto:

  • paquete.py
  • setup.py
  • MANIFEST.in

Llame al paquete python setup.py:
esto eliminará y volverá a crear la carpeta de la timonera en la raíz de su proyecto, luego recogerá los paquetes en la carpeta de la timonera.
El último paso creará el archivo en la carpeta dist en la raíz de su proyecto.

En este punto, puede copiar el archivo en una imagen acoplable o en su servidor de la forma que desee.

Desembalaje:
Para desempaquetar simplemente tar zxf archive.tar.gz y luego llamar

pip install -r required.txt --use-wheel --no-index --find-links wheelhouse

Sus paquetes se instalarán completamente fuera de línea.

Consejo de bonificación:
Si planea ejecutar este archivo en el contenedor de Docker, le sugiero que cree una imagen de Docker de Python con todos los gcc OpenSSL y otras bibliotecas de compilación para instalar todas las dependencias en y desde este contenedor ejecute el paquete setup.py en un volumen / copia de montaje compartido en donde quieras. de esta manera, su contenedor de tiempo de ejecución es delgado y no necesita todas las bibliotecas de compilación pesadas.