Python
Python Set up Python with pyenv, manage virtual environments and dependencies using uv, pipenv, or poetry, and keep your projects up to date on macOS
Languages Quest #4 Beginner

Python

Set up Python with pyenv, manage virtual environments and dependencies using uv, pipenv, or poetry, and keep your projects up to date on macOS

pythonpyenvuvpipenvpoetryvirtualenvdependencies
Download as:

What is Python?

Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. Its high-level built-in data structures, combined with dynamic typing and dynamic binding, make it very attractive for Rapid Application Development, as well as for use as a scripting or glue language to connect existing components together.

Pythonโ€™s simple, easy-to-learn syntax emphasizes readability and therefore reduces the cost of program maintenance. Python supports modules and packages, which encourages program modularity and code reuse.

Prerequisites

Install Pyenv

Pyenv intercepts Python commands using shim executables injected into your PATH, determines which Python version has been specified by your application, and passes your commands along to the correct Python installation.

Why use pyenv?

  • Isolate Python versions
  • Change the global Python version on a per-user basis
  • Support for per-project Python versions
  • Override the Python version with an environment variable

Install dependencies:

brew install openssl readline sqlite3 xz zlib tcl-tk

Install pyenv:

brew install pyenv

Close your terminal session and add pyenv to your ~/.zshrc or ~/.bashrc config file.

Install Python

Apple Silicon Note: Homebrew automatically installs to /opt/homebrew on Apple Silicon Macs. In most cases, no manual migration is needed - pyenv and other tools will work correctly out of the box.

Legacy Intel Migration (rarely needed)

If you previously had an Intel Mac with packages in /usr/local/bin and are experiencing conflicts, first verify whatโ€™s there:

# Check what's in /usr/local/bin before making changes
ls -la /usr/local/bin

Only proceed if you understand what files are present. Do not blindly move or delete system directories - this can break macOS utilities and other software. Instead, reinstall conflicting tools via Homebrew:

# Reinstall specific tools via Homebrew (preferred approach)
brew reinstall <package-name>

List all available versions:

pyenv install --list

List versions by base version (e.g., v3):

pyenv install --list | grep -E " 3\.([6-9]|1[0-9])"

Install a specific version:

pyenv install <version>

Close your terminal session and add Python exports to your ~/.zshrc or ~/.bashrc config file.

The order of the exports to the $PATH is important. From the bottom-up of the .zshrc file: SDKMAN! (BOTTOM), Python exports (ABOVE SDKMAN!).

Package Management

Choosing a Tool

Several tools exist for managing Python packages and virtual environments. Here is how they compare:

ToolPurposeNotes
uvFast Python package installer, resolver, and project managerModern Rust-based tool that replaces pip, virtualenv, and more
pipenvDependency management and virtual environmentsReplaces pip + virtualenv + requirements.txt workflow
poetryDependency management, packaging, and publishingAll-in-one tool with pyproject.toml-based configuration

For most workflows, uv is the recommended tool due to its speed and comprehensive feature set. Pipenv and Poetry are mature alternatives with large ecosystems.

Install a Package Manager

# Install uv
brew install uv

Verify:

uv --version

Install Packages

# Add a production dependency
uv add <package-name>

# Add with a version constraint
uv add "requests>=2.28"

# Add a dev dependency
uv add --dev <package-name>

Uninstall Packages

# Remove a specific package
uv remove <package-name>

Config File Examples

# pyproject.toml
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "requests>=2.28",
    "flask~=3.0",
]

[dependency-groups]
dev = [
    "pytest>=7.0",
    "ruff>=0.1",
]

Virtual Environments

A Python virtual environment is an isolated directory tree that contains a Python installation and a set of additional packages. Virtual environments prevent dependency conflicts between projects by giving each project its own independent set of packages.

Create a Virtual Environment

uv venv

This creates a .venv directory in your project root. You can specify a Python version:

uv venv --python 3.12

If using a pyproject.toml with dependencies, create the environment and install everything in one step:

uv sync

Activate a Virtual Environment

With uv, you typically do not need to activate the environment manually. Instead, run commands through uv run:

uv run python script.py
uv run pytest

If you prefer manual activation:

source .venv/bin/activate

Deactivate a Virtual Environment

If you activated manually:

deactivate

If you used uv run, no deactivation is needed.

