Python 3.14 Β·
β verified by execution on 2026-08-01
Sooner or later you write something worth sharing. Publishing it means answering one question for every other machine in the world: what is this, and what does it need to run? That answer lives in a single file β pyproject.toml.
The Two Tables That Matter
A pyproject.toml has two important tables. [build-system] says which tool turns your source into an installable package. You are not writing that tool β you are naming it.
Because Python 3.11+ ships tomllib in the standard library, you can read that file with no dependencies at all. Run this:
The second table, [project], is the metadata humans and pip both read β the name on the PyPI page, the version, and what must be installed alongside it:
1.4.2 is not decoration. Each number is a promise to everyone who installed your package.
MAJOR breaks existing code, MINOR adds to it, PATCH fixes it. Only MAJOR is allowed to break someone's build.
Those rules are mechanical enough to write as code. Run it and watch each kind of change land differently:
python
def bump(version, kind): major, minor, patch = (int(p) for p in version.split('.')) if kind == 'major': return f'{major + 1}.0.0' if kind == 'minor': return f'{major}.{minor + 1}.0' return f'{major}.{minor}.{patch + 1}'print(bump('1.4.2', 'patch'))print(bump('1.4.2', 'minor'))print(bump('1.4.2', 'major'))
Output
1.4.3
1.5.0
2.0.0
Your output
Notice that a MAJOR bump resets everything to the right back to zero. βWe rewrote the internals but nothing brokeβ is a MINOR release β size is not the signal, breakage is.
The Trap: Versions Are Not Strings
This one bites experienced developers. Sort a list of versions the obvious way and the answer is wrong:
python
versions = ['1.10.0', '1.9.0', '1.2.0']print(sorted(versions))print(sorted(versions, key=lambda v: tuple(int(p) for p in v.split('.'))))
The first line compares text character by character. '1', '.', then '1' versus '2' β and since '1' < '2', version 1.10.0 sorts before 1.2.0. Version ten is treated as older than version two.
The fix is the second line: turn '1.10.0' into the tuple (1, 10, 0) and let Python compare numbers. Any time you pick βthe latest versionβ from a list, this is the bug waiting for you.
Before You Publish
One more field decides whether pip will even try to install your package:
The publishing commands themselves are short β build a distribution, then upload it:
python -m buildpython -m twine upload dist/*
Two things are permanent, so decide them deliberately. The name is claimed first-come, and a version number can never be reused β not even if you delete the release. Publish 1.0.0, find a typo a minute later, and the fix must go out as 1.0.1.
Watch Out
Myth: You still need a setup.py to publish.
Reality:pyproject.toml replaced it. setup.py had to execute code just to read configuration; static data is safer and lets tools inspect a package without running anything.
Myth:tomllib.load() works like any other file read.
Reality: It needs the file in binary mode β open('pyproject.toml', 'rb'). Text mode raises TypeError.
Check yourself
Which file does modern Python packaging use to declare build configuration?
Reveal answer
pyproject.toml β pyproject.toml is the standard. setup.py had to be executed just to read configuration; pyproject.toml is static data, so tools can inspect it safely.
You add a new optional argument to a function. No existing code breaks. Which part of 1.4.2 changes?
Reveal answer
MINOR, giving 1.5.0 β A backwards-compatible addition is a MINOR bump. MAJOR is reserved for changes that break existing code, PATCH for bug fixes.
What does sorted(['1.10.0', '1.2.0']) return?
Reveal answer
['1.10.0', '1.2.0'] β wrong, because it compares text β Sorting compares character by character, and '1' is less than '2', so '1.10.0' lands first. Sort on tuples of integers instead.
What does tomllib.load() require that trips people up?
Reveal answer
The file must be opened in binary mode ('rb') β tomllib.load() takes a binary file object. Passing a text-mode file raises TypeError β use open(path, 'rb').
Challenges
Challenge 1 +50 XP
Parse the pyproject.toml string and print the project name and version separated by a space (e.g. 'mytool 0.3.1').
python
Test 1 β expects "mytool 0.3.1\n"
Need a hint? (β25% XP)
tomllib.loads(toml) gives you a dict; the metadata lives under the 'project' key.