How to let myprogram.py use venv without setting any envs beforehand?

Question:

I’m a newbie to python(3), but not to programming in general.

I’d like to distribute a git repo with myprogram consisting of these files:

requirements.txt
myprogram.py
lib/modulea.py
lib/moduleb.py

My question is: What is the best-practice and least surprising way to let users run myprogram.py using the dependencies in requirements.txt? So that after git clone, and some idiomatic installation command(s), ./myprogram.py or /some/path/to/myprogram.py "just works" without having to first set magical venv or python3 environment variables?

I want to be able to run it using the #! shebang so that /path/to/myprogram.py and double-clicking it from the file manager GUI does the correct thing.

I already know I can create a wrapper.sh or make a clever shebang line. But I’m looking for the best-practice approach, since I’m new to python.

More details

I’m guessing that users would

git clone $url workdir
cd workdir
python3 -m venv .
./bin/pip install -r requirements.txt

And from now on this uses the modules from requirements.txt:

./myprogram.py

If I knew that the project directory was always /home/peter/workdir, I could start the myprogram.py with:

#!/home/peter/workdir/bin/python3

but I’d like to avoid hard-coding the project directory in myprogram.py.

This also seems to work in my tiny demo, but clearly this is brittle and not best-practice, but it illustrates what I’m trying to do:

#!/usr/bin/env python3
import os
import sys
print(os.path.join(os.path.dirname(__file__), 'lib', 'python3.10', 'site-packages'))

I’m sure I could come up with some home-grown shebang line that works, but what is the idiomatic way to do this in python3?

Again: After pip install, I absolutely refuse to have to to set any environment variables or call any setup code in future shells before running myprogram.py. (Unless that strongly conflicts with "idiomatic", which I hope isn’t the case)…

Asked By: Peter V. Mørch

||

Answers:

I do not see why you would need to hardcode anything. From your last snippet it looks like you are forcing the Python import system to include the target directory of the virtual environment you first create.

Based on your explanation, it seems you are using venv as your virtual environment manager. So long as your users install the dependencies in the virtual environment, and then activate the virtual environment before running the script, the dependencies will be ready for your module/script to use them.

This line you share: ./bin/pip install -r requirements.txt manually uses the package manager of the virtual environment you create with python3 -m venv .. Instead, you would want your user to create the environment (python3 -m venv example-env), activate the environment (source example-env/bin/activate) and then run the pip install command: python3 -m pip install -r requirements.txt.

The user of the package has to make sure that the environment is active before running the script.

Answered By: Victor Sandoval

Expanding @sinoroc’s comment into an answer:

I’ve looked at https://packaging.python.org/en/latest/tutorials/packaging-projects/ and also at "entrypoints", and this is the smallest example I can think of. Create an empty directory with these two files:

pyproject.toml:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "example_module_pmorch"
version = "0.0.1"

[project.scripts]
runme = "example_module_pmorch:cli_main"

src/example_module_pmorch/__init__.py:

def cli_main():
    print("I'm the entrypoint")

Now if I run this:

$ python3 -m venv .

# Adding -e during development is optional 
$ ./bin/pip install .

Then ./bin/runme does the right thing and prints I'm the entrypoint.

Answered By: Peter V. Mørch
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.