What Is pyproject.toml?
What is pyproject.toml. The file that names a Python project, its Python version, and the packages it needs. Plus uv.lock, .python-version, and requirements.txt.
Open a Python project and you'll usually find pyproject.toml in the folder. It names the project and lists the Python version and the packages the project needs, in the standard format that modern Python tools read.
Python beginners typically install Python packages one at a time, with whatever command a tutorial gives them. A list of a project's dependencies is only in the developer's mind. Experienced Python developers write the list in a pyproject.toml file, naming every package a project needs. A second file, uv.lock, records the exact versions, so the project builds the same way on another Mac, after you reinstall macOS, or when an AI agent works with the project. You rarely edit either file by hand, because the uv command will keep both up to date.
What's in a Project Folder?, the first article in this series, covers the other common files you'll find beside pyproject.toml in a project. Python projects are a typical part of setting up a Mac for coding. See the Mac development setup guide.
Python's many tools, and uv
Python has no single standard tool for managing a project. Python comes with pip, which installs packages, and venv, which makes a virtual environment. Over the years, developers added more tools for jobs Python leaves out: pyenv to switch Python versions, pipx for command-line tools, and Poetry, PDM, Hatch, and conda to manage whole projects. Each tutorial picks its own, which is why Python setup confuses so many beginners.
Python first standardized the pyproject.toml file in 2016. Since 2020, its official standards have also defined the file's [project] section, which lists the project's packages. The tools uv, Poetry, PDM, and Hatch all read it, so the list of packages carries over if you change tools.
The tool that has emerged as the favorite is uv, from the company Astral. It does the jobs of pip, pyenv, virtualenv, and pipx in one fast program. It also installs Python itself. I recommend it, so this article uses it in its examples. How to Install Python on Mac compares uv with other ways to get Python. Install uv on Mac shows how to install it. Python on Mac lists all my Python articles.
Why write down dependencies
A project's dependencies are the packages it needs to run. If the list exists only in your mind, the project works only on the Mac where you first installed them. Move to a new Mac, reinstall macOS, or share the project, and you're left guessing which packages to install. Written in pyproject.toml, the list travels with the project. One command installs everything on it:
$ uv sync
The list also shows you, and an AI agent, what the project depends on at a glance. You can spot a package you no longer use and remove it.
Why keep a lock file
The list in pyproject.toml usually accepts a range of versions, such as "cowsay 6.1 or later". That's flexible, but two installs a month apart can get different versions. A new release of one package, or of a package it needs in turn, can break a project that worked yesterday. The lock file, uv.lock, prevents that:
- The same versions everywhere -
uv syncinstalls exactly the versions inuv.lock, on your Mac, on someone else's Mac, or on a server. - Upgrades when you choose - nothing changes until you ask.
uv lock --upgrade-package cowsaymoves one package to its newest allowed version. Becauseuv.lockis in Git, you can see what changed and go back to the old file if the upgrade breaks something. - A check on downloads -
uv.lockalso records a fingerprint (a hash) of each package file, so uv can check that each download matches the file it recorded.
What's in a pyproject.toml file
Here is the whole pyproject.toml from the example Python project that the "Set Up Your Mac for Python" app offers (described in Starter Projects):
[project]
name = "python-example-project"
version = "0.1.0"
description = "A first Python project, created by Set Up Your Mac."
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"cowsay>=6.1",
]
The file is written in TOML, a plain text format with sections in square brackets and name = value lines. The [project] section gives the project's name, version, and description, names its README file, and says which versions of Python it supports. The dependencies list names the packages it needs. Here it specifies one package, cowsay, in version 6.1 or later.
Larger projects may have two more kinds of section. A [build-system] section names the tools that turn the project into a package other people can install. A [tool.<name>] section holds settings for one tool, such as [tool.uv] for uv.
To read the file on a Mac, open it in your code editor.
How pyproject.toml is created
The tool uv creates pyproject.toml when you start a project. Install uv on Mac shows how to install it. Then open a terminal application, make a new empty folder, change to it, and run:
$ uv init
The command creates pyproject.toml, .python-version, README.md, and a .gitignore file, and starts a Git repository. It doesn't create the project's .venv folder or a uv.lock file yet. Both appear the first time uv installs the project's packages, such as at your first uv run.
To add a package, use uv add. It installs the package and records it in the dependencies list of pyproject.toml:
$ uv add cowsay
uv remove takes a package out again. You can also edit pyproject.toml by hand, but then uv.lock no longer matches it until the next uv run, so uv add and uv remove are the easier way.
What is uv.lock?
pyproject.toml says which packages a project accepts, such as "cowsay 6.1 or later". uv.lock records exactly which version uv chose, for every package, including the packages that your packages need in turn. The lock file appears the first time uv installs the project's packages, such as at your first uv run. The uv tool updates it whenever you add or remove a package.
The file is plain text, but uv's documentation says it is managed by uv and should not be edited by hand. It also says the file should be saved in Git with the rest of the project, so anyone who runs the project, including you on your next Mac, gets the same versions.
You can delete uv.lock. The next uv run writes a new one. In a quick test on a Mac, the new file was identical to the old one. If newer versions of your packages have come out in the meantime, uv may choose them instead, so leave the file in place in a project that works.
What is a .python-version file?
.python-version holds one line, the version of Python the project uses, such as 3.14. According to uv's documentation, the file "tells uv which Python version to use when creating the project's virtual environment". Both uv init and uv python pin write it. Use uv python pin to set a different version:
$ uv python pin 3.14
This matters on a Mac because macOS doesn't include Python. The python3 command that appears after you install the Xcode Command Line Tools is an old version (3.9.6). You shouldn't use it for your own projects. With .python-version in the project, uv uses a newer version you chose. Other version managers read the same file, including pyenv, as shown in Install pyenv, and mise, once you turn on a setting, as shown in Python with Mise.
The name starts with a dot, so the Finder hides it. To see it, show hidden files on Mac.
What is requirements.txt?
requirements.txt is an older way to list a project's packages. You'll still see it in many tutorials. It is a plain list for pip, Python's original package installer, with one package on each line and # at the start of a comment. With the project's virtual environment active, python -m pip install -r requirements.txt installs everything on the list. What Is a .venv Folder? explains the virtual environment.
pyproject.toml does the same job in the standard format. The uv.lock file adds the exact versions, so a new uv project doesn't need a requirements.txt. If you have a project that uses one, this command moves its list into pyproject.toml:
$ uv add -r requirements.txt
Which files go in Git
Here's what you need to know about each file:
pyproject.toml- goes in Git. You change it, usually withuv addanduv remove.uv.lock- goes in Git. The uv tool writes it. You leave it to uv..python-version- goes in Git, so everyone uses the same Python version.requirements.txt- goes in Git, if the project has one..venv/- stays out of Git. It holds the installed packages, which uv rebuilds fromuv.lock. The.gitignorefile thatuv initwrites lists it (the .gitignore file article explains how that list works).
AI agents and pyproject.toml
pyproject.toml shows an AI agent a project's packages and Python version at a glance. The example Python project's AGENTS.md file tells the AI agent to add packages with uv add and remove them with uv remove, and never to use pip install, so pyproject.toml and uv.lock always change together. When an AI agent adds a package, look at the changes to both files before you commit them. To give an AI agent the same rule, write it in AGENTS.md, as explained in Agent Instructions.
JavaScript projects keep the same kind of list in package.json, which What Is package.json? explains.
Now you can open a Python project on your Mac, tell which file lists its packages and which records their versions, add a package with uv, and save the right files in Git.
Continue setting up your Mac
Don't miss the full visual roadmap and checklist that shows how to set up a Mac for software development, with all the essential tools and settings you might not yet know about.