Generated Files

  • pyproject.toml โ€” Declares your projectโ€™s dependencies and metadata
  • uv.lock โ€” Records the exact versions of all dependencies for reproducible installs

Environment Location

uv creates the virtual environment in .venv within your project by default.

Updating Dependencies

Check What Is Outdated

uv pip list --outdated

Check if the lockfile is up to date:

uv lock --check

Update Individual Dependencies

Update within the existing constraint:

uv lock --upgrade-package <package_name>
uv sync

Change the constraint and install latest:

uv add <package_name>@latest

Update All Dependencies

uv lock --upgrade
uv sync

This re-resolves all dependencies while respecting constraints in pyproject.toml.

Version Specifiers

Package versions follow Semantic Versioning (SemVer):

major.minor.micro
  1  .  2  .  6
  • major โ€” Incremented for breaking changes
  • minor โ€” Incremented for new features (backwards compatible)
  • micro (patch) โ€” Incremented for bug fixes

All Python tools use PEP 440 version specifiers. The syntax is the same whether you write it in pyproject.toml or Pipfile:

Compatible Release ( ~= )

behave = "~=1.2.6"

Allows patch updates within the same minor version. ~=1.2.6 is equivalent to >=1.2.6, <1.3.0.

CurrentInstallableNot Installable
1.2.61.2.7, 1.2.91.3.0, 2.0.0

In this example, ~=1.2.6 would upgrade to 1.2.9 but not to 1.3.0 or 2.0.0.

Note: ~=1.2 (without a micro version) allows minor updates: >=1.2, <2.0.0.

Best for: Projects that want only patch-level updates within a known minor version.

Caret ( ^ ) โ€” Poetry Default

# pyproject.toml (Poetry)
[tool.poetry.dependencies]
behave = "^1.2.6"

Allows changes that do not modify the leftmost non-zero digit. ^1.2.6 is equivalent to >=1.2.6, <2.0.0. This is Poetryโ€™s default specifier when adding packages.

Best for: Poetry projects that want compatible updates. Functionally similar to ~= for most version numbers.

Exact Match ( == )

behave = "==1.2.6"

Only ever installs the exact version specified. A typical update command will not install a newer version.

Best for: Ensuring a specific known-good version is used. Requires manual version bumps.

Less Than ( < )

behave = "<1.2.6"

Ensures no version newer than the one specified will be installed.

Less Than or Equal ( <= )

behave = "<=1.2.6"

Matches versions older than or equal to the specified version.

Greater Than ( > )

behave = ">1.2.6"

Ensures any version newer than the one specified will be installed.

Greater Than or Equal ( >= )

behave = ">=1.2.6"

The minimum installed version is the one specified, and any newer version (including new major versions) will be installed if available.

Wildcard ( * )

behave = "*"

Always installs the latest available version. Use with caution in production.

Update Strategy

  1. Pin production dependencies with == or ~= (or ^ in Poetry) for stability
  2. Use ~= or ^ for libraries to allow compatible updates automatically
  3. Check outdated packages regularly to stay current with security patches
  4. Test after every update to catch regressions early
  5. Commit your lockfile (uv.lock, Pipfile.lock, or poetry.lock) to version control for reproducible builds

Upgrading Python

When a new Python release is available, you may want to upgrade the Python version used by your projectโ€™s virtual environment. There is no in-place upgrade path โ€” you need to recreate the environment with the new version.

Install the New Version

# List available versions
pyenv install --list | grep -E " 3\.(1[0-9])"

# Install the new version
pyenv install <new-version>

# Set it as the global version
pyenv global <new-version>

# Verify
python -V

uv can also install Python versions directly with uv python install 3.12.

Update Your Config File

Update requires-python in pyproject.toml:

[project]
requires-python = ">=3.12"

Remove the Old Environment

rm -rf .venv

Recreate the Environment

uv venv --python 3.12
uv sync

Verify:

uv run python -V

When to Upgrade

  • Security patches (e.g., 3.11.8 to 3.11.9) โ€” Upgrade promptly; backwards compatible
  • Minor releases (e.g., 3.11 to 3.12) โ€” Test your project first; some deprecations may affect your code
  • End-of-life versions โ€” Upgrade as soon as practical to maintain security

Check the Python Release Schedule to see which versions are currently supported.

