Module teruji dan test suite hijau — tetapi semua itu masih hidup di folder proyek kita. Langkah terakhir proyek ini adalah mengubah folder tersebut menjadi artefak standar yang bisa dipasang pip di komputer siapa pun, memverifikasinya seperti pengguna sungguhan, lalu menyiapkan langkah publikasi. Seluruh transkrip pada bagian ini — kecuali bagian unggah yang ditandai ilustratif — adalah hasil eksekusi nyata.
Melengkapi Metadata #
Versi pyproject.toml dari Bagian 1 cukup untuk testing, tetapi terlalu miskin untuk dipublikasikan: halaman PyPI nanti menampilkan README, pengguna perlu tahu lisensinya, dan kontributor perlu tahu di mana repository-nya. Perbarui pyproject.toml menjadi versi final:
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "example-package"
version = "0.1.0"
description = "Utilitas teks sederhana: slug URL, penghitung kata, dan peringkas teks"
readme = "README.md"
license = "MIT"
authors = [
{ name = "Budi Santoso", email = "[email protected]" },
]
requires-python = ">=3.10"
dependencies = []
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Text Processing",
]
[project.urls]
Homepage = "https://github.com/budi/example-package"
Issues = "https://github.com/budi/example-package/issues"
Changelog = "https://github.com/budi/example-package/blob/main/CHANGELOG.md"
[project.optional-dependencies]
dev = ["pytest>=8.0"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]Kunci-kunci baru yang ditambahkan:
readme = "README.md": menautkan berkas deskripsi; isinya di-render PyPI sebagai halaman depan package. Karena kunci ini merujuk sebuah berkas, README-nya harus benar-benar ada — buatREADME.mddi akar proyek:
# example-package
Utilitas teks sederhana untuk mengolah string sehari-hari: slug URL,
penghitung kata, dan peringkas teks.
## Instalasi
pip install example-package
## Penggunaan
from example_package import hitung_kata, ringkas, slugify
slugify("Belajar Python Dasar!") # belajar-python-dasar
hitung_kata("Belajar Python setiap hari") # 4
ringkas("Teks panjang sekali di sini", 20)license = "MIT": identifier SPDX — format metadata modern yang menggantikan cara lama berbasis classifier lisensi.authors: nama dan email penulis; gunakan data yang sanggup dihubungi pengguna, bukan data palsu, saat mempublikasikan package sungguhan.classifiers: kategori taxonomi PyPI yang membantu pencarian — status kematangan, versi Python yang didukung, dan topik. Perhatikan bahwa classifier lisensi tidak diperlukan lagi karena kuncilicensesudah memakai SPDX.[project.urls]: tautan pendukung yang tampil di bilah samping halaman PyPI.[project.optional-dependencies]: extra bernamadev— pengguna yang ingin ikut mengembangkan package memasangnya denganpip install example-package[dev], sedangkan pengguna biasa tidak perlu men-download pytest.
Membangun wheel dan sdist #
Alat build tidak kita pasang di lingkungan utama. Mengikuti Chapter Menggunakan pip, siapkan venv khusus di dalam proyek lalu pasang build di sana:
python3 -m venv .venv-build
source .venv-build/bin/activate
pip install buildJalankan dulu test suite untuk memastikan versi yang dikemas sehat, lalu bangun artefaknya:
python3 -m pytest
python -m buildOutput yang Dihasilkan (log internal setuptools — penyalinan berkas dan penulisan manifest — diringkas):
============================== 14 passed in 0.02s ==============================
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools>=61
* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools>=61
* Getting build dependencies for wheel...
* Building wheel...
Successfully built example_package-0.1.0.tar.gz and example_package-0.1.0-py3-none-any.whlBaris pertama adalah ringkasan test suite dari Bagian 3 — versi yang dikemas terbukti sehat. Baris-baris berikutnya adalah keluaran python -m build, dan di balik layar front-end build tidak pernah menyentuh lingkungan kita: python -m build membuat venv terisolasi, memasang setuptools di sana, lalu meminta backend menyalin src/example_package/ beserta metadata menjadi dua artefak di folder dist/. Urutannya menarik untuk dicatat: Building wheel from sdist berarti wheel dibangun dari dalam sdist yang baru saja dibuat — dengan begitu kedua artefak dijamin berasal dari sumber yang persis sama.
Hasil akhirnya, dua berkas di folder dist/:
dist/
├── example_package-0.1.0-py3-none-any.whl
└── example_package-0.1.0.tar.gzPerhatikan nama berkasnya: example_package dengan garis bawah — persis normalisasi nama yang dibahas pada Bagian 1, diterapkan otomatis pada nama artefak.
Memeriksa Isi Wheel #
Wheel hanyalah arsip ZIP dengan konvensi khusus. Karena wheel berisi kode Python murni, nama berkasnya membawa tag py3-none-any — berlaku untuk semua Python 3 di semua platform. Isinya bisa diperiksa langsung:
unzip -l dist/example_package-0.1.0-py3-none-any.whlOutput yang Dihasilkan (tanggal dan waktu pada kolom Date mengikuti waktu build di masing-masing mesin):
Archive: dist/example_package-0.1.0-py3-none-any.whl
Length Date Time Name
--------- ---------- ----- ----
2760 2026-10-12 23:44 example_package/__init__.py
1297 2026-10-12 23:45 example_package-0.1.0.dist-info/METADATA
91 2026-10-12 23:45 example_package-0.1.0.dist-info/WHEEL
16 2026-10-12 23:45 example_package-0.1.0.dist-info/top_level.txt
414 2026-10-12 23:45 example_package-0.1.0.dist-info/RECORD
--------- -------
4578 5 filesDua fakta penting tampak pada daftar berkas itu. Pertama, module example_package/__init__.py utuh di dalamnya — folder tests/ dan pyproject.toml sengaja tidak ikut karena hanya diperlukan saat pengembangan, bukan saat dipakai. Kedua, seluruh metadata hasil render tabel [project] — nama, versi, README, sampai classifiers — tersimpan di example_package-0.1.0.dist-info/METADATA, itulah berkas yang dibaca pip saat instalasi dan PyPI saat menampilkan halaman package.
Verifikasi Instalasi di Venv Bersih #
Langkah paling jujur untuk menguji wheel adalah berpura-pura menjadi pengguna: venv baru tanpa apa pun, lalu pasang wheel-nya. Buat venv uji, pasang wheel langsung dari berkasnya, lalu import dan jalankan:
python3 -m venv .venv-uji
source .venv-uji/bin/activate
pip install dist/example_package-0.1.0-py3-none-any.whl
python -c "
import example_package
from example_package import slugify
print(example_package.__version__)
print(slugify('Belajar Python Dasar!'))
"Output yang Dihasilkan:
Processing ./dist/example_package-0.1.0-py3-none-any.whl
Installing collected packages: example-package
Successfully installed example-package-0.1.0
0.1.0
belajar-python-dasarTiga baris pertama adalah keluaran pip, dua baris terakhir keluaran Python: versi 0.1.0 terbaca dari __version__, dan slugify() bekerja dari dalam venv yang sama sekali tidak tahu keberadaan folder proyek kita. Wheel terbukti bisa dipasang dan berfungsi.
Unggah ke TestPyPI #
Langkah berikut bersifat ilustratif — transkripnya bukan hasil eksekusi nyata karena unggahan membutuhkan akun milik pembaca sendiri. Alurnya mengikuti Chapter Mendistribusikan Python Package:
- Buat akun di
test.pypi.org— instance PyPI khusus latihan dengan basis data terpisah. - Buat API token di halaman pengaturan akun dengan scope upload. Token hanya ditampilkan sekali; contoh di sini memakai placeholder
YOUR_API_TOKEN. - Periksa kemasan dengan
twine check dist/*— statusPASSEDberarti metadata valid dan README akan ter-render. - Unggah kedua artefak:
twine upload --repository testpypi dist/*Output yang Dihasilkan (ilustratif):
Uploading example_package-0.1.0-py3-none-any.whl
100% ━━━━━━━━━━━━━━━━━━ 2.9/2.9 kB • 00:00:01 • 6.1 MB/s
Uploading example_package-0.1.0.tar.gz
100% ━━━━━━━━━━━━━━━━━━ 3.3/3.3 kB • 00:00:01 • 5.8 MB/s
View at:
https://test.pypi.org/project/example-package/0.1.0/Sebagai pengguna sungguhan, uji hasil unggahan dengan pip install --index-url https://test.pypi.org/simple/ example-package. Bila semua beres, alur yang sama diulang ke PyPI produksi: buat akun pypi.org, aktifkan two-factor authentication, buat token, lalu twine upload dist/* tanpa flag --repository.
Strategi Naik Versi #
PyPI tidak pernah menimpa versi yang sudah ada: mengunggah 0.1.0 untuk kedua kali ditolak, apa pun isinya. Karena itu setiap rilis baru wajib menaikkan version di pyproject.toml mengikuti semantic versioning dari Chapter Dependency Pinning:
| Perubahan | Versi baru | Contoh |
|---|---|---|
| Perbaikan bug tanpa mengubah API | naikkan PATCH | 0.1.0 → 0.1.1 |
| Function baru yang tidak merusak kode lama | naikkan MINOR | 0.1.0 → 0.2.0 |
| Perubahan yang merusak kode pengguna | naikkan MAJOR | 0.1.0 → 1.0.0 |
Setelah menaikkan versi, siklusnya selalu sama: jalankan test, bangun ulang dengan python -m build, uji instalasi di venv bersih, lalu unggah.
Proyek ini menutup perjalanan packaging kita pada Course Python: struktur, implementasi, testing, build, sampai distribusi — satu siklus hidup package yang utuh dan bisa diulang untuk library sungguhan berikutnya. Berkas pyproject.toml final tersedia pada bagian Berkas Sumber di bawah halaman ini.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
pyproject.toml
Unduh berkas[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "example-package"
version = "0.1.0"
description = "Utilitas teks sederhana: slug URL, penghitung kata, dan peringkas teks"
readme = "README.md"
license = "MIT"
authors = [
{ name = "Budi Santoso", email = "[email protected]" },
]
requires-python = ">=3.10"
dependencies = []
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Text Processing",
]
[project.urls]
Homepage = "https://github.com/budi/example-package"
Issues = "https://github.com/budi/example-package/issues"
Changelog = "https://github.com/budi/example-package/blob/main/CHANGELOG.md"
[project.optional-dependencies]
dev = ["pytest>=8.0"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]