This episode takes your package to the world: the pyproject.toml standard, build backends like setuptools, Poetry, and Flit, building wheels, publishing to PyPI with twine, plus versioning strategies and managing releases.

Building a library that others can use is a major achievement. Episode 19 teaches packaging and distribution: how to declare a package with pyproject.toml, choose a build backend, build a wheel, and publish it to PyPI.
We'll also cover versioning and release strategies so your library is managed professionally. By the end of the episode, your package can be installed with pip install nama-package by anyone in the world.
pyproject.toml per PEP 621 is the source of truth for metadata:
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "belajar-utils"
version = "0.1.0"
description = "Library util untuk belajar packaging Python"
readme = "README.md"
requires-python = ">=3.12"
license = {text = "MIT"}
dependencies = ["requests>=2.31"]
[project.optional-dependencies]
dev = ["pytest>=8.0", "twine"][project] declares the name, version, description, and license. readme = "README.md" points to the documentation. The [project.optional-dependencies] section separates dev dependencies. This structure is read by all modern build backends.
setuptools is the most widely used build backend:
pip install buildpython3 -m buildpython3 -m build builds the package using the backend declared in pyproject.toml. The output is a dist/ directory containing a source distribution and a wheel. setuptools is flexible for almost any package structure.
Poetry and Flit provide alternative backends:
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"[build-system]
requires = ["flit_core >=3.2,<4"]
build-backend = "flit_core.buildapi"Poetry uses poetry.core.masonry.api and Flit uses flit_core.buildapi. All three produce the same artifacts — wheel and sdist. Choose the backend that's most comfortable for your team's workflow.
Builds produce two artifacts:
.whl): a binary format ready to install..tar.gz): a source distribution for rebuilding.Wheel is the faster-to-install format. Look at the contents of dist after building:
ls dist/ls dist/ shows files like belajar_utils-0.1.0-py3-none-any.whl and belajar-utils-0.1.0.tar.gz. The wheel name encodes the version, Python tag, and platform. py3-none-any means universal for Python 3 on all platforms.
Before publishing, test the package from the built wheel:
python3 -m venv .venv-test
source .venv-test/bin/activate
pip install dist/belajar_utils-0.1.0-py3-none-any.whlpip install dist/belajar_utils-0.1.0-py3-none-any.whl installs the package from the wheel into a clean venv. Test the import and functions in this venv. This guarantees the package doesn't depend on your development environment.
To publish, register at PyPI and create an API token. Store the token as an environment variable:
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="pypi-TokenRahasiaAnda"export TWINE_USERNAME="__token__" sets twine's credentials. Modern PyPI uses API tokens, not account passwords. Never write the token in code or committed files — follow the secret best practices from episode 11.
Upload the artifacts with twine:
pip install twine
twine check dist/*
twine upload dist/*twine check dist/* verifies the metadata before uploading. twine upload dist/* uploads the wheel and sdist to PyPI. After success, the package can be installed anywhere with pip install belajar-utils.
Test the upload on TestPyPI first to avoid mistakes:
twine upload --repository testpypi dist/*twine upload --repository testpypi dist/* uploads to the test server. After testing on TestPyPI, publish to the official PyPI. This habit prevents broken versions or wrong metadata from landing on the main index.
Use semantic versioning with the MAJOR.MINOR.PATCH format:
MAJOR.MINOR.PATCH
1.4.2These rules help users know how much risk an upgrade carries.
Every release is marked with a Git tag:
git tag -a v0.1.0 -m "Rilis pertama belajar-utils"
git push origin v0.1.0git tag -a v0.1.0 -m "Rilis pertama belajar-utils" marks a specific version in history. This tag serves as a reference for release notes and can trigger an automated publishing pipeline in episode 20. Managed releases make your library easy to maintain.
Key takeaways:
In the next episode, episode 20, we'll cover CI/CD, containerization, and deployment — CI pipelines with GitHub Actions, caching and matrix builds for multiple Python versions, multi-stage Docker, running Gunicorn and Uvicorn, and deployment targets like Cloud Run, AWS Lambda, and Kubernetes. Your application is ready to launch to production!