Good to Know

Check Versions

List installed versions:

ls ~/.pyenv/versions/

Check which version is being used:

pyenv versions

The version with the * next to it is the currently used version.

List the current version:

python -V

Switch Versions

pyenv global <version>

If your Python version is not changing after setting it using pyenv:

  1. Update pyenv: brew update && brew upgrade pyenv
  2. Check for a .python-version file in your home directory and delete it: cd ~ && rm .python-version

Find Install Location

pyenv which python
# Output: /Users/<user>/.pyenv/versions/<version>/bin/python

which python
# Output: /Users/<user>/.pyenv/shims/python

Migrating from requirements.txt

If you have an existing project using requirements.txt:

# Import existing requirements
uv add -r requirements.txt

Troubleshooting

โ€pyenv: command not foundโ€

  1. Ensure pyenv is initialized in your shell config (~/.zshrc):

    export PYENV_ROOT="$HOME/.pyenv"
    export PATH="$PYENV_ROOT/bin:$PATH"
    eval "$(pyenv init -)"
  2. Re-source your shell:

    source ~/.zshrc

Python Version Not Changing

  1. Update pyenv:

    brew update && brew upgrade pyenv
  2. Check for local override files:

    rm ~/.python-version
  3. Verify shims are first in PATH:

    echo $PATH | tr ':' '\n' | grep pyenv

Build Errors During Installation

Missing dependencies can cause build failures:

# Install all required dependencies
brew install openssl readline sqlite3 xz zlib tcl-tk

# Set compiler flags for Apple Silicon
export LDFLAGS="-L$(brew --prefix openssl)/lib"
export CPPFLAGS="-I$(brew --prefix openssl)/include"

# Retry installation
pyenv install <version>

โ€œpip: command not foundโ€

pip should be included with Python. If missing:

# Reinstall Python with pyenv
pyenv uninstall <version>
pyenv install <version>

# Or install pip manually
python -m ensurepip --upgrade

Tool Cannot Find Python Version

# Install directly with uv
uv python install 3.12

# Or install via pyenv
pyenv install <version>

Lock Resolution Takes Too Long

uv is designed to be fast. If resolution is slow, check for conflicting constraints:

uv lock --verbose

Hash Mismatch Errors

uv lock
uv sync

Uninstalling Python

Uninstall via Pyenv

# List installed versions
pyenv versions

# Uninstall a specific version
pyenv uninstall --force <version>

Remove any code or aliases in your ~/.zshrc file that reference the uninstalled Python version.

Uninstall via Homebrew

# Check which Python packages are installed
brew list | grep python

# Uninstall Python and run diagnostics
brew uninstall python
brew doctor

To uninstall a specific version, use brew uninstall python@<version>.

Delete broken symlinks:

brew cleanup --prune-prefix

Remove remaining Python framework and binaries:

sudo rm -rf "/Applications/Python"
sudo rm -rf /Library/Frameworks/Python.framework
sudo rm -rf /usr/local/bin/python

Direct Install Removal

If Python is directly installed (without pyenv or Homebrew):

# Uninstall all pip packages
pip3 freeze | xargs pip3 uninstall -y

# Get the location
which python3

# Look in common installation directories
ls -1 /usr/local/bin/python* 2>/dev/null

Do not remove system Python from /usr/bin. On macOS, /usr/bin is protected by System Integrity Protection (SIP). Only remove third-party installations.

Clearing pip Cache

rm -rf ~/Library/Caches/pip

Resources

๐Ÿ”—
uv Documentation docs.astral.sh

Fast Python package installer, resolver, and project manager

๐Ÿ”—
Pipenv Documentation pipenv.pypa.io

Official Pipenv documentation with tutorials and API reference

๐Ÿ”—
Poetry Documentation python-poetry.org

Official Poetry documentation for dependency management and packaging

๐Ÿ”—
pyenv on GitHub github.com

Simple Python version management for installing and switching between versions

๐Ÿ”—
Semantic Versioning 2.0.0 semver.org

The specification that defines how package versions are structured

๐Ÿ”—
PEP 440 -- Version Identification peps.python.org

Python standard for version specifiers and dependency resolution

๐Ÿ”—
Python Release Schedule devguide.python.org

Status of all Python branches including end-of-life dates