How to deploy a python app with dependencies similar to npm

If you have a ruby or node.js background, you probably came to appreciate gem and npm.
Python is lacking in this respect.
Current tools fail “There should be one– and preferably only one –obvious way to do it.”.
We show how to leverage pip and virtualenv in a non-obvious way.

There are several ways to deploy a python app with dependencies:

I decided to go with bare pip and virtualenv since both tools have to be mastered in anyway. The rest of this 1approach is simple enough to be described here. We assume the project has a standard directory layout:

README.rst
LICENSE
run.py
setup.py
requirements.txt
sample/__init__.py
sample/core.py
sample/helpers.py
docs/conf.py
docs/index.rst
tests/test_basic.py
tests/test_advanced.py

Now enter this directory and do (assuming it is a python3 project):

virtualenv -p python3 --prompt="(myproject)" env

This creates a directory env that holds your dependencies. You may want to add env to the .gitignore-file such that it does not end up under version control.
Next we activate the virtualenv.

source env/bin/activate

Note, that “(myproject)” became part of the prompt.
Then install the dependencies (into env) using pip:

pip install -r requitements.txt

Now you can run your project (here run.py) by:

python3 run.py
#or 
./run.py 

Once you finished working with your project you can re-enable the global python environment by typing:

deactivate

Great, so whenever you want to run your project you have to setup the virtualenv (i.e. by using source env/bin/activate).

You can wrap that in a shell-script:

#!/usr/bin/env bash

# activate env-directory relative to script
# cf. http://stackoverflow.com/questions/5137497/find-current-directory-and-files-directory
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$DIR/../env/bin/activate"

python "$DIR/run.sh"

Or you can enforce the virtualenv from within your python script:

#!/usr/bin/env python3

### activate virtualenv
import os
script_path = os.path.dirname(os.path.realpath(__file__))
activate_this = os.path.join(script_path,'env','bin','activate_this.py')
with open(activate_this) as file_:
   exec(file_.read(), dict(__file__=activate_this))
### 

# ... here comes your stuff

For convenience you may want to put the setup commands into a Makefile (make sure to indent by tab):

.PHONY: init requirements checkvirtualenv

init: requirements 
requirements: env checkvirtualenv requirements.txt
        pip3 install -r requirements.txt

env:
        virtualenv -p python3 env --prompt="(myproject)"

checkvirtualenv:
        test -n "$(VIRTUAL_ENV)" || \
        (echo "did you source env/bin/activate ?" && \
        exit 1)

and after checkout the next developer may do:

make #sets up env, then fails because it needs to be activated.
source env/bin/activate
make
#now the requirements are loaded 
python run.py

Further references