Skip to content

Dependencies and isolated environments

Exercise dependencies-and-isolated-environments-annex.pdf · PDF

Your program works perfectly on your laptop. You send it to a friend. On her laptop it crashes on the first line. Same code, same file. "But it works on my machine!" Every developer in the world has said that sentence. It is always true, and it never helps.

The program did not change. The machine did. Today you find out what your program silently needs from the machine, why two projects on the same computer can fight over it, and how professionals make sure the program that runs today also runs on a colleague's laptop, and on yours in six months.

What you will learn

  • Explain what a dependency is, and why two projects can ask for different versions of the same one
  • Read the error Debian gives to pip install, and why the system protects its own Python
  • Create, activate and leave a Python virtual environment, and see what it changes in PATH
  • Rebuild a project on a "second machine" from a requirements.txt file
  • Place virtual environment, container and virtual machine on one scale of isolation
  • Why a virtual environment is not a small virtual machine

Eleven lines that broke the web

22 March 2016. All over the world, programmers start their day and their projects refuse to build. Nobody has touched the code. The error says a package called left-pad cannot be found.

left-pad was eleven lines of JavaScript that add spaces to the left of a text, written by Azer Koçulu. He had a dispute with a company over the name of another of his packages, lost it, and in protest deleted everything he had published on npm, the big public store of JavaScript packages. Thousands of projects used left-pad without knowing it, among them Babel, a tool a large part of the web was built with. They never asked for it directly: something they used, used something that used it.

Whose fault was it? There is no clean answer. And that is the point.

A dependency is code written by somebody else that your program needs in order to run.

Almost no program is written from zero, and dependencies have dependencies of their own. One small missing piece deep in the chain is enough to stop everything above it.

flowchart TB
    APP["<b>Your project</b><br>the dish you cook"] --> B["<b>Babel</b><br>a tool you asked for"]
    B --> X["<b>Another package</b><br>that Babel uses"]
    X --> LP["<b>left-pad</b><br>11 lines nobody knew about"]
    LP -.->|"deleted on<br>22 March 2016"| GONE["Build fails"]

The tomato sauce problem

Back to the kitchen. Your recipe says "add the house tomato sauce". The sauce is another cook's recipe: a dependency. Two problems appear in every real kitchen:

  • The sauce recipe changes: version 2 has garlic. Your dish was tested with version 1.
  • Two dishes need different versions of the same sauce, and the shelf only has room for one jar with that label.

Careful: dependencies are not a Python thing

Every language has them, and a tool to fetch them: pip for Python, npm for JavaScript, Gradle and Maven for Java and Android.

Debian says no

On deb-lab, install the tools and try to install a package for the whole system:

$ sudo apt install python3-venv python3-pip
$ python3 --version
Python 3.13.5
$ pip install cowsay
error: externally-managed-environment
...

Read the whole message before searching the web. The cure is inside: it tells you to create a virtual environment using python3 -m venv.

Why does Debian refuse? Parts of the system itself are written in Python, and they rely on the versions that apt installed. A pip install for the whole system could replace one of them and break the system's own tools. The message even names the door it leaves open, --break-system-packages. The name is honest.

Not sudo pip install

It may look faster. It changes the Python your system depends on. The rule is one environment per project.

A shelf per project: the virtual environment

A virtual environment is a folder with its own copy of the Python commands and its own set of libraries, for one project.

In the kitchen, it is a private shelf per dish: each project keeps its own jar of sauce, in the version it needs.

mkdir ~/greeter
cd ~/greeter
python3 -m venv .venv            # create the environment in a hidden folder .venv
source .venv/bin/activate        # step into it

The prompt now starts with (.venv). Look at what changed:

(.venv) $ which python
/home/student/greeter/.venv/bin/python
(.venv) $ echo $PATH | tr ':' '\n' | head -1
/home/student/greeter/.venv/bin

That is all activate does: it puts one directory in front of PATH. The shell searches PATH in order, so python and pip now come from your project's folder.

Now install and run:

(.venv) $ pip install cowsay
Successfully installed cowsay-6.1
(.venv) $ pip list

pip list shows two packages, cowsay and pip. Write app.py with two lines:

