Contents
1 Chapter

Getting started and uv setup

Build your first project with modern Python and uv, with type hints and the latest Python workflow from the start.

The opening chapter of the book. We set the two standards that run through the whole book — Python 3.14 and uv — from the start and use them to build your first project. From Chapter 2 onward, every example runs on top of the environment built in this chapter.

The later operations chapters in Part 5 (type checkers, logging, library publishing, CLIs) all end up extending the same pyproject.toml file you create in this chapter. So this chapter carries more weight than a typical “Part 1 Chapter 1”. Take your time and follow along.

What changed in Python? #

Python has changed a lot by 3.14. The major changes, in short:

  • Type hints became a standard convention. Most library code carries types
  • match-case (3.10) — pattern matching, different in feel from JavaScript’s switch
  • Built-in generics (3.9) — you can write list[int], dict[str, int] directly
  • Union shorthand (3.10) — int | None instead of Optional[int]
  • exception group (3.11) — handle simultaneous exceptions with except*
  • free-threaded (3.13–3.14, PEP 703/779) — GIL-less builds reach official support
  • t-string (3.14, PEP 750) — template literals with deferred interpolation, distinct from f-strings
  • lazy annotations (3.14, PEP 649/749) — annotations are evaluated lazily by default

The toolchain has changed a lot too. Old flows like calling pip directly, requirements.txt, and manually activating virtualenv are barely used anymore. Instead, one tool — uv — takes over most of that role.

The procedure for moving old code over to the style of this book is collected separately in Appendix A (Moving old Python code to the modern style). Refer to Appendix A whenever you need it.

What is uv? #

uv is a Rust-based Python package and project manager built by Astral (the team behind Ruff). In one line:

pip + pip-tools + pipx + poetry + pyenv + virtualenv bundled into a single binary.

A short comparison of why it’s becoming the standard:

ItemExisting toolsuv
SpeedTens of seconds to minutesHundreds of ms
Python interpreter installpyenv separatelyOne line: uv python install
Project setuppython -m venv + source .venv/bin/activate + pip installuv init + uv add
Lock filerequirements.txt by hand / poetry.lockuv.lock automatic
Standard complianceVaries per toolPEP 621 pyproject.toml as is

This book uses uv as the default. Interpreters, virtual environments, dependencies, and execution — all handled with uv alone.

Install #

macOS / Linux:

install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

If you use Homebrew:

install via brew
brew install uv

Windows (PowerShell):

windows install
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

If you work in WSL, use the macOS/Linux command above as is. Python inside WSL and Python on the Windows side run separately, so sticking to one of them consistently is safer.

After installation, open a new terminal and check the version.

check version
uv --version
# uv 0.5.x  (or later)

Installing Python 3.14 #

uv installs Python interpreters directly. You don’t need pyenv separately.

install Python
uv python install 3.14

To list installed interpreters:

list installed Pythons
uv python list --installed
# cpython-3.14.x-macos-aarch64-none ...

This interpreter is permanently installed on your system, but it is not exposed directly on PATH. uv picks the right one per project automatically. It doesn’t mix with other Pythons on your system, so it’s safe.

First project #

Make an empty directory and initialize the project with uv init.

initialize project
mkdir hello-py
cd hello-py
uv init --python 3.14

The --python 3.14 is the key. This project is pinned to 3.14. Even if another project uses 3.12, they don’t interfere. Running the command creates these files:

generated files
hello-py/
├── .python-version    # 3.14
├── pyproject.toml     # project metadata + dependencies
├── README.md
└── main.py            # print("Hello from hello-py!")

If you open pyproject.toml:

pyproject.toml
[project]
name = "hello-py"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = []

It’s the PEP 621 standard format as is. Think of this file as the single definition of the project. Dependencies, Python version, build info, and tool settings (ruff/mypy, etc.) all gather here. You’ll rarely create requirements.txt, setup.py, or setup.cfg again.

