Entornos Virtuales Python en fish usando Oh-My-Fish y Pyenv
Tags: python, linux
Published: 2020-08-18 10:12:52

Una manera directa de crear entornos virtuales Python 3.8 usando el shell fish

## Pyenv y el Modulo venv de Python

### Pyenv

Primero, usamos [Pyenv](https://github.com/pyenv/pyenv#simple-python-version-management-pyenv) para instalar o seleccionar una implementacion de Python distinta a la del sistema. Podemos verificar primero si tenemos la versión de Python que necesitamos mediante

```console
> pyenv versions
* system (set by /home/cjadeveloper/.pyenv/version)
  3.6.8
  3.7.4
  3.8.2
```

De no tenerlo instalado, haríamos un `pyenv install 3.8.2` pero como ya lo tengo instalado, lo selecciono para el proyecto haciendo

```console
pyenv local 3.8.2
```

Esto nos va a crear un archivo `.python-version` en el directorio que nos permitirá 2 cosas, guardar una referencia de la versión de Python con la que trabajaremos en el proyecto y cambiar la versión global de Python por la que hemos elegido.

NOTAS:

1. Este mecanismo se puede anular configurando la variable de entorno `PYENV_VERSION` o con el comando [pyenv shell](https://github.com/pyenv/pyenv/blob/master/COMMANDS.md#pyenv-shell).

2. Tanto el comando `pyenv local [version]` como `pyenv global [version]` nos permiten definir mas de una version a usar al mismo tiempo en un proyecto. Esto es muy útil para usar con herramientas como [tox](https://tox.readthedocs.io/en/latest/) por ejemplo.

3. Si al configurar un intérprete local (o global) no funciona en fish *(Es decir que siempre nos inicia el interprete de Python por defecto instalado)*. Vamos a tener que editar el archivo de configuración de fish `~/.config/fish/config.fish` y agregar las siguientes líneas:
```python
setenv PYENV_ROOT "$HOME/.pyenv"
setenv PATH "$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH"
pyenv rehash
```

## Creando un entorno virtual dentro del proyecto con el modulo venv directamente

La forma que a mi más me gusta trabajar con los entornos virtuales de Python es la que se conoce como *"own site directory"* de la misma manera que se suele trabajar con los modulos en Node: *"Creando una carpeta dentro del proyecto que estamos trabajando en la que contenga nuestra versión de los paquetes que queremos aislar de los del sistema y el propio binario de Python (que va a coincidir con el usado para crear el venv)."* Para hacer todo ello de un solo paso, podemos usar el modulo `venv` que esta disponible desde la versión 3.3 de Python, de la siguiente manera

```console
python -m venv .venv
```

Esto nos creará un entorno virtual de nombre `.venv` en el directorio actual y bajo la carpeta `/.venv`

## Activando nuestro entorno virtual bajo fish

Si estamos usando [fish](https://fishshell.com/) con [Oh-My-Fish](https://github.com/oh-my-fish/oh-my-fish/blob/master/docs/es-ES/README.md) como infraestructura y tenemos instalados unos de sus temas, activar un entorno virtual python de la manera tradicional puede [romper](https://github.com/fish-shell/fish-shell/issues/1118) el bonito prompt que hayamos configurado con Oh-My-Fish. Para que esto no suceda tenemos que setear la variable de entorno `VIRTUAL_ENV_DISABLE_PROMPT` en `1` en nuestro archivo de configuración de fish que, generalmente se encuentra en `~/.config/fish/`

```bash
# ~/.config/fish/config.fish

# ... another config

set -x VIRTUAL_ENV_DISABLE_PROMPT 1
```

Luego, hay que recargar el shell, para que vuelva a leer los archivos de config, mediante `exec fish`. Con esto no se nos va a romper el shell al usar algún tema de [Oh-My-Fish](https://github.com/oh-my-fish/oh-my-fish/blob/master/docs/es-ES/README.md). Ésto lo haremos solo la primera vez. Luego podemos activar el entorno virtual sin tener que configurar nuevamente la variable `VIRTUAL_ENV_DISABLE_PROMPT`, claro ésta.

Para desactivar el entorno, simplemente escribimos `deactivate` en la consola.

## Entendiendo un poco más que hace el módulo venv

Otra forma de gestionar el entorno virtual sin usar el script que sobreescribe nuestro prompt es hacer nuestro propio *activador de entorno virtual* cambiando el valor de la variable de entorno PATH que es el que le dice a nuestro shell que instancia de Python usar cuando llamemos al interprete. Para entender a que nos referimos vamos a listar la variable $PATH y ver su contenido antes de la activación

```console
> echo $PATH
/home/cjadeveloper/.pyenv/shims /home/cjadeveloper/.pyenv/bin /home/cjadeveloper/.local/bin /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin
```

Cuando invoca a un intérprete de Python o ejecuta un script `.py`, su shell busca en los directorios listados en el diccionario PATH en orden, hasta que encuentra una instancia de Python. Para ver qué instancia de Python encuentra primero PATH, ejecute el comando que viene con pyenv: `pyenv which python`.

```console
> pyenv which python
/home/cjadeveloper/.pyenv/versions/3.8.2/bin/python
```

Entonces lo que tenemos que hacer es indicarle al principio del path la ruta del interprete de python que queremos usar. Lo podemos hacer sobreescribiendo la variable de entorno `PATH` para la sesión actual del shell así

```console
set -lx PATH (realpath .venv/bin) $PATH
```

Lo anterior significa que definimos una variable PATH local y exportada que va a incluir delante del path actual la ruta completa a la carpeta `.venv/bin` que estoy especificando. Esta variable PATH al ser local y exportada va a permanecer activa en la sesion actual solamente y prevalecera sobre la variable PATH heredada del shell. Esto tiene su pro y su contra, como veremos mas adelante.

Pero sigamos con el path, por un momento. Al volver a consultar la variable PATH vamos a obtener lo siguiente

```console
> echo $PATH
/home/cjadeveloper/code/examples/python/venvexs/venv1/.venv/bin /home/cjadeveloper/.pyenv/shims /home/cjadeveloper/.pyenv/bin /home/cjadeveloper/.local/bin /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin
```

Con el path del entorno virtual creado anteriormente inyectado al principio. Por ende, si verificamos python nuevamente, vamos a tener

```console
> which python
/home/cjadeveloper/code/examples/python/venvexs/venv1/.venv/bin/python
```

Ahora podemos simular lo que hace virtualenv con su script de activación y establecer el prompt de shell un poco mas bonito e informativo seteando la variable de entorno `VIRTUAL_ENV` que es usada por fish para indicar que estamos dentro de un entorno virtual

```console
set VIRTUAL_ENV "$PWD/.venv"
```

Ya estamos listos para usar python en nuestro entorno virtual. Primero que todo veamos si estamos utilizando la versión de pip correcta para instalar nuestros paquetes. Podemos hacerlo de 2 formas:

```console
pip show pip
```

o también (y es la que algunos prefieren para estar 100% de trabajar con el intérprete que desean)

 ```console
python -m pip show pip
```

Hooray! Ahora podemos hacer nuestro día en Python y cuando terminamos, solamente hay que setear el PATH nuevamente

```console
set -lx PATH $PATH[2..-1]
```

Y salir del entorno, reiniciando el shell.

```console
exec fish
```

## Referencias

- [Prefer Subshells for Context](https://datagrok.org/python/activate/#top)
- [A Guide to Python’s Virtual Environments](https://towardsdatascience.com/virtual-environments-104c62d48c54)
- [A quick-and-dirty guide on how to install packages for Python](https://snarky.ca/a-quick-and-dirty-guide-on-how-to-install-packages-for-python/)
- [pyenv issue: Fish Shell support](https://github.com/pyenv/pyenv/issues/32#issuecomment-21018739)