import cowsay
cowsay.cow("Tramuntana Apps")

python app.py draws a cow that says Tramuntana Apps. Then leave the environment and try again:

(.venv) $ deactivate
$ python3 app.py
ModuleNotFoundError: No module named 'cowsay'

Same file, same machine, other environment. The library lives on the project's shelf, not on the system's.

Where they get stuck Why Fix
source venv/bin/activate fails The folder is .venv, hidden ls -a, and type the dot
pip in a new terminal installs nothing useful Each terminal starts outside the environment source .venv/bin/activate again
python: command not found Outside an environment, Debian's command is python3 Use python3, or activate

Careful: a virtual environment is not a virtual machine

It is a folder plus a change in PATH. No second operating system runs, nothing is virtualised. The word "virtual" is the only thing they share.

"It works on my machine": write the list

The cure is old and simple: write the shopping list down, with exact versions, and keep it next to the code.

(.venv) $ pip freeze > requirements.txt
(.venv) $ cat requirements.txt
cowsay==6.1

==6.1 is the important part: not "cowsay", but this cowsay. On another machine, one command rebuilds the same shelf:

pip install -r requirements.txt

You can rehearse it on deb-lab: a new folder plays Pau's laptop.

deactivate
mkdir ~/pau-laptop
cd ~/pau-laptop
cp ~/greeter/app.py ~/greeter/requirements.txt .
python3 -m venv .venv
source .venv/bin/activate
python app.py                     # fails: the new environment is empty
pip install -r requirements.txt
python app.py                     # the cow appears

You copied only two files. The environment itself (.venv, about 12 MB here) is never sent: it holds paths of your own machine, and it can be thrown away and rebuilt from the list in seconds.

Send to a colleague Never send
The code (app.py) The .venv folder
requirements.txt

Careful: the list does not write itself

You create requirements.txt with pip freeze, and again after every new install. And "latest versions everywhere" does not mean fewest problems: the version the project was tested with does.

When dependencies disagree

Sometimes two dependencies of the same project disagree with each other. Then the installer stops and tells you:

(.venv) $ pip install "requests==2.25.0" "urllib3==2.2.0"
...
The conflict is caused by:
    The user requested urllib3==2.2.0
    requests 2.25.0 depends on urllib3<1.27 and >=1.21.1
...
ERROR: ResolutionImpossible

Read it from the bottom up: requests 2.25.0 demands a urllib3 older than 1.27, and you asked for 2.2.0. Both cannot be on the same shelf, so nothing is installed. That is good: a half-installed project would be worse.

Three sizes of isolation

A virtual environment isolates the libraries of one language. Sometimes that is not enough: the program also needs a certain Python version, a database, a system library. There are bigger boxes.

What is isolated What is shared Cost
Virtual environment The libraries of one project Everything else: Python itself, the OS MB · seconds
Container Libraries, runtime, tools, files: all the software except the kernel The kernel of the host Tens of MB · seconds
Virtual machine A whole computer with its own kernel Only the hardware GB · minutes
flowchart TB
    subgraph VM ["Virtual machine: its own kernel"]
        subgraph CT ["Container: shares the host's kernel"]
            subgraph VE ["Virtual environment: one project's libraries"]
                LIB["cowsay==6.1"]
            end
        end
    end

In March 2013, at the PyCon conference in California, Solomon Hykes showed a tool called Docker in a five-minute talk. The idea: do not send only the program; send it packed together with everything it needs. You will build containers later in the course. Virtual machines you have used since Unit 2: deb-lab is one.

Sizing laptops for three people

Tramuntana Apps has three laptops and three people. Each laptop goes to exactly one person. The method: add up the official requirements of what each person runs at the same time (the numbers come from What a development machine needs).

Laptop RAM CPU SSD
A 8 GB 4 cores 256 GB
B 16 GB 8 cores 512 GB
C 32 GB 12 cores 1 TB
Person Runs at the same time RAM needed Laptop
Marta, Android developer Android Studio + one virtual device 16 GB minimum, 32 GB recommended C
Biel, back-end developer IntelliJ IDEA (8 GB) + a test VM like deb-lab (4 GB) 12 GB B
Pau, intern VS Code (1 GB) + Python virtual environments (a few MB) Well under 8 GB A

