10xdev Book: Programming from Zero to Pro with Python
Chapter 12: Modern Package Management: uv, pip, and External Libraries
Chapter Introduction
Python’s Standard Library is fantastic, giving you lots of built-in tools. But the real magic, the thing that makes Python a powerhouse for everything from web development to AI, is its vast ecosystem of external libraries (or packages). These are toolkits created by the global Python community, offering specialized functionality for almost anything you can imagine.
Want to build a web app? Grab Flask or Django. Need to work with data? Pandas and NumPy are your friends. Want to talk to web APIs easily? requests is the go-to.
But using these external tools brings a new challenge: managing dependencies. Different projects often need different versions of the same library, leading to conflicts if you just install everything globally.
This chapter dives into the modern, professional way to handle this using:
- Virtual Environments: Creating isolated sandboxes for each project (often handled automatically by
uv). uv: The fast, all-in-one tool we introduced for managing environments and packages viapyproject.toml.pip(viauv): The underlying installer thatuvuses to fetch packages from the Python Package Index (PyPI).- Dependency Locking: Ensuring your builds are reproducible using
uv.lock. uvx: Running Python tools without installing them permanently.
Let’s learn how to harness the power of the Python ecosystem safely and efficiently!
1. Virtual Environments: Your Project Bubbles 🫧
We touched on this during setup, but it’s worth reinforcing: always use a virtual environment for every Python project.
Why? It creates an isolated “bubble” containing a specific Python version and its own set of installed libraries, separate from your system-wide Python and other projects. This prevents version conflicts – Project A can use libraryX v1.0 while Project B uses libraryX v2.0 on the same machine without issues.
Using uv:
The great thing about uv is that it often handles this automatically. When you run uv init or uv add in a project folder, it typically creates and manages a .venv folder for you behind the scenes. When you use uv run, it automatically executes your code within that environment.
Manual Activation (If Needed):
While uv hides much of this, it’s good to know how to manually activate if your editor or terminal doesn’t pick it up automatically:
- Windows:
\.venv\Scripts\activate - macOS/Linux:
source .venv/bin/activateYou’ll see(.venv)or similar appear in your prompt. Typedeactivateto exit.
(Note: The older way used python -m venv venv to create and required manual activation every time. uv streamlines this significantly.)
2. Managing Packages with uv
uv replaces the need for using pip directly for most common tasks and uses the modern pyproject.toml file to manage your project’s dependencies.
- Adding a Dependency: Installs the package into your virtual environment and adds it to
pyproject.toml.# Adds the latest version of 'requests' uv add requests # Adds a specific version uv add "requests<3.0.0" - Adding a Development Dependency: For tools only needed during development (like testing frameworks), use the
--devflag.uv add --dev pytestThis adds it to a separate
[tool.uv.dev-dependencies]section inpyproject.toml. - Installing from
pyproject.toml: When you clone a project or update dependencies,uv syncreads theuv.lockfile (orpyproject.tomlif no lock file exists) and installs the exact required versions, ensuring consistency.# Ensure your environment matches the lock file uv sync # Install dev dependencies too uv sync --dev - Removing a Dependency: Removes the package from the environment and
pyproject.toml.uv remove requests - Listing Installed Packages: Use the underlying
pipcommand viauv.uv pip list uv pip freeze # Shows exact versions for requirements files uv pip tree # Shows dependency tree
pyproject.toml and uv.lock:
pyproject.toml: Defines your direct dependencies and project settings. You edit this.uv.lock: Automatically generated/updated byuv. It locks down the exact versions of all packages (direct and indirect dependencies) for reproducible builds. You typically commit this file to version control.
(Contrast: Older projects used requirements.txt, often generated by pip freeze. uv can read these but prefers pyproject.toml for better structure and dependency resolution.)
3. Example: Talking to Web APIs with requests
Let’s use the requests library (make sure you’ve run uv add requests) to fetch data from a public API. An API (Application Programming Interface) is like a restaurant menu and waiter – it defines how your program can request specific information from a web server. We’ll use DummyJSON again.
import requests
import sys
# API endpoint for a specific product
api_url: str = "[https://dummyjson.com/products/1](https://dummyjson.com/products/1)"
try:
print(f"Fetching data from {api_url}...")
# Send an HTTP GET request
response = requests.get(api_url, timeout=10) # Added timeout
# Check for HTTP errors (4xx or 5xx status codes)
response.raise_for_status()
print("Request successful!")
# Parse the JSON response into a Python dictionary
data: dict = response.json()
# Safely extract data using .get()
title: str | None = data.get("title")
price: float | None = data.get("price")
if title and price is not None:
print("\nProduct Details:")
print(f" Title: {title}")
print(f" Price: ${price:.2f}")
else:
print("Title or price not found in response.")
except requests.exceptions.Timeout:
print(f"Error: Request timed out contacting {api_url}")
sys.exit(1)
except requests.exceptions.HTTPError as e:
print(f"Error: HTTP Error occurred: {e.response.status_code} {e.response.reason}")
sys.exit(1)
except requests.exceptions.RequestException as e:
# Catch any other requests-related errors (DNS, connection, etc.)
print(f"Error: Could not connect to API: {e}")
sys.exit(1)
except requests.exceptions.JSONDecodeError:
print("Error: Could not decode JSON response from server.")
sys.exit(1)
This shows the basic pattern: make a request, check for errors (raise_for_status), parse the JSON, and extract the data you need.
4. Running Tools On-the-Fly with uvx
What if you want to quickly run a Python command-line tool (like a code formatter black or linter ruff) without permanently installing it in your project or globally? uvx is the answer.
# Format a file using the latest 'black' formatter
uvx black my_script.py
# Run a specific version of the 'ruff' linter on your project
uvx --package ruff==0.1.9 ruff check .
# Start a temporary Jupyter Lab instance
uvx jupyter lab
Behind the scenes, uvx creates a temporary, cached environment, installs the tool there, runs it, and then cleans up. It’s incredibly handy for one-off tasks or trying out tools without cluttering your main environment.
5. Chapter Summary
- Always use virtual environments to isolate project dependencies;
uvsimplifies their creation and management. - Use
uv add <package>to install libraries and updatepyproject.toml. pyproject.tomldefines direct dependencies;uv.lockensures reproducible builds.- Use
uv syncto install dependencies based on the lock file. - External libraries like
requestsdramatically extend Python’s capabilities (e.g., for working with APIs). - Use
uvx <command>to run Python tools without installing them in your project.
You can now manage dependencies like a pro and leverage the vast Python ecosystem! But as your projects use more libraries and grow in complexity, how you structure your own code becomes even more critical. Let’s learn how to organize your project into logical modules and packages.