pip, apurate por favor

pip es una herramienta esencial para el trabajo diario de un programador python: es el manejador de paquetes de nuestro entorno de trabajo (¡virtual por favor!), con el que instalamos, actualizamos o eliminamos las dependencias de nuestro proyecto (y, recursivamente, las dependencias que estas pudieran tener).

Conceptualmente es similar a los manejadores de paquetes de sistema como apt-get, diferenciándose en que, por defecto, consulta cada vez a una base de datos online si el paquete solicitado existe y de dónde puede bajar la última versión o la específica que se haya pedido.

Responder "qué, cuál y de dónde" es una tarea lenta porque dicha base de datos no es más que una página html por cada paquete con links que funcionan como un índice (como este que pip debe parsear comparar y elegir la mejor opción para bajar (a veces incluso debe parsear la homepage del proyecto en busca de links de descarga, puaj!).

Por eso (y porque muchas veces la infraestructura se satura) el uso estándar de pip es lento. Pero hay algunas maneras de que lo sea menos. Veámoslas.

No bajes dos veces lo mismo

El funcionamiento básico de pip es instalar un paquete con pip install <paquete>: busca, baja e instala el paquete. El flag --download_cache=<path> evita repetir el paso del medio, cosa tediosa cuando tenemos que instalar frecuentemente (por ejemplo en distintos virtualenvs) la misma dependencia o cuando el uso de ancho de banda es limitado.

Por ejemplo instalamos por primera vez lxml y vemos cuanto tarda.

(test)tin@traful:~/lab/test$ time pip install lxml --download-cache=~/.pip_download
Downloading/unpacking lxml
  Downloading lxml-3.1.1.tar.gz (3.3MB): 3.3MB downloaded
  Storing download in cache at /home/tin/.pip_download_cache/https%3A%2F%2Fpypi.python.org%2Fpackages%2Fsource%2Fl%2Flxml%2Flxml-3.1.1.tar.gz
  Running setup.py egg_info for package lxml
    Building lxml version 3.1.1.

    [... compilación]


Successfully installed lxml
Cleaning up...

real    2m58.276s
user    0m38.822s
sys 0m0.676s
(test)tin@traful:~/lab/test$

¡3 minutos! Y eso que estoy en una conexión bastante rápida.

Consejo

Cualquier flag que pip acepta en su linea de comando se puede configurar como una variable de entorno. Entonces podemos setear flag por defecto en nuestro .bashrc, por ejemplo.. code-block: bash

export PIP_DOWNLOAD_CACHE=~/.pip_download_cache

Pero sigamos: una vez cacheado, las siguientes veces que queramos instalar la misma versión de lxml no bajará el archivo de nuevo

(test2)tin@traful:~/lab/test2$ time pip install lxml --download-cache=~/.pip_download

Downloading/unpacking lxml
  Using download cache from /home/tin/.pip_download_cache/https%3A%2F%2Fpypi.python.org%2Fpackages%2Fsource%2Fl%2Flxml%2Flxml-3.1.1.tar.gz
  Running setup.py egg_info for package lxml
    Building lxml version 3.1.1.

    [... compilación]

Successfully installed lxml
Cleaning up...

real    2m30.624s
user    0m38.966s
sys 0m0.504s

Mejoró realmente poco. ¿que clase de caché es esta? Chusmeemos que hay en el directorio.

(test)tin@traful:~/lab/test$ ls ~/.pip_download_cache/ | grep lxml
https%3A%2F%2Fpypi.python.org%2Fpackages%2Fsource%2Fl%2Flxml%2Flxml-3.1.1.tar.gz
https%3A%2F%2Fpypi.python.org%2Fpackages%2Fsource%2Fl%2Flxml%2Flxml-3.1.1.tar.gz.content-type

¿Caché de urls? -download-cache no evita todo el laburo de averiguar de dónde bajar, sino simplemente no baja si la url resultante ya existe (como nombre de un archivo) en este directorio.

Lo explica simple Carl Meyer:

La función --download-cache no apunta a prevenir la búsqueda en la red del archivo correcto a bajar: todo lo que hace es guardarlo una vez que lo encontró. Si de verdad te interesa instalar tus depedencias desde tu compu (sin salir a la red) usá --download primero y luego --find-links (apuntando al path de descarga) con --no-index.

Una caché sin salir a la red

Sigamosle la corriente al bueno de @carljm:

(test3)tin@traful:~/lab/test3$ time pip install --download=~/.pip_packages lxml
Downloading/unpacking lxml
  Using download cache from /home/mgaitan/.pip_download_cache/https%3A%2F%2Fpypi.python.org%2Fpackages%2Fsource%2Fl%2Flxml%2Flxml-3.1.1.tar.gz
Saved /home/mgaitan/.pip_packages/lxml-3.1.1.tar.gz
    [...]

Successfully downloaded lxml
Cleaning up...

real    2m8.969s
user    0m1.008s
sys 0m0.136s

¡Uff, 2 minutos en copiar un archivo que ya tenía bajado! (evidentemente lo que demora mucho es averiguar la versión del archivo a bajar)

Consejo

se puede inspeccionar el berenjenal de redirecciones y parseos que suceden hasta que pip da con el paquete lxml a bajar haciendo el comando más verborrágico con pip install lxml -vvv

En este caso, el caché es directamente el archivo:

(test)mgaitan@traful:~/lab/test$ ls ~/.pip_packages/ | grep lxml
lxml-3.1.1.tar.gz

Por suerte, una vez cacheado el paquete de esta manera no tendremos que consultar el índice online las siguientes veces.

(test3)mgaitan@traful:~/lab/test3$ time pip install --no-index --find-links=~/.pip_packages lxml
Ignoring indexes: https://pypi.python.org/simple/
Downloading/unpacking lxml
  Running setup.py egg_info for package lxml
    Building lxml version 3.1.1.

    [...]


Successfully installed lxml
Cleaning up...

real    0m38.944s
user    0m38.338s
sys 0m0.564s

Ok, ya va mejor.

Haciendo que la cosa vuele: no recompiles la rueda

pip 1.4 (en desarrollo) trae soporte integrado para el nuevo formato de paquetes wheel (superador del viejo egg y basado en los estándares actuales) que es muchísimo más rápido que instalar desde fuentes (sobre todo en casos que se debe compilar, como lxml)

Para usar wheel el paquete a bajar tiene que existir en dicho formato y todavía no abundan en PyPi asi que podemos armarlos localmente con el propio pip

pip wheel --wheel-dir=./pip_packages lxml

Eso es similar a usar --download pero además compila y empaqueta como un archivo .whl

Para que pip acepte instalar estos archivos hay que usar --use-wheel y para que los busque localmente haremos:

pip install --use-wheel --no-index --find-links=~/.pip_packages lxml

¡Lo que tardó menos de 2 decimas de segundo! Un speedup del 90mil veces respecto al primer y canónico pip install lxml

(test)tin@morochita:~/lab/test$ time pip install --use-wheel --no-index --find-links=. lxml
Ignoring indexes: https://pypi.python.org/simple/
Downloading/unpacking lxml
Installing collected packages: lxml
Successfully installed lxml
Cleaning up...

real    0m0.180s
user    0m0.152s
sys 0m0.024s

Asi que ya sabés: todos esos paquetes que instalás en cada entorno (quizas ipython, django, whatever) me los haces rodar para que pip vuele.


El código de este artículo está disponible en github. ¿Encontraste un error? Por favor, enviame un pull request.

Comentarios

Comments powered by Disqus