Only one assignment works for all three. Marta on B would start, since 16 GB is her minimum, but then Biel would have to live on A, where 12 GB do not fit. And if B had to serve Marta, what would you buy first? RAM.

Nobody asked which laptop is the best. The question was: which one is enough, for this person, for this work. That is what evaluating the needs of a system means.

Now do the whole session yourself, as Marta and then as Pau, on the Exercise sheet (button at the top of the page).

Key takeaways

  • A dependency is code by somebody else that your program needs; dependencies have their own dependencies, and one missing piece (like left-pad in 2016) can stop everything above it.
  • Debian refuses a system-wide pip install (externally-managed-environment) because system tools are written in Python. Never sudo pip install.
  • A virtual environment (python3 -m venv .venv) is a folder plus a change in PATH: activate puts .venv/bin first. One environment per project.
  • pip freeze > requirements.txt writes the list with exact versions; pip install -r requirements.txt rebuilds it anywhere. Send the code and the list, never .venv.
  • Three sizes of isolation: virtual environment (libraries), container (all the software, shares the kernel), virtual machine (its own kernel).
  • To size a workstation, add up what each person runs at the same time and pick what is enough, not the best.

Quick check

1. What is a dependency?
  • Code written by somebody else that your program needs to run
  • A bug that appears only on somebody else's machine
  • A Linux user who depends on the administrator
  • A kind of virus that spreads through packages

A dependency is someone else's code your program uses, like the house tomato sauce in your recipe. Dependencies often have dependencies of their own, which is how left-pad could break projects that never asked for it.

2. What does source .venv/bin/activate change?
  • It puts the environment's bin directory first in PATH
  • It starts a small virtual machine with its own kernel
  • It installs a fresh copy of Python from the Internet
  • It logs you in as root for the rest of the session

Activate only puts .venv/bin in front of PATH, so python and pip come from the project's folder. Nothing is virtualised; deactivate undoes it.

3. Why does Debian refuse a system-wide pip install?
  • It could replace libraries that system tools written in Python rely on
  • pip is not installed on Debian until you ask for it with apt
  • Debian has no network access for Python packages by default
  • Python programs are forbidden on Debian for security reasons

Parts of the system are written in Python and use the versions apt installed. The externally-managed-environment error protects them, and tells you the cure: a virtual environment.

4. What do you send a colleague so the project runs on her machine?
  • The code and requirements.txt
  • The code and the .venv folder
  • Only the .venv folder, which has everything
  • A screenshot of pip list on your machine

requirements.txt lists every package with its exact version, so pip install -r requirements.txt rebuilds the same environment. The .venv folder holds paths of your machine and is rebuilt in seconds, so it is never sent.

5. Which of these runs its own kernel?
  • A virtual machine
  • A container
  • A virtual environment
  • A requirements.txt file

A virtual machine is a whole computer with its own kernel; it shares only the hardware. A container shares the host's kernel, and a virtual environment only isolates one project's libraries.

6. You run pip install with requests==2.25.0 and urllib3==2.2.0 and get ResolutionImpossible. What happened?
  • requests 2.25.0 needs a urllib3 older than 1.27, so nothing was installed
  • Both packages were installed, but one of them will not work
  • The network failed halfway, so only requests was installed
  • urllib3 2.2.0 does not exist, so pip installed the latest one

This is a dependency conflict inside one project: two requirements that cannot be on the same shelf. pip stops and installs nothing, which is better than a half-installed project.

7. A program works in your virtual environment. After deactivate, python3 app.py fails. Select the two that are true.
  • The library was installed on the project's shelf, not for the whole system
  • Running source .venv/bin/activate again makes it work
  • The file app.py was changed by deactivate
  • deactivate uninstalled cowsay from the environment

The file did not change; the environment did. Outside .venv, Python cannot see the libraries installed inside it, so you get ModuleNotFoundError. Activating again puts .venv/bin back in front of PATH.

That closes Unit 5. Next: your network gets its first server. Unit 6 builds the Tramuntana office server, starting with Clients, servers and shared resources.