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’sswitch- Built-in generics (3.9) — you can write
list[int],dict[str, int]directly - Union shorthand (3.10) —
int | Noneinstead ofOptional[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+virtualenvbundled into a single binary.
A short comparison of why it’s becoming the standard:
| Item | Existing tools | uv |
|---|---|---|
| Speed | Tens of seconds to minutes | Hundreds of ms |
| Python interpreter install | pyenv separately | One line: uv python install |
| Project setup | python -m venv + source .venv/bin/activate + pip install | uv init + uv add |
| Lock file | requirements.txt by hand / poetry.lock | uv.lock automatic |
| Standard compliance | Varies per tool | PEP 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:
curl -LsSf https://astral.sh/uv/install.sh | shIf you use Homebrew:
brew install uvWindows (PowerShell):
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.
uv --version
# uv 0.5.x (or later)Installing Python 3.14 #
uv installs Python interpreters directly. You don’t need pyenv separately.
uv python install 3.14To list installed interpreters:
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.
mkdir hello-py
cd hello-py
uv init --python 3.14The --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:
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:
[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.
uv run main.py
# Hello from hello-py!What uv run does:
- Finds an interpreter that satisfies the
requires-pythoninpyproject.toml(downloads automatically if missing) - Automatically creates the project’s virtual environment (
.venv/) and syncs it withuv.lock - 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.
uv add httpxThis single line does several things at once:
- Adds
httpxtodependenciesinpyproject.toml - Records the exact version and hash in
uv.lock - Installs it into
.venv/
If you reopen pyproject.toml:
[project]
# ...
dependencies = [
"httpx>=0.28.1",
]Tools needed only at development time (test runners, linters) go into the --dev group.
uv add --dev pytest ruffThese are excluded at deploy time and only installed during development. To remove a package:
uv remove httpxOnce more — the whole flow #
# 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 pytestCompared with the old flow, the volume shrinks this much.
# 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.pyIt 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.
# /// script
# requires-python = ">=3.14"
# dependencies = ["httpx"]
# ///
import httpx
resp = httpx.get("https://httpbin.org/get")
print(resp.json())To run:
uv run hello.pyuv 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.
- Use
uv initto create a new project namedweather-py, addhttpxas a dependency, and insidemain.pycallhttps://httpbin.org/getand print the response JSON. Run it withuv run main.py. - Add
pytestto the project above as a--devgroup dependency, and confirm thatuv run pytest --versionworks. Openpyproject.tomland check thatpytestis in thedependency-groups(or[tool.uv.dev-dependencies]) section yourself. - Write a
whoami.pyin script-scale mode with a# /// scriptheader (depends onhttpx, callshttps://httpbin.org/ip), and confirm that it runs in one line withuv run whoami.pywithout 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, anduv runmake up the daily workflow, and every project setting lives in a singlepyproject.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.