Python

Python Packaging & Distribution

Tutorial lengkap Python packaging dari nol — pip, venv, pyproject.toml, building packages, PyPI publishing, Docker Python, dan quiz interaktif dengan contoh kode praktis

1. Pengenalan Python Packaging

Python Packaging adalah proses mengemas kode Python agar bisa didistribusikan dan digunakan oleh orang lain. Ekosistem Python memiliki lebih dari 500.000 package di PyPI (Python Package Index), menjadikannya salah satu repositori package terbesar di dunia.

Memahami packaging sangat penting untuk: berbagi library yang Anda buat, mengelola dependensi proyek, deployment ke server, dan menjaga reproducibility (konsistensi) environment.

Ekosistem Packaging Python

Tool Fungsi Status
pipInstal dan kelola package dari PyPI✅ Standar
venvMembuat virtual environment terisolasi✅ Built-in
setuptoolsBuild backend untuk packaging✅ Mature
pyproject.tomlKonfigurasi packaging modern (PEP 621)✅ Standar baru
buildFrontend untuk mem-build package✅ PEP 517
twineUpload package ke PyPI✅ Standar
PoetryAll-in-one: dependency + packaging🟢 Populer
uvPackage manager ultra-cepat (Rust)🟢 Baru
PDMModern package manager (PEP 621)🟢 Alternatif
Diagram: Alur Python Packaging
┌───────────────────────────────────────────────────────┐
│              PYTHON PACKAGING WORKFLOW                │
│                                                       │
│  ┌──────────┐   ┌──────────┐   ┌──────────────────┐  │
│  │  Tulis   │   │  Build   │   │   Upload ke      │  │
│  │  Kode +  │──►│  Package │──►│   PyPI           │  │
│  │  pyproj  │   │  (wheel) │   │   (twine)        │  │
│  └──────────┘   └──────────┘   └────────┬─────────┘  │
│                                         │             │
│                                         ▼             │
│                                  ┌──────────────┐     │
│                                  │  pip install  │     │
│                                  │  oleh user    │     │
│                                  └──────────────┘     │
│                                                       │
│  Format Package:                                      │
│  • .whl (wheel) — format binary, cepat diinstal      │
│  • .tar.gz (sdist) — source distribution              │
│  • Keduanya dibutuhkan untuk PyPI                     │
└───────────────────────────────────────────────────────┘

2. pip: Package Installer

pip adalah package manager standar Python untuk menginstal dan mengelola package dari PyPI. pip sudah terinstal otomatis bersama Python 3.4+.

Perintah pip Dasar

Bash — pip Commands
# Instal package
pip install requests
pip install flask==3.0.3        # Versi spesifik
pip install "django>=4.2,<5.0"  # Rentang versi
pip install requests[security]  # Dengan extras

# Instal beberapa package sekaligus
pip install flask sqlalchemy pytest

# Instal dari file requirements
pip install -r requirements.txt

# Upgrade package
pip install --upgrade flask
pip install -U requests

# Uninstall package
pip uninstall requests

# List semua package yang terinstal
pip list

# Cek info package
pip show flask
# Output:
# Name: Flask
# Version: 3.0.3
# Summary: A simple framework for building web applications.
# Location: /path/to/lib/python3.12/site-packages

# Freeze requirements (export dependencies)
pip freeze > requirements.txt
# Output format:
# Flask==3.0.3
# Werkzeug==3.0.3
# Jinja2==3.1.4
# click==8.1.7
# ...

# Cek package yang outdated
pip list --outdated

requirements.txt

Text — requirements.txt
# requirements.txt — Pin versi untuk reproducibility
Flask==3.0.3
Flask-SQLAlchemy==3.1.1
Flask-Migrate==4.0.7
Flask-WTF==1.2.1
gunicorn==22.0.0
psycopg2-binary==2.9.9
python-dotenv==1.0.1

# Development dependencies (pisahkan)
# requirements-dev.txt
pytest==8.2.2
pytest-cov==5.0.0
black==24.4.2
ruff==0.4.8
mypy==1.10.0
⚠️ Selalu Gunakan Virtual Environment!

Jangan instal package secara global. Selalu gunakan virtual environment untuk menghindari konflik antar proyek. Tanpa venv, proyek A yang butuh Flask 2.x bisa konflik dengan proyek B yang butuh Flask 3.x.

3. Virtual Environments

Virtual environment adalah Python environment terisolasi yang memiliki package dan versi sendiri, terpisah dari environment global. Setiap proyek sebaiknya memiliki venv sendiri.

