Building and Publishing a Package
18 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Sharing code means giving others an installable file. Python tools build that file from your pyproject.toml. This lesson builds it, checks it and explains how it is published.
Build the package
Wheel is a built package file ending in .whl. pip installs it by unpacking it, with no build step, so installation is fast and runs none of the package's build code.
Users do not install your source folder. They install a built file. The build tool reads pyproject.toml and writes two files into a dist folder.
python -m pip install buildpython -m build... Successfully built shopkit-0.1.0.tar.gz and shopkit-0.1.0-py3-none-any.whl
The first file, ending .tar.gz, is the source distribution. The second, ending .whl, is the wheel. Prefer the wheel: pip installs it by unpacking, with no build step.
Look inside the wheel
A wheel is a zip file. List what it holds. Only the package and its metadata are inside. Your tests and notes are not.
python -m zipfile -l dist/shopkit-0.1.0-py3-none-any.whlFile Name Modified Size shopkit/__init__.py 2026-11-08 09:39:48 131 shopkit/__main__.py 2026-11-08 09:40:18 123 shopkit/pricing.py 2026-11-08 09:39:48 111 shopkit-0.1.0.dist-info/METADATA 2026-11-08 09:40:44 179 shopkit-0.1.0.dist-info/WHEEL 2026-11-08 09:40:44 91 shopkit-0.1.0.dist-info/entry_points.txt 2026-11-08 09:40:44 56 shopkit-0.1.0.dist-info/top_level.txt 2026-11-08 09:40:44 8 shopkit-0.1.0.dist-info/RECORD 2026-11-08 09:40:44 615
Example 1 · The source distribution
Runs in: Your terminal
python -m tarfile -l dist/shopkit-0.1.0.tar.gzshopkit-0.1.0/ shopkit-0.1.0/PKG-INFO shopkit-0.1.0/README.md shopkit-0.1.0/pyproject.toml shopkit-0.1.0/setup.cfg shopkit-0.1.0/src/ shopkit-0.1.0/src/shopkit/ shopkit-0.1.0/src/shopkit/__init__.py shopkit-0.1.0/src/shopkit/__main__.py shopkit-0.1.0/src/shopkit/pricing.py shopkit-0.1.0/src/shopkit.egg-info/ shopkit-0.1.0/src/shopkit.egg-info/PKG-INFO shopkit-0.1.0/src/shopkit.egg-info/SOURCES.txt shopkit-0.1.0/src/shopkit.egg-info/dependency_links.txt shopkit-0.1.0/src/shopkit.egg-info/entry_points.txt shopkit-0.1.0/src/shopkit.egg-info/top_level.txt
What this code does
- The source distribution holds your whole source tree plus metadata.
- Installing from it needs a build step, which is why wheels are preferred.
Example 2 · Reading a wheel file name
filename = "shopkit-0.1.0-py3-none-any.whl"
name, version, python_tag, abi_tag, platform_tag = filename.removesuffix(".whl").split("-")print(name)print(version)print(python_tag, abi_tag, platform_tag)shopkit 0.1.0 py3 none any
What this code does
- The name has five parts joined by dashes: package, version, Python tag, ABI tag, platform tag.
py3-none-anymeans pure Python code for any system, so one wheel serves everyone.
The dist-info folder holds the name, version and dependencies that pip reads. Check this list before publishing, so no private file slips in.
Version numbers
Semantic versioning writes versions as MAJOR.MINOR.PATCH. Raise PATCH for a fix, MINOR for a compatible feature, and MAJOR for a change that breaks users.
Each release needs a new version. Most projects use three numbers, so users can see how risky an upgrade is. PyPI never lets you upload the same version twice.
1.4.2 -> 1.4.3 fix a bug1.4.3 -> 1.5.0 add a feature1.5.0 -> 2.0.0 change that breaks usersExample 3 · Bumping a version
def bump(version, part): major, minor, patch = (int(n) for n in version.split(".")) if part == "major": return f"{major + 1}.0.0" if part == "minor": return f"{major}.{minor + 1}.0" return f"{major}.{minor}.{patch + 1}"
print(bump("1.4.2", "patch"))print(bump("1.4.3", "minor"))print(bump("1.5.0", "major"))1.4.3 1.5.0 2.0.0
What this code does
- Raising a part resets the smaller parts to zero.
- Split the string, convert to integers, then join back as text.
When a bug fix goes out, raise the last number and reset nothing. When a feature goes out, raise the middle number and set the last to zero.
Try the wheel before you share it
Install the built file in a new, empty virtual environment and use it. This proves that the wheel holds everything, not just your working folder.
python -m pip install dist/shopkit-0.1.0-py3-none-any.whlpython -c "import shopkit; print(shopkit.__version__)"Processing ./dist/shopkit-0.1.0-py3-none-any.whl Installing collected packages: shopkit Successfully installed shopkit-0.1.0 0.1.0
Example 4 · Install the wheel in a clean environment
Runs in: Your terminal
python -m pip install dist/shopkit-0.1.0-py3-none-any.whlProcessing ./dist/shopkit-0.1.0-py3-none-any.whl Installing collected packages: shopkit Successfully installed shopkit-0.1.0
What this code does
- pip unpacks the wheel straight into the environment.
- Nothing is built or compiled, so no build tools are needed.
If the import fails here, a file is missing from the package.
Publishing safely
API token is a long secret string that lets a tool act for your account on a package index. Create one for the index, store it outside your code and never commit it.
PyPI is the public package index. Practise on TestPyPI, a separate copy for experiments. The twine tool uploads the files from dist. It asks for an API token, never your password.
python -m pip install twinepython -m twine upload --repository testpypi dist/*Keep the token out of your code and out of Git. Store it in your tool's keyring or as a secret in your build system. Before the first release, check that the project name is free, add a licence, and read the wheel listing once more.
Next step
Check your understanding
8 quick questions