martes, 18 de julio de 2017

VirtualEnvs en Python


Una de las mejores cosas que tiene python, especialmente respecto a otros lenguajes, es la gestión de dependencias externas de un proyecto o paquete con pip.

Pero todo desarrollador sabe que, antes o después, hay que enfrentarse a la realidad de lidiar con versiones distintas de una misma dependencia en distintos proyectos. Así, podríamos estar trabajando en un proyecto nuevo, donde usásemos por ejemplo la versión 1.10 de Django y a la vez manteniendo un proyecto más antiguo con la versión 1.8 del framework.

Ya que las dependencias se instalan a nivel global en el intérprete de python, no podemos tener ambas versiones de una misma biblioteca instaladas a la vez en nuestro sistema. De modo que, ¿cómo solucionamos esto? La respuesta es sencilla: Entornos virtuales o VirtualEnvs.


¿Qué es un VirtualEnv?


En python, todas las bibliotecas que instalamos van a un mismo directorio. Bueno, en realidad, la forma en que lo gestiona cada distribución de Linux internamente puede llegar a ser caótica, aunque no es algo que deba preocuparnos como programadores ya que python es capaz de encontrar todas las bibliotecas de manera transparente para nosotros.

He aquí una lista de las posibles rutas en las que podemos encontrarnos bibliotecas de python en Ubuntu 16.04:

Un virtualenv es, esencialmente, otra carpeta más donde almacenar paquetes, con la salvedad de que esta carpeta no será buscada por el intérprete global de python a la hora de ejecutarse, lo que la hace independiente de cualquier paquete que tengamos instalado en el sistema, permitiéndonos así almacenar en ella una versión distinta de una biblioteca que ya tengamos instalada globalmente.

En este punto, uno puede estar tentado de añadir dicha carpeta con los módulos que tenemos apartados al PYTHONPATH y listo. Pero si hiciéramos tal cosa y tuviésemos dos versiones del mismo paquete, tendríamos garantizados conflictos y errores en nuestro programa. Además, se supone que queremos trabajar en un entorno totalmente aislado del resto, un sandbox, donde no dispongamos de los paquetes preinstalados en el sistema si no queremos, sino que únicamente estén presentes los que decidamos instalar nosotros mismos.

Por ello, la solución pasa por tener un intérprete de python dedicado, de uso exclusivo para nuestro entorno, que sea totalmente independiente del intérprete global. Veremos ahora cómo se logra todo esto pero, antes que nada, ¡hay que instalarlo!


Instalación de VirtualEnv


Antes de proceder con la instalación del paquete virtualenv, vamos a asegurarnos de que tenemos unos cuantos paquetes previos que, si bien no todos son imprescindibles para nuestro cometido de hoy, sí conviene tener si somos desarrolladores:

A veces se da la circunstancia de que la versión que se instala de un determinado paquete de python desde los repositorios de Ubuntu no es la última disponible, pero podemos actualizarlo fácilmente con pip, aún incluso si el paquete es el propio pip, tal y como me ocurre ahora mismo a mí, que la versión que se me ha instalado de pip es la 8.1.1 pero la última disponible es la 9.0.1:

Quiero señalar que he utilizado pip3 para actualizar la versión de pip de python 3. Para actualizar la versión de python 2, tendremos que usar pip2. No podemos predecir el comportamiento de la instrucción pip a secas (en algunas instalaciones me he encontrado que utilizaba python 2 y en otras python 3), así que mejor utilizar las instrucciones no ambiguas.

Una vez hecho esto, vamos a proceder a instalar el paquete virtualenv. Nos da igual la versión de python con la que lo instalemos, ya que el paquete es compatible con ambas, si bien pienso que no existe ninguna razón para iniciar un nuevo proyecto hoy día en python 2.

Ya que estamos, he instalado también el paquete virtualenvwrapper, que nos proveerá de una serie de funciones adicionales que explicaré más tarde.