Bash — venv
# Membuat virtual environment
python -m venv venv

# Aktifkan virtual environment
# macOS/Linux:
source venv/bin/activate

# Windows (Command Prompt):
venv\Scripts\activate.bat

# Windows (PowerShell):
venv\Scripts\Activate.ps1

# Cek bahwa venv aktif (prompt berubah)
# (venv) $ which python
# /path/to/proyek/venv/bin/python

# Instal package di dalam venv
pip install flask
pip install -r requirements.txt

# Deactivate venv
deactivate

# Menghapus venv (cukup hapus folder)
rm -rf venv        # macOS/Linux
rmdir /s /q venv   # Windows

# .gitignore — JANGAN commit venv!
echo "venv/" >> .gitignore
echo "__pycache__/" >> .gitignore
echo "*.pyc" >> .gitignore
echo ".env" >> .gitignore

Alternatif: uv (Ultra Fast)

Bash — uv
# uv — Package manager ultra-cepat ditulis dalam Rust
# 10-100x lebih cepat dari pip!

# Instal uv
pip install uv
# atau: curl -LsSf https://astral.sh/uv/install.sh | sh

# Buat venv dengan uv
uv venv

# Instal package (sangat cepat!)
uv pip install flask sqlalchemy pytest

# Sync dari requirements
uv pip sync requirements.txt

# Compile requirements (seperti pip-compile)
uv pip compile requirements.in -o requirements.txt

# Jalankan script dengan uv
uv run python app.py

4. pyproject.toml: Konfigurasi Modern

pyproject.toml adalah file konfigurasi standar untuk proyek Python (PEP 621 + PEP 518). File ini menggantikan setup.py, setup.cfg, requirements.txt, dan berbagai file konfigurasi lainnya dalam satu file terpadu.

TOML — pyproject.toml (Lengkap)
# pyproject.toml — Konfigurasi proyek Python modern
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.backends._legacy:_Backend"

[project]
name = "my-awesome-library"
version = "1.0.0"
description = "Library Python yang luar biasa untuk melakukan X"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.10"
authors = [
    {name = "Budi Santoso", email = "budi@example.com"},
]
keywords = ["python", "library", "awesome"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Topic :: Software Development :: Libraries",
]

dependencies = [
    "requests>=2.31.0",
    "pydantic>=2.0",
    "rich>=13.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "pytest-cov>=5.0",
    "black>=24.0",
    "ruff>=0.4",
    "mypy>=1.10",
]
docs = [
    "sphinx>=7.0",
    "sphinx-rtd-theme>=2.0",
]

[project.urls]
Homepage = "https://github.com/budi/my-awesome-library"
Documentation = "https://my-awesome-library.readthedocs.io"
Repository = "https://github.com/budi/my-awesome-library"
Issues = "https://github.com/budi/my-awesome-library/issues"

[project.scripts]
# CLI entry point — bisa dijalankan dari terminal
my-cli = "my_library.cli:main"

# Konfigurasi tools
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --tb=short"

[tool.black]
line-length = 88
target-version = ["py312"]

[tool.ruff]
line-length = 88
select = ["E", "F", "I", "N", "W", "UP"]

[tool.mypy]
python_version = "3.12"
strict = true

[tool.setuptools.packages.find]
where = ["src"]

Struktur Proyek Modern

File Structure
my-awesome-library/
├── src/
│   └── my_library/
│       ├── __init__.py        ← Versi dan export utama
│       ├── core.py            ← Modul utama
│       ├── utils.py           ← Utilities
│       └── cli.py             ← CLI entry point
├── tests/
│   ├── __init__.py
│   ├── test_core.py
│   └── test_utils.py
├── docs/
│   └── index.md
├── pyproject.toml             ← Konfigurasi utama
├── README.md                  ← Dokumentasi proyek
├── LICENSE                    ← Lisensi
├── .gitignore                 ← Git ignore rules
└── CHANGELOG.md               ← Riwayat perubahan

5. Building Packages

Setelah proyek siap, kita perlu mem-build package menjadi distributable artifacts (wheel dan sdist) sebelum bisa dipublish ke PyPI atau diinstal oleh orang lain.

Bash — Build Process
# Instal build tool
pip install build

# Build package (menghasilkan wheel + sdist)
python -m build

# Output:
# Successfully built my-awesome-library-1.0.0.tar.gz
#                 and my_awesome_library-1.0.0-py3-none-any.whl

