Python Virtual Environments — Django 1.11 & 4.0 Conflict
One server running Django 1.11 and 4.0 causes AttributeError crashes.
20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
- A virtual environment is an isolated folder with its own Python binary and packages — nothing affects the global Python installation.
- Create with
python -m venv .venv; activate withsource .venv/bin/activate(Mac/Linux) or.venv\Scripts\activate(Windows). - When active, your terminal prompt shows
(.venv)— that's your visual confirmation isolation is working. pip freeze > requirements.txtcaptures exact package versions;pip install -r requirements.txtrecreates the environment anywhere.- Performance insight: Virtual environments add zero runtime overhead — they just change which Python binary your terminal uses.
- Production failure: committing the
.venvfolder to Git causes repository bloat (100+ MB) and broken setups across operating systems. - Biggest mistake: forgetting to activate before running
pip install— packages go into the global environment and your project fails on another machine.
Imagine you're a chef who cooks at two different restaurants. Restaurant A wants you to use only olive oil; Restaurant B insists on butter. You can't mix them up or both meals are ruined. A Python virtual environment is your own private kitchen for each project — it keeps that project's ingredients (libraries) completely separate from every other project's ingredients, so nothing ever gets mixed up or breaks each other.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
You clone a repo, run pip install, and suddenly your Django 3 project breaks because your system has Django 5. That’s what happens without virtual environments. They isolate project dependencies so each app gets exactly the packages it needs, and your global Python stays clean and stable.
Why Python Virtual Environments Are Non-Negotiable
A Python virtual environment is an isolated directory that contains its own Python interpreter, site-packages, and scripts. The core mechanic: it modifies the sys.path so that import statements resolve to packages installed inside that environment, not the global system Python. This prevents the classic 'Django 1.11 vs 4.0' conflict where two projects on the same machine require different major versions of the same library.
Each environment is a self-contained copy of the Python binary plus a lib/pythonX.Y/site-packages/ folder. When you activate it, your shell's PATH is prepended with the environment's bin/ directory, making python and pip point to the local versions. Deactivation restores the original PATH. No magic — just careful path manipulation.
Use a virtual environment for every project, without exception. In production, you'll typically recreate environments from a requirements.txt or Pipfile.lock inside a Docker container. The rule: one environment per project, never share between projects, and never install project dependencies globally. This eliminates version conflicts and makes builds reproducible.
pip install after activation.TemplateDoesNotExist errors for valid templates, or ImportError for new Django features.pip list inside the active environment to verify the installed versions match your lock file.Why Global Package Installation Is a Ticking Time Bomb
When you first install Python and run pip install requests, that library lands in a single global location on your computer. Every Python script you ever write shares that same copy. Sounds convenient — until it isn't.
Picture this: your first project uses requests version 2.20. Six months later you start a new project that needs requests version 2.31 because it uses a feature that didn't exist before. You upgrade globally. Your old project breaks. You downgrade to fix the old project. The new project breaks. You're stuck in a loop with no clean way out.
This exact scenario has a name in software: dependency conflict. It's not hypothetical — it's the single most common source of pain for Python beginners and professionals alike.
Virtual environments break this cycle permanently. Each environment is a self-contained folder that holds its own Python interpreter and its own set of packages. Project A's requests 2.20 and Project B's requests 2.31 can coexist peacefully on the same machine because they live in completely different folders and never meet.
Here's a subtle but important detail: virtual environments don't copy the entire Python installation. They create symlinks (Mac/Linux) or shortcuts (Windows) to the Python binary and then add their own site-packages folder. This keeps environments lightweight — typically 10-20 MB plus whatever packages you install.
/usr/local/bin/python3. When you activate a virtual environment later, this path will change to something inside your project folder — that's your proof the isolation is working.sudo pip install on the global Python. A security update to a system package required a new version of requests—and their app broke at 3 AM.pip install away from breaking something else.Creating and Activating Your First Virtual Environment
Python ships with a built-in module called venv (short for virtual environment). You don't need to install anything — it's already there, waiting. You create a virtual environment with a single terminal command, and from that moment on, that folder is your project's private kitchen.
The general pattern is: python -m venv <name-of-environment>. The name is just a folder name — most developers use venv or .venv (with a dot prefix so the folder is hidden by default on Mac/Linux).
Once created, you need to activate it. Activating tells your terminal 'for every command I run from now on, use the Python and pip inside this folder, not the global ones'. The activation command is slightly different depending on your operating system, but the effect is identical.
After activation your terminal prompt usually changes to show the environment name in parentheses — that's your visual confirmation that you're inside the bubble. When you're done working, deactivate drops you back to the global environment.
What's actually happening when you activate? The activation script modifies your shell's PATH environment variable, adding the .venv/bin directory to the front. Since shells search PATH in order, python now resolves to .venv/bin/python first. The VIRTUAL_ENV environment variable is also set so tools can detect which environment is active. The deactivate command restores the original PATH.
.venv\Scripts\Activate.ps1 might throw 'running scripts is disabled on this system'. This is a Windows security setting — it doesn't affect your code at all. Fix it by running Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser in PowerShell once. Alternatively, use Command Prompt where .venv\Scripts\activate.bat works without policy changes.pip install -r requirements.txt in the global Python, then python -m pytest also runs globally — but the packages are installed in the global site-packages. Everything passes locally because your environment was activated, but fails in CI.source .venv/bin/activate && python -m pytest or use python -m venv --clear to ensure a fresh environment per run.python -m venv .venvsource .venv/bin/activate (Mac/Linux) or .venv\Scripts\activate (Windows)(.venv) — if you don't see it, you're not inside the environment.Freezing Dependencies So Your Team Gets Identical Setups
Stop relying on pip list for reproducibility. pip list shows installed packages including those from the system or editable installs — it's a mess. pip freeze outputs only packages installed via pip in the exact format pip needs to reinstall them: package==version. That's what goes into requirements.txt. Period.
But pinning everything with == creates the pinning problem: your requirements.txt locks every transitive dependency, making upgrades a nightmare. You don't know which deps are direct vs indirect. Enter pip-compile from pip-tools. Create a requirements.in file listing only your direct dependencies (e.g., requests>=2.31.0). Run pip-compile requirements.in to generate requirements.txt with all transitive deps pinned. Devs edit .in, CI uses .txt. Workflow: pip-compile --upgrade-package requests to bump just one dep without blowing up the lockfile.
For supply chain security, install with hash verification: pip install -r requirements.txt --require-hashes. Generate hashes via pip-compile --generate-hashes requirements.in. This ensures every downloaded wheel matches a known SHA-256.
Dockerfile pattern for layer caching: `` COPY requirements.txt . RUN pip install -r requirements.txt --require-hashes COPY . . ` This caches the dependency layer until requirements.txt` changes, slashing rebuild times.
pip freeze is for output, pip list is for debugging. Never put pip list output into a requirements file.pip list --format=freeze and got system packages like apt or setuptools pinned. That breaks on different base images. Always use pip freeze from a clean virtualenv.requirements.in for direct deps, pip-compile for lockfile, --require-hashes for integrity, and Docker layer caching to keep builds fast.A Real-World Mini Project Tying It All Together
Theory is fine, but let's build something real inside a virtual environment so the whole workflow clicks. We'll write a small script that fetches the current weather for a city using the requests library — a package we install inside a virtual environment, not globally.
This example shows the complete developer workflow you'll repeat on every Python project you ever build: create environment → activate → install dependencies → write code → freeze requirements → deactivate. Once this muscle memory kicks in, you'll do it automatically.
Pay attention to the Python script itself too. It runs perfectly inside the virtual environment because requests is installed there. If you deactivate the environment and try to run the same script with the global Python, it would fail with ModuleNotFoundError: No module named 'requests' — unless you also happen to have it globally. That's virtual environment isolation working exactly as intended.
The script uses a free weather API (Open-Meteo) that requires no API key — perfect for learning. It demonstrates error handling for network issues and API errors, showing real production considerations even in a demo.
requests globally and share only your .py file, a colleague cloning your repo gets ModuleNotFoundError immediately. The professional fix is always: virtual environment + requirements.txt committed to the repo. Interviewers love this answer because it shows you think about reproducibility, not just making code work on your own machine.pip install --user.' The problem: --user still stores packages in a single user-wide location, just not system-wide. Two projects can still conflict.--user is for tools you want available across all projects (like black, mypy), not for project dependencies.--user for project dependencies — that's what virtual environments are for.which python — it should point inside .venv.pip install -r requirements.txt.Lock Your Environment With a Spec File, Not a Prayer
Pip freeze > requirements.txt is the bare minimum. But raw freeze is brittle. It dumps every dependency, including ones you don’t explicitly import. Your CI pipeline picks up different OS-level patches? Your environment breaks silently.
Use pip-compile from pip-tools instead. It creates a locked requirements.txt from a declarative requirements.in. That way you pin exact versions only for packages you actually need. Transitive dependencies get locked too, but you control the source.
This is what separates hobby projects from production pipelines. Locking transitive deps prevents a pandas-hotfix version from introducing an incompatible numpy build. Your teammates and your container build get exactly the same environment, down to the patch level.
Run pip-compile requirements.in once. Commit the generated requirements.txt. Never touch it manually.
Activate Once, Forget Forever: Shell Integration Saves Hours
Activating a virtual environment manually every time you open a terminal is friction. And friction kills workflow.
Ship it with direnv (or autoenv if you're legacy). Drop a .envrc file in your project root that reads:
layout python3
That’s it. Every time you cd into that directory, direnv automatically activates the venv. Leave the directory? It deactivates. No source ./venv/bin/activate. No muscle memory required.
Direnv works with pyenv too. You can set a specific Python version per directory:
use python 3.12
The shell prompt changes to show the active env. One less mental context switch. Onboarding a junior dev? They cd into the repo, and everything just works. No docs needed for setup.
Stop wasting terminal history on activation commands. Automate it.
venv vs virtualenv vs pipenv vs Poetry — Pick the Right Tool
## 1. venv (stdlib)
Zero dependencies. Ships with Python 3.3+. Use for simple scripts, CI pipelines, or when you need zero overhead.
``bash python -m venv .venv source .venv/bin/activate pip install requests ``
Weakness: No lock file, no dependency resolution beyond pip's flat list. You'll get different versions on different machines unless you manually pin everything. No build system. No publish.
When to use: One-off scripts, throwaway containers, CI where you control the environment exactly.
## 2. virtualenv
The old guard. Needed for Python 2 legacy. Has extra flags like --copies (copy instead of symlink) and --symlinks (force symlinks). Still maintained but not needed for Python 3+.
``bash pip install virtualenv virtualenv --python=python2.7 .venv source .venv/bin/activate ``
When to use: You're maintaining a Python 2 codebase in 2026. Otherwise, use venv.
## 3. pipenv
Pipfile + Pipfile.lock. Auto-manages .venv in your project directory. Was supposed to be the future, but the resolver is slow, the project had long abandonment periods, and trust is broken.
``bash pip install pipenv pipenv install requests pipenv shell ``
Current status (2026): Still maintained but not recommended for new projects. The lock file works, but the resolver can take minutes for complex graphs. Use only if you're already on it and migration cost is too high.
When to use: You're already on it and can't migrate. Otherwise, skip.
## 4. Poetry
The 2026 standard. Uses pyproject.toml, generates poetry.lock, handles build system, and can publish to PyPI. Fast resolver, deterministic builds, and first-class support for both libraries and applications.
```bash # Install curl -sSL https://install.python-poetry.org | python3 -
# New project poetry new my-project cd my-project
# Add dependency poetry add requests
# Install from lock poetry install
# Switch Python version poetry env use /usr/bin/python3.11
# Build and publish poetry build poetry publish ```
When to use: Any team project, library, or application that needs reproducibility and deployment.
## 5. Feature Comparison
| Feature | venv | virtualenv | pipenv | Poetry |
|---|---|---|---|---|
| Lock file | ❌ | ❌ | ✅ (Pipfile.lock) | ✅ (poetry.lock) |
| Dependency resolution | ❌ (pip) | ❌ (pip) | ✅ (slow) | ✅ (fast) |
| Build system | ❌ | ❌ | ❌ | ✅ |
| Publish to PyPI | ❌ | ❌ | ❌ | ✅ |
| Python version switching | ❌ | ❌ | ❌ | ✅ (poetry env use) |
| Speed | Instant | Instant | Slow | Fast |
| Zero dependencies | ✅ | ❌ | ❌ | ❌ |
## 6. Decision Rule
- Simple script →
venv - Team application →
Poetry - Maintaining legacy →
virtualenv - Nowhere →
pipenv(unless already on it, then stay until you can migrate)
pyenv + venv: Managing Multiple Python Versions Like a Pro
The Core Problem
Ubuntu 22.04 ships Python 3.10. Period. You need 3.12 for that shiny new library. System Python is frozen by apt. Don't touch it — break your OS. Enter pyenv.
pyenv Installation
``bash curl https://pyenv.run | bash ``
Add to ~/.bashrc: ``bash export PYENV_ROOT="$HOME/.pyenv" [[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" ``
How Shims Work
pyenv inserts $PYENV_ROOT/shims at the front of your PATH. When you type python, the shim intercepts the call, reads .python-version (or falls back to global), and routes to the correct Python binary. No aliases, no symlink chaos.
Essential Commands
```bash # Install a specific version pyenv install 3.12.3
# Set for current directory (creates .python-version) pyenv local 3.12.3
# Set global default pyenv global 3.11.9
# Verify python --version # should show 3.12.3 ```
.python-version is a plain text file containing just the version string. Commit it.
Combining pyenv with venv
``bash cd /your/project pyenv local 3.12.3 python -m venv .venv source .venv/bin/activate ``
This creates a venv using the pyenv-managed Python 3.12.3. The venv is tied to that exact interpreter. No more "but I installed 3.12" confusion.
Team Pattern
Commit .python-version to git. Add .venv/ to .gitignore. Every dev runs: ``bash pyenv install python -m venv .venv source .venv/bin/activate ``
Reproducible. Fast. No fights.
Docker Consideration
Inside Docker, skip pyenv. Use official images: ``dockerfile FROM python:3.12-slim ``
pyenv is a host-side tool. Containers are disposable — just pick the right base image. Don't install pyenv in production images.
sudo pip install or sudo apt install python3.X. You will break your system package manager. pyenv + venv is the only safe path for per-project isolation.pyenv install --skip-existing to avoid rebuilds. For Docker, skip pyenv entirely — use python:3.12-slim and pip install -r requirements.txt. pyenv is for dev machines and CI runners, not production containers..python-version, ignore .venv, and sleep better.uv: Modern Python Package and Project Manager
While pyenv and venv are powerful, they can be slow for large projects. Enter uv, a blazing-fast Python package and project manager written in Rust. uv aims to replace pip, pip-tools, and virtualenv with a single tool that is 10-100x faster. It uses a global cache and parallel downloads to dramatically speed up dependency resolution and installation.
To get started, install uv via pip: pip install uv. Then, create a new virtual environment with uv venv and activate it. uv can also manage Python versions, similar to pyenv, by running uv python install 3.11.
For dependency management, uv uses a pyproject.toml file. Initialize a project with uv init, then add dependencies with uv add requests. uv automatically creates a uv.lock file for reproducible builds. To sync dependencies, run uv sync, which installs exactly what's in the lock file.
Here's a quick example:
```bash # Create a new project uv init my_project cd my_project
# Add a dependency uv add django==4.2
# Install all dependencies uv sync
# Run a command in the virtual environment uv run python manage.py runserver ```
uv also supports scripts with inline metadata. Add a # /// script comment to a Python file, and uv can run it with dependencies automatically installed. This is perfect for one-off scripts or sharing code snippets.
For teams, uv's lock file ensures everyone gets identical environments. It also integrates with CI/CD pipelines, where you can use uv sync --frozen to fail if the lock file is out of date.
While uv is still relatively new, its speed and simplicity make it a compelling choice for modern Python development. It's especially useful for monorepos or projects with many dependencies.
uv sync --frozen to enforce that the lock file is up to date, preventing unexpected dependency changes from breaking builds.pip-tools: Lock File Workflow
For teams that prefer a lightweight, pip-compatible approach to lock files, pip-tools is an excellent choice. It builds on top of pip and provides two main commands: pip-compile to generate a lock file from high-level requirements, and pip-sync to install exactly those dependencies.
Start by creating a requirements.in file with your top-level dependencies (without versions, or with loose constraints):
`` # requirements.in django>=4.2,<5.0 requests ``
Then run pip-compile requirements.in to generate a requirements.txt with pinned versions and all transitive dependencies. You can also use pip-compile --output-file=requirements.lock for a different filename.
To install the locked dependencies, run pip-sync requirements.txt. This will remove any packages not in the lock file, ensuring a clean environment.
For development, you can have multiple input files, e.g., requirements-dev.in that includes requirements.in plus dev tools like pytest. Run pip-compile requirements-dev.in to generate a combined lock file.
Here's a typical workflow:
```bash # Create virtual environment python -m venv .venv source .venv/bin/activate
# Install pip-tools pip install pip-tools
# Compile dependencies pip-compile requirements.in
# Sync environment pip-sync requirements.txt ```
pip-tools integrates well with version control. Commit both the .in and the generated .txt files. When a teammate pulls changes, they run pip-sync to get the exact same environment.
One powerful feature is the ability to upgrade specific packages: pip-compile --upgrade-package django will update only Django and its dependencies, leaving others pinned.
pip-tools is ideal for projects that want a simple, auditable lock file without adopting a full project manager like Poetry. It's also compatible with any existing pip workflow.
.in and .txt files. In CI, run pip-sync requirements.txt to guarantee the exact same dependencies as development, avoiding 'works on my machine' issues.PDM and Poetry: Modern Python Project Management
For teams that want an all-in-one solution for dependency management, packaging, and virtual environments, PDM and Poetry are the leading modern tools. Both use pyproject.toml as the single configuration file and provide lock files for reproducible builds.
Poetry is the more established tool. It handles virtual environment creation automatically, resolves dependencies with a SAT solver, and generates a poetry.lock file. To start a new project: poetry new my-project. Add dependencies with poetry add django, and install with poetry install. Poetry also builds and publishes packages to PyPI.
PDM (Python Development Master) is a newer alternative that uses PEP 582's local packages directory (__pypackages__) instead of virtual environments, though it also supports venvs. It's faster than Poetry and has a plugin system. PDM uses pdm.lock for locking.
- Dependency groups (dev, test, docs)
- Python version constraints
- Scripts and entry points
- Publishing to PyPI
Here's a comparison workflow:
```bash # Poetry poetry new my-project cd my-project poetry add django==4.2 poetry install poetry run python manage.py runserver
# PDM pdm init pdm add django==4.2 pdm install pdm run python manage.py runserver ```
Both tools integrate with IDEs and CI/CD. For example, in GitHub Actions, you can use actions/setup-python with Poetry's cache to speed up installs.
Which one to choose? Poetry has a larger community and more tutorials, but PDM is faster and more standards-compliant (PEP 582). Both are excellent choices for modern Python projects.
For existing projects, you can migrate by running poetry init or pdm import to convert from requirements.txt.
poetry install --no-dev or pdm install --prod to install only production dependencies, reducing build times and attack surface.The Production Server That Had Django 1.11 and 4.0 Side by Side
AttributeError on every request. Rolling back the deployment fixed App A but broke App B's new features. The team was stuck in a loop.pip install django==4.0 ran for App B, it overwrote the global Django 1.11. App A's code, written for 1.11, tried to use 4.0 APIs and crashed. The server had no mechanism to isolate dependencies per application.python -m venv /opt/appA/venv and python -m venv /opt/appB/venv.
2. Installed each app's dependencies inside its own environment.
3. Updated the systemd service files to point to each app's virtual environment Python: ExecStart=/opt/appA/venv/bin/gunicorn ....
4. Verified isolation by checking which python inside each service — each pointed to its own .venv/bin/python, not the global.
5. Never touched the global Python again for application dependencies.- One server, one global Python is a single point of failure for dependency conflicts.
- Every application — even on the same server — needs its own virtual environment.
- Systemd, cron, and supervisor can all use the virtual environment's Python binary directly:
/path/to/venv/bin/python script.py. - Never install application dependencies globally on a production server. Your global Python should only contain what the OS needs.
ModuleNotFoundError: No module named 'requests' despite running pip install requests(.venv). Run which python (Mac/Linux) or where python (Windows). If it points to /usr/bin/python or C:\Python3, you're in global Python. Activate the environment first.ModuleNotFoundErrorpip freeze > requirements.txt and commit the file. Your colleague should run pip install -r requirements.txt after activating their environment. Also check that .venv is in .gitignore — never commit the folder itself.(.venv) but which python still points to a system pathGet-ExecutionPolicy. Run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser. On Mac/Linux, ensure you used source .venv/bin/activate, not just .venv/bin/activate (missing source runs the script in a subshell, which doesn't affect your current shell).pip freeze shows dozens of packages you don't recognise.venv, recreate it with python -m venv .venv, activate, then install ONLY what your project needs. Run pip freeze again — it should show only the packages you explicitly installed plus their dependencies.which python # macOS/Linuxwhere python # Windows (Command Prompt)/usr/local/bin/python or C:\Python3, you are NOT in a virtual environment. Activate it with source .venv/bin/activate or .venv\Scripts\activate.| File | Command / Code | Purpose |
|---|---|---|
| io | print("=== Python Executable Location ===") | Why Global Package Installation Is a Ticking Time Bomb |
| io | mkdir my_weather_app | Creating and Activating Your First Virtual Environment |
| io | source .venv/bin/activate | Freezing Dependencies So Your Team Gets Identical Setups |
| io | WEATHER_API_BASE_URL = "https://api.open-meteo.com/v1/forecast" | A Real-World Mini Project Tying It All Together |
| requirements.in | pandas | Lock Your Environment With a Spec File, Not a Prayer |
| .envrc | layout python3 | Activate Once, Forget Forever |
| uv_workflow.sh | pip install uv | uv |
| pip_tools_workflow.sh | python -m venv .venv | pip-tools |
| poetry_vs_pdm.sh | poetry new my-project | PDM and Poetry |
Key takeaways
python -m venv .venv. Activate with source .venv/bin/activate (Mac/Linux) or .venv\Scripts\activate (Windows).(.venv) when activepip freeze > requirements.txt captures exact versions. pip install -r requirements.txt recreates the environment anywhere..venv to Git.gitignore. Commit requirements.txt instead.requirements.txt for production, requirements-dev.txt for development tools.pip installwhich python to verify your environment is active.venv/bin/python.Interview Questions on This Topic
Why would you use a virtual environment instead of just installing packages globally? Can you describe a real scenario where not using one caused a problem?
ExecStart to the venv's Python binary, solve this.Frequently Asked Questions
20+ years shipping production Python across data and backend systems. Drawn from code that ran under real load.
That's Advanced Python. Mark it forged?
10 min read · try the examples if you haven't