Skip to content

Python venv Environments

This page covers Python's standard-library venv module.

Applied Project

Project Setup

The applied project is a small utility library called PyGuard Project. It blocks suspicious web requests before they reach application handlers without any runtime dependencies beyond the Python standard library. This makes it a good fit for venv because a pure-Python library shows clearly how one project-local environment can isolate build and development tools while keeping the installed package itself lightweight.

For a hands-on introduction to the environment itself, see The Isolation Awakens and its setup instructions.

Run the Project

Application, lint, build, and shell-exit commands are documented in the section README.

venv Environment Model

venv has shipped with Python since Python 3.3 in 2012, when it was introduced to give Python a standard built-in tool for project-level isolation. It creates a project-local directory with a private interpreter, pip, a package directory, console scripts, and a pyvenv.cfg file, while activation is mostly a PATH change so the environment-local interpreter resolves first even though the standard library still comes from the base Python installation. Treat that .venv/ directory as disposable rather than movable: virtual environments often record absolute paths, so rebuilding from dependency files is safer than relocating the environment directory byte-for-byte.

When to Use venv?

Because it is built in, lightweight, and close to standard Python packaging, venv is a good default for small web services, command-line tools, tutorials, libraries, and scripts whose dependencies come cleanly from PyPI. It is also a good fit when you want to inspect Python's environment mechanics directly.

Tradeoffs

Pros

  • ✅ Bundled with Python
  • ✅ Familiar to most Python users.
  • ✅ Lightweight folder-based environment that is easy to inspect and remove.
  • ✅ Clear PATH and sys.path behavior once the environment is activated.

Cons

  • ⚠️ Does not manage Python versions by itself.
  • ⚠️ Cannot install non-Python system libraries (e.g. compiler toolchains, etc.)
  • ⚠️ Does not manage dependencies or reproducible installs by itself
  • ⚠️ Zero portability once created (paths are hardcoded)

Install venv

venv is bundled with Python, so there is no separate PyPI package to install.

If venv support is absent, install the distribution-supported package with apt:

sudo apt install python3-venv

If Python is not installed yet, follow Install Python.

If Python is not installed yet, follow Install Python.

Environment Layout

Environment Definition

The project declares its package metadata and development dependencies in its pyproject.toml. The file is the source of truth for the package name, supported Python versions, build backend, runtime dependencies, and development dependency group. Creating .venv/ supplies the isolated interpreter, while pip or uv reads this file when the project is installed.

[project]
name = "pyguard"
requires-python = ">=3.9"
dependencies = []

[dependency-groups]
dev = ["karva>=0.0.1a5", "ruff>=0.15.12"]

Key Directories and Files

The exact directory names vary by operating system, but each venv still contains an environment-local interpreter, console scripts, a package directory, headers, and pyvenv.cfg.

.venv/
├── bin/
│   ├── activate
│   ├── activate.csh
│   ├── activate.fish
│   ├── pip
│   ├── pip3
│   ├── pip3.x
│   ├── python
│   ├── python3
│   └── python3.x
├── include/
│   └── python3.x/
├── lib/
│   └── python3.x/
│       └── site-packages/
│           ├── pip/
│           └── pip-*.dist-info/
├── lib64/
└── pyvenv.cfg
.venv\
├── Include\
├── Lib\
│   └── site-packages\
│       ├── pip\
│       └── pip-*.dist-info\
├── Scripts\
│   ├── Activate.ps1
│   ├── activate.bat
│   ├── pip.exe
│   ├── python.exe
│   └── pythonw.exe
└── pyvenv.cfg
.venv/
├── bin/
│   ├── activate
│   ├── activate.csh
│   ├── activate.fish
│   ├── pip
│   ├── pip3
│   ├── pip3.x
│   ├── python
│   ├── python3
│   └── python3.x
├── include/
│   └── python3.x/
├── lib/
│   └── python3.x/
│       └── site-packages/
│           ├── pip/
│           └── pip-*.dist-info/
└── pyvenv.cfg
  • Executable directory: on Linux and macOS, .venv/bin/ contains the virtual environment's Python executables, pip, and shell activation scripts. On Windows, the equivalent directory is .venv\Scripts\, which holds python.exe, pip.exe, and activation scripts such as Activate.ps1 and activate.bat. On Linux, a fresh venv commonly uses symlinks for the Python executables, so the environment-local python3 can still point back to the base interpreter while the surrounding environment changes where packages and scripts are installed.

    Inspect the executable directory:

    ls -lah .venv/bin/
    

    A shortened output focused on the executable entries looks like this:

    -rwxr-xr-x 1 user user  253 May  9 05:46 pip
    -rwxr-xr-x 1 user user  253 May  9 05:46 pip3
    -rwxr-xr-x 1 user user  253 May  9 05:46 pip3.12
    lrwxrwxrwx 1 user user    7 May  9 05:46 python -> python3
    lrwxrwxrwx 1 user user   16 May  9 05:46 python3 -> /usr/bin/python3
    lrwxrwxrwx 1 user user    7 May  9 05:46 python3.12 -> python3
    

    The arrows show the redirect chain. python points to python3, python3.12 points to python3, and python3 points to the base interpreter at /usr/bin/python3.

  • Header directory: Linux and macOS store headers under .venv/include/python3.x/, while Windows uses .venv\Include\. These headers are used when native extensions compile against the interpreter inside the virtual environment.

  • Package directory: Linux and macOS store installed packages under .venv/lib/python3.x/site-packages/, while Windows uses .venv\Lib\site-packages\. A fresh Python 3.x venv typically contains pip/ and its .dist-info metadata and no project dependencies yet.

  • lib64/: is a platform-specific library alias that appears on some Linux systems.

  • pyvenv.cfg: stores the base interpreter reference and flags such as whether system site-packages are visible. When you run the environment-local python3, CPython reads this file early during startup. The home key points back to the base Python installation that owns the standard library, while sys.prefix and sys.exec_prefix are redirected to the local .venv/ directory. The command entry can also record the path used when the environment was created, which is one reason a copied or renamed .venv/ can keep pointing at the original project location.

    A fresh pyvenv.cfg looks like this:

    home = /usr/bin
    include-system-site-packages = false
    version = 3.12.3
    executable = /usr/bin/python3.12
    command = /usr/bin/python3 -m venv /path/to/projects/proj1_pyguard/.venv
    