# Struktur output:
# dist/
# ├── my_awesome_library-1.0.0-py3-none-any.whl  ← Wheel (binary)
# └── my-awesome-library-1.0.0.tar.gz            ← Sdist (source)

# Instal package lokal (untuk testing)
pip install dist/my_awesome_library-1.0.0-py3-none-any.whl

# Instal dalam mode editable (development)
pip install -e .
# Editable mode: perubahan kode langsung ter-refleksi tanpa reinstall

# Instal dengan dev dependencies
pip install -e ".[dev]"

# Build dengan hatchling (alternatif modern)
pip install hatchling
# Ganti build-backend di pyproject.toml:
# build-backend = "hatchling.build"
python -m build

Entry Points (CLI)

Python — src/my_library/cli.py
# src/my_library/cli.py — CLI Entry Point
import argparse
from my_library import __version__
from my_library.core import proses_data

def main():
    parser = argparse.ArgumentParser(
        description='My Awesome Library CLI'
    )
    parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
    parser.add_argument('input', help='Input file atau data')
    parser.add_argument('-o', '--output', help='Output file')
    parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')

    args = parser.parse_args()

    if args.verbose:
        print(f"Memproses: {args.input}")

    hasil = proses_data(args.input)

    if args.output:
        with open(args.output, 'w') as f:
            f.write(hasil)
        print(f"Output disimpan ke {args.output}")
    else:
        print(hasil)

if __name__ == '__main__':
    main()

# Setelah install, bisa dijalankan dari terminal:
# my-cli data.txt -o output.txt -v

6. Publishing ke PyPI

PyPI (Python Package Index) adalah repositori resmi untuk package Python. Setelah di-publish, siapa saja bisa menginstal package Anda dengan pip install nama-package.

Persiapan

Bash — PyPI Preparation
# 1. Buat akun di https://pypi.org/register/
# 2. Buat API token di https://pypi.org/manage/account/token/

# Instal twine
pip install twine