He utilizado sudo para ejecutar las instrucciones de instalación porque me interesa que estas bibliotecas estén disponibles globalmente en el sistema. No obstante, no suele ser recomendable utilizar sudo con pip, sino utilizar la opción ‑‑user a la hora de instalar algo, para que lo instale en la ruta ~/local/lib/python3.5/site-packages/.


Creación y gestión de entornos virtuales


Como he dicho antes, un virtualenv no es más que una carpeta donde se almacenarán los paquetes que queramos tener para un determinado proyecto; además, también he comentado que un virtualenv debe tener su propio intérprete de python para poder garantizar el aislamiento de dichos paquetes, sin que interfieran los que tenemos instalados en el sistema globalmente.

Para crear un virtualenv, hacemos lo siguiente:

Esto creará la carpeta mi_proyecto y copiará el intérprete del sistema que le hayamos especificado, python 3 en este caso, dentro de la carpeta mi_proyecto/bin/ .

La estructura de carpetas creadas será la siguiente (he truncado la salida para mostrar lo más destacado y no saturar):

Como vemos, en la carpeta mi_proyecto/bin/ se han copiado más programas, como easy_install, pip, setuptools y wheel.

Debo añadir que es muy importante que en la ruta absoluta hacia la carpeta no haya ni un solo espacio ya que, de haberlo, tendremos problemas para activar el entorno virtual. De ahí que haya escogido el nombre mi_proyecto en lugar de mi proyecto para la carpeta contenedora del virtualenv.

El siguiente paso es "activar" el virtualenv. ¿Qué significa esto? Activar un virtualenv sólo tiene sentido cuando estamos trabajando desde un terminal, ya que lo que hace es activar un contexto específico desde el cual todas las llamadas a los programas python, easy_install, pip, setuptools o wheel se hacen a los programas alojados en mi_proyecto/bin/ en lugar de a los programas instalados globalmente en el sistema. En realidad, no es más que un atajo para no tener que escribir las rutas completas a dichos programas cada vez que queramos ejecutarlos, tal y como ahora veremos.

Para activar un virtualenv hacemos lo siguiente:

Tras activarlo, aparecerá el nombre del entorno virtual entre paréntesis al principio de cada línea de terminal que ejecutemos, hasta que lo desactivemos.

Para desactivar el virtualenv activo y volver al contexto global, basta con la siguiente línea:

Para que quede clara la diferencia entre tener activo o no un entorno virtual, vamos a ilustrarlo con un ejemplo: Supongamos que tenemos instalada en el sistema la biblioteca watchdog. Es evidente, pues, que si la importamos desde el python 3 global de nuestro sistema, no habrá ningún problema. Por otro lado, en el entorno virtual que hemos creado anteriormente no hemos instalado watchdog, por lo que si intentamos importar la biblioteca desde el python 3 del virtualenv, obtendremos un error. Veámoslo:

En realidad no es estrictamente necesario "activar" el entorno virtual, ya que podemos llamar directamente al programa python 3 que hay instalado en mi_proyecto/bin/ y ver que no es capaz de importar watchdog, si bien activando el virtualenv nos ahorramos tener que escribir toda la ruta hasta el programa:

Y lo mismo pasa con pip y el resto de programas de mi_proyecto/bin/. Si llamamos al programa pip que hay instalado en mi_proyecto/bin/, ya sea directamente o habiendo activado el virtualenv (ya hemos establecido que ambas cosas son equivalentes), cualquier gestión que hagamos con él, como instalar una biblioteca por ejemplo, se hará exclusivamente en el contexto del virtualenv. Concretamente, las instalaciones se ubicarán en mi_proyecto/lib/python3.5/site-packages/ como podemos ver en este ejemplo donde instalamos django:

Si utilizamos para gestionar nuestro proyecto un IDE, que es lo más habitual, como PyCharm por ejemplo, bastará con indicar que el intérprete de python que debe utilizar el proyecto es el que está ubicado dentro de mi_proyecto/bin/ en lugar del general, que estará normalmente en /usr/bin/.

