xgord
xgord

Reputation: 4776

What shebang to use for Python scripts run under a pyenv virtualenv

When a Python script is supposed to be run from a pyenv virtualenv, what is the correct shebang for the file?

As an example test case, the default Python on my system (OS X) does not have pandas installed. The pyenv virtualenv venv_name does. I tried getting the path of the Python executable from the virtualenv.

pyenv activate venv_name
which python

Output:

/Users/username/.pyenv/shims/python

So I made my example script.py:

#!/Users/username/.pyenv/shims/python
import pandas as pd
print 'success'

But when I tried running the script (from within 'venv_name'), I got an error:

./script.py

Output:

./script.py: line 2: import: command not found
./script.py: line 3: print: command not found

Although running that path directly on the command line (from within 'venv_name') works fine:

/Users/username/.pyenv/shims/python script.py

Output:

success

And:

python script.py # Also works

Output:

success

What is the proper shebang for this? Ideally, I want something generic so that it will point at the Python of whatever my current venv is.

Upvotes: 140

Views: 81805

Answers (8)

clayweb
clayweb

Reputation: 1

In a Linux/MacOS env, if you want to have your python directly executable and accomplish the source activate and reference the right python and libraries, you can skip a lot of heartache of complex bash or environment variable work with a simple "shebang" that references directly into your virtual environment via the python symbolic link.

If your python virtual environment was created with python -m venv myVenv while in /usr/local/something then you can get the virtual env with a shebang like this:

#!/usr/local/something/myVenv/bin/python3

If your virtual environment was created with something like pyenv virtualenv 3.12.8 myVenv then your shebang can look like this: #!/home/<username>/.pyenv/versions/myVenv/bin/python

Of course you don't need to have your pyenv directory tied and hidden away for just one user. I prefer to have it system-wide.

And yes, you'd need to have your script executable (chmod +x) for the shebang to do anything.

Upvotes: 0

shawn oscar ning
shawn oscar ning

Reputation: 51

If you want just a single script with a simple selection of your pyenv virtualenv, you may use a Bash script with your source as a heredoc as follows:

#!/bin/bash
PYENV_VERSION=<your_pyenv_virtualenv_name> python - $@ <<EOF
import sys
print(sys.argv)
exit
EOF

I did some additional testing. The following works too:

#!/usr/bin/env -S PYENV_VERSION=<virtual_env_name> python

Upvotes: 2

PydPiper
PydPiper

Reputation: 498

To expand this to an answer, yes, in 99% of the cases if you have a Python executable in your environment, you can just use:

#!/usr/bin/env python

However, for a custom venv on Linux following the same syntax did not work for me since the venv created a link to the Python interpreter which the venv was created from, so I had to do the following:

#!/path/to/the/venv/bin/python

Essentially, however, you are able to call the Python interpreter in your terminal. This is what you would put after #!.

Upvotes: 7

Goblinhack
Goblinhack

Reputation: 3088

It's not exactly answering the question, but this suggestion by ephiement I think is a much better way to do what you want. I've elaborated a bit and added some more of an explanation as to how this works and how you can dynamically select the Python executable to use:

#!/bin/sh
#
# Choose the Python executable we need. Explanation:
# a) '''\' translates to \ in shell, and starts a python multi-line string
# b) "" strings are treated as string concatenation by Python; the shell ignores them
# c) "true" command ignores its arguments
# c) exit before the ending ''' so the shell reads no further
# d) reset set docstrings to ignore the multiline comment code
#
"true" '''\'
PREFERRED_PYTHON=/Library/Frameworks/Python.framework/Versions/2.7/bin/python
ALTERNATIVE_PYTHON=/Library/Frameworks/Python.framework/Versions/3.6/bin/python3
FALLBACK_PYTHON=python3

if [ -x $PREFERRED_PYTHON ]; then
    echo Using preferred python $ALTERNATIVE_PYTHON
    exec $PREFERRED_PYTHON "$0" "$@"
elif [ -x $ALTERNATIVE_PYTHON ]; then
    echo Using alternative python $ALTERNATIVE_PYTHON
    exec $ALTERNATIVE_PYTHON "$0" "$@"
else
    echo Using fallback python $FALLBACK_PYTHON
    exec python3 "$0" "$@"
fi
exit 127
'''

__doc__ = """What this file does"""
print(__doc__)
import platform
print(platform.python_version())

Upvotes: 1

sadkeanu
sadkeanu

Reputation: 1

Maybe you need to check the file privileges:

sudo chmod +x script.py

Upvotes: -4

Dave X
Dave X

Reputation: 5137

As you expected, you should be able to use the full path to the virtual environment's Python executable in the shebang to choose/control the environment the script runs in regardless of the environment of the controlling script.

In the comments on your question, VPfB & you find that the /Users/username/.pyenv/shims/python is a shell script that does an exec $pyenv_python. You should be able to echo $pyenv_python to determine the real python and use that as your shebang.

See also: https://unix.stackexchange.com/questions/209646/how-to-activate-virtualenv-when-a-python-script-starts

Try pyenv virtualenvs to find a list of virtual environment directories.

And then you might find a using shebang something like this:

#!/Users/username/.pyenv/python/versions/venv_name/bin/python
import pandas as pd
print 'success'

... will enable the script to work using the chosen virtual environment in other (virtual or not) environments:

(venv_name) $ ./script.py
success

(venv_name) $ pyenv activate non_pandas_venv

(non_pandas_venv) $ ./script.py
success

(non_pandas_venv) $ . deactivate

$ ./script.py
success

The trick is that if you call out the virtual environment's Python binary specifically, the Python interpreter looks around that binary's path location for the supporting files and ends up using the surrounding virtual environment. (See per *How does virtualenv work?)

Upvotes: 19

DSLima90
DSLima90

Reputation: 2838

I don't really know why calling the interpreter with the full path wouldn't work for you. I use it all the time. But if you want to use the Python interpreter that is in your environment, you should do:

#!/usr/bin/env python

That way you search your environment for the Python interpreter to use.

Upvotes: 179

ephemient
ephemient

Reputation: 204768

If you need to use more shell than you can put in the #! shebang line, you can start the file with a simple shell script which launches Python on the same file.

#!/bin/bash
"exec" "pyenv" "exec" "python" "$0" "$@"
# the rest of your Python script can be written below

Because of the quoting, Python doesn't execute the first line, and instead joins the strings together for the module docstring... which effectively ignores it.

You can see more here.

Upvotes: 23

Related Questions