dinogalactic

A xonsh alias for using pyenv with vox for nice Python virtualenvironments

Today I started migrating away from virtualenvironments created by pyenv-virtualenv and started managing virtualenvs with xonsh's built-in vox utility. (I had previously been using the same old virtualenvironments created by pyenv virtualenv and just activating them with vox activate ~/.pyenv/versions/virtualenv_symlink', but that's for the birds.)

So, one thing I wanted to do is have vox use my pyenv-detected python bin whenver I created new virtualenvironments, if I didn't specify an override python bin. In other words, I wanted stuff like the .python-version file and the pyenv shell command to be respected by vox without adding all that functionality to vox proper. This is probably a pretty specific use case, though if more people need it, lmk!

I came up with an alias that wraps the original vox alias (that's how the vox command is loaded into the environment, as an alias).

Before I override it, I save the previous vox alias in a variable - the vox alias normally just maps to a callable, so I saved the callable. This is because I want to call the previous vox functionality inside of my new alias, which I am naming 'vox', and I would end up with unwanted recursion if I called my new alias function from inside itself.

1
normal_vox = aliases['vox']

Next I define a function that does a simple check for whether I had specified an interpreter in the arguments, and if not, it prepends the pyenv-detected python bin to the argument list.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def pyenv_vox(argument_list, **kwargs):
    """Use pyenv-detected python to create environments if no other interpreter is specified."""
    print(f'Using alias for vox in .xonshrc')
    if len(argument_list) and argument_list[0] == 'new' \
            and '--interpreter' not in argument_list \
            and '-p' not in argument_list:
            python = $(pyenv which python).strip()
            print(f'Using pyenv-detected python to create new environment: {python}')
            argument_list = ['new', '--interpreter', python] + argument_list[1:]
    return normal_vox(argument_list, **kwargs)

Regardless of whether that modification is made to the argument list, it calls the original vox alias with the argument list, so other vox commands effectively pass through untouched.

Finally, I set aliases['vox'] to this new alias function.

1
aliases['vox'] = pyenv_vox

And, that's it. With the print() statements included in the alias function, you can see what happens.

It works, too:

1
2
3
4
5
eddie@eddie-ubuntu ~/website master $ vox new excitement
Using alias for vox in .xonshrc
Using pyenv-detected python to create new environment: /home/eddie/.pyenv/versions/3.7.3/bin/python
Creating environment...
Environment 'excitement' created. Activate it with "vox activate excitement".

Other commands are untouched by the alias - well, at least not substantively. The logging still lets you know you're using the alias.

1
2
3
4
5
6
eddie@eddie-ubuntu ~/website master $ vox ls
Using alias for vox in .xonshrc
Available environments:
butter
excitement
website

You can set up pyenv to use a specific python, and that python will be used for the virtualenv:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
eddie@eddie-ubuntu ~ [1]$ pyenv local 3.7.3
eddie@eddie-ubuntu ~ $ python --version
Python 3.7.3
eddie@eddie-ubuntu ~ $ pyenv version
3.7.3 (set by /home/eddie/.python-version)
eddie@eddie-ubuntu ~ $ vox new demo-3.7.3
Using alias for vox in .xonshrc
Using pyenv-detected python to create new environment: /home/eddie/.pyenv/versions/3.7.3/bin/python
Creating environment...
Environment 'demo-3.7.3' created. Activate it with "vox activate demo-3.7.3".

eddie@eddie-ubuntu ~ $ pyenv local 2.7.15
eddie@eddie-ubuntu ~ $ vox new demo
Using alias for vox in .xonshrc
Using pyenv-detected python to create new environment: /home/eddie/.pyenv/versions/2.7.15/bin/python
Creating environment...
New python executable in /home/eddie/.virtualenvs/demo/bin/python
Installing setuptools, pip, wheel...done.
Environment 'demo' created. Activate it with "vox activate demo".

eddie@eddie-ubuntu ~ $ vox activate demo
Using alias for vox in .xonshrc
Activated "demo".

(demo) eddie@eddie-ubuntu ~ $ python --version
Python 2.7.15
(demo) eddie@eddie-ubuntu ~ $ vox deactivate
Using alias for vox in .xonshrc
Deactivated "demo".

eddie@eddie-ubuntu ~ $ python --version
Python 2.7.15
eddie@eddie-ubuntu ~ $ vox activate demo-3.7.3
Using alias for vox in .xonshrc
Activated "demo-3.7.3".

(demo-3.7.3) eddie@eddie-ubuntu ~ $ python --version
Python 3.7.3

You can still override pyenv's current python when creating virtualenvs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
eddie@eddie-ubuntu ~ $ pyenv local 2.7.15
eddie@eddie-ubuntu ~ $ python --version
Python 2.7.15
eddie@eddie-ubuntu ~ [2]$ vox new -p /usr/bin/python demo-system-python
Using alias for vox in .xonshrc
Creating environment...
New python executable in /home/eddie/.virtualenvs/demo-system-python/bin/python
Installing setuptools, pip, wheel...done.
Environment 'demo-system-python' created. Activate it with "vox activate demo-system-python".
eddie@eddie-ubuntu ~ $ /usr/bin/python --version
Python 2.7.16
eddie@eddie-ubuntu ~ $ vox activate demo-system-python
Using alias for vox in .xonshrc
Activated "demo-system-python".
(demo-system-python) eddie@eddie-ubuntu ~ $ python --version
Python 2.7.16
(demo-system-python) eddie@eddie-ubuntu ~ $ vox deactivate
Using alias for vox in .xonshrc
Deactivated "demo-system-python".
eddie@eddie-ubuntu ~ $ python --version
Python 2.7.15

I'll upload the code sometime, and I'll update this page with it, hopefully, but you've got what you need :)