When Part 5 covers library publishing (Chapter 32) and type-checker setup (Chapter 30), the work ends up extending the same pyproject.toml file.

First run #

Let’s run main.py as is. But not python main.py — it’s uv run main.py.

first run
uv run main.py
# Hello from hello-py!

What uv run does:

  1. Finds an interpreter that satisfies the requires-python in pyproject.toml (downloads automatically if missing)
  2. Automatically creates the project’s virtual environment (.venv/) and syncs it with uv.lock
  3. Runs the command inside that environment

You no longer need to activate the virtual environment manually (source .venv/bin/activate). The flow shifts to prefixing every command with uv run. It takes a moment to adjust, but once you’re used to it, accidents like forgetting to activate and installing packages into the system Python disappear.

Adding dependencies #

Use uv add to add a package.

add a dependency
uv add httpx

This single line does several things at once:

  • Adds httpx to dependencies in pyproject.toml
  • Records the exact version and hash in uv.lock
  • Installs it into .venv/

If you reopen pyproject.toml:

dependencies added
[project]
# ...
dependencies = [
    "httpx>=0.28.1",
]

Tools needed only at development time (test runners, linters) go into the --dev group.

dev dependencies
uv add --dev pytest ruff

These are excluded at deploy time and only installed during development. To remove a package:

remove
uv remove httpx

Once more — the whole flow #

zero-to-running in one go
# 1. Install the tool (once)
curl -LsSf https://astral.sh/uv/install.sh | sh

# 2. Create the project
uv init my-app --python 3.14
cd my-app

# 3. Add dependencies
uv add httpx
uv add --dev pytest

# 4. Run
uv run main.py
uv run pytest

Compared with the old flow, the volume shrinks this much.

old flow (for reference)
# Install Python with pyenv
pyenv install 3.14.0
pyenv local 3.14.0

# Create a venv
python -m venv .venv
source .venv/bin/activate

# Install dependencies
pip install httpx
pip install pytest
pip freeze > requirements.txt

# Run
python main.py

It does the same thing, but without a lock file reproducibility is poor, and pip freeze doesn’t distinguish direct vs. transitive dependencies. uv is lock-based from the start, so the exact same environment can be reproduced on another machine.

Using it at script scale — the lighter case #

When you only want to run one small script and even setting up a project feels heavy, there’s a mode where the script itself declares its dependencies.

hello.py
# /// script
# requires-python = ">=3.14"
# dependencies = ["httpx"]
# ///
import httpx

resp = httpx.get("https://httpbin.org/get")
print(resp.json())

To run:

standalone script
uv run hello.py

uv recognizes the dependencies inside # ///, builds a temporary environment automatically, and runs it. Useful for one-off scripts, automation, and small tools.

Chapter 33 on building CLI tools revisits how to choose between this script-scale mode and the full project mode.

Exercises #

A short hands-on to check that the environment of this chapter actually took hold.

  1. Use uv init to create a new project named weather-py, add httpx as a dependency, and inside main.py call https://httpbin.org/get and print the response JSON. Run it with uv run main.py.
  2. Add pytest to the project above as a --dev group dependency, and confirm that uv run pytest --version works. Open pyproject.toml and check that pytest is in the dependency-groups (or [tool.uv.dev-dependencies]) section yourself.
  3. Write a whoami.py in script-scale mode with a # /// script header (depends on httpx, calls https://httpbin.org/ip), and confirm that it runs in one line with uv run whoami.py without a separate project directory.

In one line: This book sets Python 3.14 and uv as the standard. The three commands uv init, uv add, and uv run make up the daily workflow, and every project setting lives in a single pyproject.toml. Manual virtual environment activation is no longer part of the workflow.

Next chapter #

In the next chapter, Chapter 2 Variables, basic types, and type hints, we cover the most basic — types and type hints — on top of the environment built in this chapter. Why write types in a dynamic language, modern syntax like int | None, built-in generics like list[int], all the way to mypy / pyright.

X