Nótese que en ningún momento hemos hablado de dónde debe ubicarse el código fuente de un proyecto. Lo cierto es que no tiene la más mínima importancia. Un entorno virtual y el código fuente del proyecto que lo ejecuta no tienen por qué estar en la misma carpeta, son cosas totalmente independientes y, de hecho, ahora explicaremos una forma cómoda de gestionar todos los virtualenvs que generemos en nuestro sistema.

Para eliminar un virtualenv, basta con eliminar la carpeta que lo contiene. En nuestro ejemplo:



Gestión de entornos con virtualenvwrapper


Aunque la gestión de virtualenvs no es especialmente compleja, si no somos meticulosos, podemos acabar con muchos repartidos por nuestro sistema sin orden ni lógica, lo cual puede acabar siendo complicado de llevar.

El paquete virtualenvwrapper nos proporciona un punto de acceso único para la creación y gestión de todos los virtualenvs que queramos. Bueno, en realidad, posee algunas funcionalidades más relacionadas con la gestión de proyectos y la asociación de proyectos a virtualenvs, pero no me voy a parar en ellas ya que, personalmente, mis proyectos los gestiono con PyCharm, que me resulta mucho más cómodo y práctico que trabajar con el terminal para este tipo de cosas.

Sólo nos interesarán aquí, pues, las opciones básicas: creación, activación y eliminación de un virtualenv, y listado de todos los virtualenvs creados.

Pero antes, un requisito indispensable: elegir un sitio donde almacenar todos los entornos virtuales que creemos con virtualenvwrapper y activar el script que inicializa todas las funciones del programa en consola. Para ello, abrimos el archivo ~/.bashrc y añadimos las siguientes líneas al final del mismo:

Otra ventaja añadida de virtualenvwrapper es que nos permite utilizar una ruta con espacios para la carpeta contenedora de todos los virtualenvs (y, de hecho, también para el nombre de los virtualenvs que creemos), siempre y cuando especifiquemos toda la ruta entre comillas.

Además, hemos añadido una línea para indicar cuál es el intérprete de python por defecto que debería utilizarse para los virtualenvs que se creen con este paquete.

Las funciones que nos ofrece virtualenvwrapper se ejecutan directamente desde el terminal, y son las siguientes:

  • cdsitepackages : Navega hasta la carpeta site‑packages del virtualenv activo.

  • cdvirtualenv : Navega hasta la carpeta raíz del virtualenv activo.

  • deactivate : Desactiva el virtualenv activo.

  • lssitepackages : Lista los contenidos de la carpeta site‑packages del virtualenv activo.

  • lsvirtualenv : Lista todos los virtualenvs que hayamos creado. Es aconsejable utilizar la opción ‑b con esta función para generar una salida más compacta.

  • mktmpenv : Crea y activa de inmediato un virtualenv temporal, que se borrará en cuanto lo desactivemos. Podemos especificar la opción ‑p para especificar la versión de python que queremos utilizar en el virtualenv, tal y como hacíamos en la sección anterior cuando los creábamos manualmente.

  • mkvirtualenv : Crea y activa de inmediato un virtualenv con el nombre que le especifiquemos. Podemos especificar la opción ‑p para especificar la versión de python que queremos utilizar en el virtualenv, tal y como hacíamos en la sección anterior cuando los creábamos manualmente.

  • rmvirtualenv : Elimina el virtualenv que le especifiquemos.

  • workon : Activa el virtualenv que le especifiquemos.

Resulta mucho más cómodo y recomendable gestionar los virtualenvs con virtualenvwrapper que manualmente uno a uno como especificábamos al principio del artículo. No obstante, la decisión es propia de cada uno.

¡Y esto ha sido todo por hoy! Para cualquier duda, sugerencia o consulta, tenéis la sección de comentarios, como siempre. ¡Un saludo a todos!


Referencias:
  • Instalar y utilizar virtualenv (Virtual Environments) [Link]
  • Python e virtualenv: como programar em ambientes virtuais [Link]
  • Tutorial virtualenv y virtualenvwrapper [Link]
  • Virtual Environments [Link]
  • VirtualEnv Docs [Link]
  • Virtualenvwrapper Docs [Link]

No hay comentarios:

Publicar un comentario