# Verifikasi package sebelum upload
twine check dist/*
# Output: PASSED dist/my_awesome_library-1.0.0-py3-none-any.whl

# Upload ke TestPyPI dulu (testing)
twine upload --repository testpypi dist/*

# Upload ke PyPI (production)
twine upload dist/*

# Atau gunakan Trusted Publisher (tanpa API token):
# 1. Setup GitHub Actions + PyPI Trusted Publisher
# 2. Upload otomatis setiap push tag

Setelah Publish

Bash
# Install dari PyPI
pip install my-awesome-library

# Install dari TestPyPI
pip install --index-url https://test.pypi.org/simple/ my-awesome-library

# Install dari GitHub langsung
pip install git+https://github.com/budi/my-awesome-library.git

# Install dari local folder
pip install /path/to/my-awesome-library/

# Install dengan extras
pip install "my-awesome-library[dev]"
pip install "my-awesome-library[docs]"

Best Practices Publishing

Best Practice Alasan
Semantic VersioningGunakan format MAJOR.MINOR.PATCH (contoh: 1.2.3)
README.md yang bagusDokumentasi, instalasi, contoh penggunaan
LICENSEJelaskan lisensi (MIT, Apache 2.0, GPL, dll)
CHANGELOG.mdCatat perubahan di setiap versi
CI/CDOtomatisasi test dan publish dengan GitHub Actions
py.typed markerUntuk library yang mendukung type hints

7. Docker untuk Python

Docker mengemas aplikasi Python beserta semua dependensinya ke dalam container yang bisa dijalankan di mana saja. Docker memastikan aplikasi berjalan konsisten di development, testing, dan production.

Dockerfile Multi-Stage (Best Practice)

Dockerfile — Python App
# Dockerfile — Multi-stage build untuk Python
# Stage 1: Build
FROM python:3.12-slim as builder

WORKDIR /app

# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# Stage 2: Production
FROM python:3.12-slim

WORKDIR /app

# Copy installed packages from builder
COPY --from=builder /install /usr/local

# Copy application code
COPY . .

# Create non-root user (security best practice)
RUN adduser --disabled-password --gecos '' appuser
USER appuser

# Environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    FLASK_ENV=production

EXPOSE 5000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:5000/health || exit 1

# Run with gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--timeout", "120", "app:create_app()"]

docker-compose.yml

YAML — docker-compose.yml
# docker-compose.yml — Full stack Python app
version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "5000:5000"
    environment:
      - DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
      - SECRET_KEY=my-secret-key-change-this
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis

  db:
    image: postgres:15
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=secret
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  worker:
    build: .
    command: celery -A app.celery worker --loglevel=info
    environment:
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis

volumes:
  pgdata:

Best Practices Docker Python

.dockerignore
# .dockerignore — Jangan masukkan ini ke Docker image
__pycache__
*.pyc
*.pyo
.git
.gitignore
.venv
venv
.env
*.md
tests/
docs/
dist/
build/
.pytest_cache
.mypy_cache
.coverage
htmlcov/
Bash — Docker Commands
# Build image
docker build -t my-python-app:latest .

# Jalankan container
docker run -p 5000:5000 my-python-app:latest

# Jalankan dengan docker-compose
docker-compose up -d           # Background
docker-compose up --build      # Rebuild and start
docker-compose down            # Stop and remove
docker-compose logs -f web     # Lihat logs

# Masuk ke container (debugging)
docker exec -it container_id /bin/bash

# Cek ukuran image
docker images my-python-app
docker system df               # Cek disk usage Docker

# Cleanup
docker system prune -a         # Hapus semua yang tidak terpakai
💡 Tips Docker Python

Gunakan python:3.12-slim sebagai base image (bukan python:3.12 yang full) untuk mengurangi ukuran image. Selalu gunakan --no-cache-dir saat pip install di Docker. Gunakan multi-stage build untuk production image yang lebih kecil.

8. Dependency Managers

Selain pip + venv, ada beberapa dependency manager modern yang menyediakan fitur lebih lengkap seperti lock files, dependency resolution, dan build system terintegrasi.

Poetry

Bash — Poetry
# Instal Poetry
curl -sSL https://install.python-poetry.org | python3 -

# Buat proyek baru
poetry new my-project
cd my-project

# Tambah dependency
poetry add requests flask sqlalchemy
poetry add --group dev pytest black ruff

# Instal semua dependencies
poetry install

# Jalankan perintah dalam environment
poetry run python app.py
poetry run pytest

# Update dependencies
poetry update

# Export ke requirements.txt
poetry export -f requirements.txt -o requirements.txt

# Build dan publish
poetry build
poetry publish

# Lock file (poetry.lock) — commit ke Git!
# Memastikan semua developer menggunakan versi yang sama

Perbandingan Tools

Tool Lock File Kecepatan Built-in Build Learning Curve
pip❌ (pip freeze manual)🟡 Sedang🟢 Mudah
Poetry✅ poetry.lock🟡 Sedang🟡 Sedang
PDM✅ pdm.lock🟡 Sedang🟡 Sedang
uv✅ uv.lock🟢 Sangat Cepat🟢 Mudah
💡 Tips: Pilih Tool yang Tepat

Pemula: Gunakan pip + venv — sederhana dan banyak tutorial. Proyek menengah: Gunakan Poetry atau PDM — lock file dan dependency resolution lebih baik. Performa tinggi: Gunakan uv — sangat cepat karena ditulis dalam Rust. Tim besar: Poetry atau PDM dengan CI/CD yang terkonfigurasi.

9. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Python packaging:

Pertanyaan 1: Apa itu PyPI?

a) Python Package Installer — tool untuk menginstal package
b) Python Package Index — repositori online untuk package Python
c) Python Programming Interface — API untuk Python
d) Python Package Integration — tool untuk integrasi package

Pertanyaan 2: Mengapa virtual environment penting?

a) Agar Python berjalan lebih cepat
b) Untuk mengisolasi dependensi setiap proyek dan menghindari konflik versi
c) Untuk mengenkripsi kode Python
d) Tidak penting, hanya opsional

Pertanyaan 3: Apa perbedaan antara .whl dan .tar.gz?

a) .whl lebih besar ukurannya dari .tar.gz
b) .whl adalah format binary (cepat diinstal), .tar.gz adalah source distribution
c) Tidak ada perbedaan
d) .tar.gz hanya untuk Linux

Pertanyaan 4: File apa yang menggantikan setup.py dalam packaging modern?

a) setup.cfg
b) requirements.txt
c) pyproject.toml
d) MANIFEST.in

Pertanyaan 5: Mengapa menggunakan multi-stage build dalam Dockerfile Python?

a) Agar kode Python berjalan lebih cepat
b) Untuk mengurangi ukuran final image dengan tidak menyertakan build tools
c) Agar bisa menjalankan beberapa Python sekaligus
d) Untuk mengenkripsi source code