Activation and Import Path

  • Environment-local interpreter: activation puts the virtual environment's executable directory at the front of PATH. On Linux and macOS that means .venv/bin/; on Windows it means .venv\Scripts\. It does not change Python itself; it changes which executable the shell finds first when you run python, python3, py, or pip.

    Before activation:
    PATH=/usr/local/bin:/usr/bin:/bin:...
    python3 -> /usr/bin/python3
    
    After activation:
    PATH=/path/to/project/.venv/bin:/usr/local/bin:/usr/bin:/bin:...
    python3 -> /path/to/project/.venv/bin/python3
    
  • Environment-local packages: installed packages land under the environment-local site-packages directory, such as .venv/lib/python3.x/site-packages/ on Linux or macOS and .venv\Lib\site-packages\ on Windows. This keeps project dependencies separate from the base interpreter. A virtual environment does not replace the base standard library; it uses the base Python's standard library and puts the environment-local package directory ahead of any inherited package directories.

  • System package visibility: with the default include-system-site-packages = false setting in pyvenv.cfg, system-wide package directories are normally omitted. That is why a venv feels isolated even though it still relies on the base standard library.

  • Prefix inspection: sys.prefix points at the virtual environment, while sys.base_prefix still points at the underlying system Python.

    import sys
    print(sys.prefix)
    print(sys.base_prefix)
    

Development Workflow

The Dockerfile.devEnv image uses Ubuntu 24.04 with Python 3.12, python3-venv, unzip, uv, and cloudsmith-cli, then copies the project into /app. Choose the workflow that matches the state of the local mpe/proj1_pyguard image:

From the repository's projects/ directory, use build.sh to build and open the dedicated PyGuard development container. The script mounts the project at /app and its .build/ directory at /build for build artifacts:

./build.sh build \
    --path proj1_pyguard/Dockerfile.devEnv

From the repository's projects/ directory, run the existing image directly:

docker run -it \
    -v "$PWD/proj1_pyguard:/app" \
    -v "$PWD/proj1_pyguard/.build:/build" \
    mpe/proj1_pyguard \
    /bin/bash

Create the Environment

Create the project environment from the project root. Choose uv for the recommended workflow or venv + pip for the traditional workflow.

The following commands creates an in-project .venv, installs the project and all of its depencencies defined in the pyproject.toml:

uv sync --all-groups

On later runs, uv sync updates the .venv and only creates it if missing.

Create the project-local .venv environment:

python3 -m venv .venv

Activate the environment, upgrade pip, and install the project from pyproject.toml:

source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install .

Add Additional Packages

Add packages to the project environment and record them in pyproject.toml when using uv:

Add the respective package to dependencies entry in the [project] table:

dependencies = [
    "requests"
]

To synchronize an existing virtual environment with the definition file, update it from project root:

uv sync --all-groups

Add a runtime package:

uv add requests

Add a development-only package:

uv add --dev ruff

Run the Project

PyGuard is a library rather than a standalone command-line application. Use the project README for the middleware and FastAPI examples. After installation, verify that the package can be imported from the active environment:

python -c "import pyguard; print(pyguard.__file__)"

Inspect the Environment

Show the active prefixes:

python3 -c "import sys; print(sys.prefix); print(sys.base_prefix)"

Show where installed packages live:

python3 -c "import pyguard; print(pyguard.__file__)"

Show the folders where the interpreter imports the packages from

python3 -c "import sys; print(sys.path)"