Giao diện
Distribution — Đóng gói và Phân phối Python Package
Tháng 3 năm 2023, một team infrastructure push release v2.4.0 của internal SDK lên PyPI chiều thứ Sáu. Thứ Hai, 14 microservice đồng loạt crash khi auto-upgrade. Nguyên nhân: wheel build thiếu file py.typed và thư mục migrations/. Install OK, import OK, nhưng runtime fail. Rollback mất 4 giờ vì PyPI không cho re-upload cùng version — phải bump v2.4.1 hotfix.
Bài học: viết code giỏi chưa đủ — đóng gói và phân phối đúng cách mới là yếu tố quyết định. Distribution là cầu nối giữa source code trên máy bạn và pip install trên máy người khác.
Tin tốt: sau bài viết này, bạn sẽ nắm vững toàn bộ quy trình — từ build format, publish lên registry, đến CI/CD tự động.
Bức tranh tư duy
Hãy nghĩ về distribution như đóng gói sản phẩm trước khi giao hàng. Bạn sản xuất ghế gỗ (source code). Để giao đến khách, bạn cần: đóng thùng đúng kích thước (build format), dán nhãn vận chuyển (metadata), gửi đến kho trung chuyển (registry), và khách đặt qua hệ thống (pip install). Gửi nguyên khối gỗ thô thì khách phải tự lắp — chậm, dễ hỏng. Gửi ghế đã lắp sẵn trong hộp chuẩn (wheel) thì chỉ cần mở ra dùng.
text
Source Code Build Distribution Registry Install
┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌──────────────┐
│ src/ │ │ python │ │ .tar.gz │ │ PyPI │ │ pip install │
│ tests/ │ ───→ │ -m build │ ───→ │ (sdist) │ ─→ │ Test │ ──→ │ my-package │
│ pyproject│ │ │ │ │ │ PyPI │ │ │
│ .toml │ │ │ │ .whl │ │ Private │ │ → site- │
└──────────┘ └──────────┘ │ (wheel) │ │ Registry│ │ packages/ │
└──────────────┘ └──────────┘ └──────────────┘
Bạn viết code → Đóng gói thành → Hai format → Upload lên → Người dùng
artifact chuẩn phân phối kho lưu trữ cài đặtsdist vs wheel — So sánh trực quan
text
sdist (.tar.gz) wheel (.whl)
┌─────────────────────┐ ┌─────────────────────┐
│ 📦 Source archive │ │ 📦 Pre-built archive │
│ pyproject.toml │ │ my_package/ │
│ src/my_package/ │ │ __init__.py │
│ __init__.py │ │ module.py │
│ ext.c ← cần │ │ METADATA, RECORD │
│ MANIFEST.in │ │ ✅ Đã compile sẵn │
└─────────────────────┘ └─────────────────────┘
⏱️ Chậm (cần build + compiler) ⏱️ Nhanh (unzip → done)📌 Khi nào cần cả hai?
Thực tế tốt nhất: luôn publish cả sdist lẫn wheel. Wheel cho tốc độ install, sdist cho trường hợp wheel không tương thích với platform đích. python -m build mặc định tạo cả hai — đừng bỏ bước này.
Cốt lõi kỹ thuật
Distribution Formats — sdist vs wheel
Source Distribution (sdist) là archive .tar.gz chứa toàn bộ source code. Khi install, pip phải chạy build system để compile. Nếu có C extensions, user cần compiler.
Wheel là archive .whl (ZIP) chứa code đã build sẵn. pip chỉ cần unzip vào site-packages/ — không cần build. Format khuyến nghị cho distribution hiện đại.
| Tiêu chí | sdist (.tar.gz) | wheel (.whl) |
|---|---|---|
| Nội dung | Source code gốc | Code đã build |
| Cần build khi install | ✅ Có | ❌ Không |
| Tốc độ install | Chậm | Nhanh (10-100x) |
| C extensions | Cần compiler | Đã compile sẵn |
| Platform | Độc lập | Có thể platform-specific |
| Reproducibility | Phụ thuộc build env | Deterministic |
Wheel Naming Convention
Mỗi tên file wheel mã hóa chính xác compatibility:
text
{distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
# Pure Python — chạy mọi nơi
analytics_sdk-2.1.0-py3-none-any.whl
# │ │ │ │
# │ │ │ └── Mọi architecture
# │ │ └─────── Không ABI requirement
# │ └─────────── Python 3.x
# └───────────────── Version 2.1.0
# Platform-specific — có C extensions
numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.whl
# │ │ │ │
# │ │ │ └── Linux x86_64, glibc ≥ 2.17
# │ │ └──────── ABI: CPython 3.12
# │ └────────────── CPython 3.12
# └──────────────────── Version 1.26.4
# macOS universal
cryptography-42.0.0-cp312-cp312-macosx_10_12_universal2.whl
# Windows
pillow-10.2.0-cp312-cp312-win_amd64.whlBuilding Packages
Tool chuẩn: python -m build — frontend PEP 517 tương thích mọi build backend.
bash
# Cài đặt build tool
pip install build
# Build cả sdist và wheel (khuyến nghị)
python -m build
# Chỉ build wheel
python -m build --wheel
# Chỉ build sdist
python -m build --sdist
# Kiểm tra output
ls dist/
# analytics_sdk-2.1.0.tar.gz
# analytics_sdk-2.1.0-py3-none-any.whlVerify trước khi publish — bắt buộc:
bash
pip install twine
# Kiểm tra metadata và long description rendering
twine check dist/*
# Test install trong clean environment
python -m venv /tmp/test_install
source /tmp/test_install/bin/activate
pip install dist/analytics_sdk-2.1.0-py3-none-any.whl
python -c "import analytics_sdk; print(analytics_sdk.__version__)"
deactivate && rm -rf /tmp/test_installPublishing với Twine
Twine là tool upload chuẩn — hỗ trợ cả PyPI, TestPyPI, và private registries.
bash
# Bước 1: Upload lên TestPyPI để kiểm tra
twine upload --repository testpypi dist/*
# Bước 2: Verify install từ TestPyPI
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
analytics-sdk
# Bước 3: Mọi thứ ổn → upload lên PyPI production
twine upload dist/*Cấu hình credentials qua ~/.pypirc (cho local development):
ini
[distutils]
index-servers = pypi, testpypi
[pypi]
username = __token__
password = pypi-AgEIcH...
[testpypi]
repository = https://test.pypi.org/legacy/
username = __token__
password = pypi-AgEIcH...Nhớ chmod 600 ~/.pypirc để bảo mật file credentials.
Trusted Publishing (OIDC) — Không cần API Token
Trusted publishing dùng OpenID Connect (OIDC) để CI provider xác thực trực tiếp với PyPI — không cần lưu trữ API token. Thiết lập: vào PyPI → Manage project → Publishing → Add GitHub Actions publisher (điền owner, repo, workflow filename).
yaml
# .github/workflows/publish.yml
name: Publish to PyPI
on:
release:
types: [published]
permissions:
id-token: write # Bắt buộc cho OIDC
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install build && python -m build
- uses: pypa/gh-action-pypi-publish@release/v1
# Không cần token — OIDC tự động xác thực!Private Registries
Khi package nội bộ không phù hợp để public, private registry là giải pháp.
AWS CodeArtifact:
bash
# Login và publish
aws codeartifact login --tool pip \
--domain my-org --domain-owner 123456789012 --repository internal-pypi
aws codeartifact login --tool twine \
--domain my-org --domain-owner 123456789012 --repository internal-pypi
twine upload --repository codeartifact dist/*devpi — Self-hosted registry:
bash
pip install devpi-server devpi-client
devpi-server --init && devpi-server --start --port 4040
devpi use http://localhost:4040
devpi user -c deploy password=S3cureP@ss
devpi login deploy --password=S3cureP@ss
devpi index -c production bases=root/pypi
devpi upload dist/*Cấu hình trong pyproject.toml (Poetry):
toml
[[tool.poetry.source]]
name = "internal"
url = "https://artifacts.company.com/pypi/internal/simple/"
priority = "primary"Semantic Versioning (SemVer)
text
MAJOR.MINOR.PATCH[-pre_release]
MAJOR ──→ Breaking changes: xóa/đổi public API
MINOR ──→ Backward-compatible features: thêm hàm mới
PATCH ──→ Backward-compatible fixes: bug fix, security patch
VD: 2.1.3Quy tắc bump version (PEP 440 compatible):
python
# pyproject.toml — các giai đoạn release
[project]
version = "1.0.0" # Stable release
version = "2.0.0a1" # Alpha — API chưa ổn định
version = "2.0.0b1" # Beta — feature-complete, đang test
version = "2.0.0rc1" # Release Candidate — sẵn sàng nếu không có bugSingle Source of Truth cho version — dùng setuptools-scm:
toml
# pyproject.toml
[build-system]
requires = ["setuptools>=64", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"
[project]
dynamic = ["version"]
[tool.setuptools_scm]
# Version tự derive từ git tag — không hardcode ở đâu cảbash
# Workflow: tag → build → version tự động
git tag v2.1.0
git push origin v2.1.0
python -m build
# → analytics_sdk-2.1.0-py3-none-any.whl (version từ tag)Thực chiến
Scenario: CI/CD Publishing Pipeline hoàn chỉnh
Bạn maintain analytics-sdk, internal package dùng bởi 8 backend services. Yêu cầu: mỗi khi tag release (v*), pipeline tự động chạy test → build → publish TestPyPI → verify → publish PyPI → tạo GitHub Release.
yaml
# .github/workflows/release.yml
name: Release Pipeline
on:
push:
tags:
- "v*"
permissions:
id-token: write
contents: write
jobs:
# ──────────── Stage 1: Test matrix ────────────
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: pip install -e ".[dev]"
- run: ruff check src/
- run: mypy src/
- run: pytest --cov=analytics_sdk --cov-report=xml
# ──────────── Stage 2: Build & verify ────────────
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build & verify
run: |
pip install build twine
python -m build
twine check dist/*
pip install dist/*.whl
python -c "import analytics_sdk; print(analytics_sdk.__version__)"
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
# ──────────── Stage 3: Publish & Verify TestPyPI ────────────
publish-testpypi:
needs: build
runs-on: ubuntu-latest
environment: testpypi
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Verify from TestPyPI
run: |
sleep 30
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
analytics-sdk==${GITHUB_REF_NAME#v}
# ──────────── Stage 4: Publish PyPI (production) ────────────
publish-pypi:
needs: publish-testpypi
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/analytics-sdk
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
# ──────────── Stage 5: GitHub Release ────────────
github-release:
needs: publish-pypi
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: softprops/action-gh-release@v2
with:
files: dist/*
generate_release_notes: trueSai lầm điển hình
1. Publish thẳng lên PyPI không qua TestPyPI
bash
# ❌ SAI: Bypass TestPyPI — "nhanh hơn mà"
python -m build
twine upload dist/*
# Phát hiện metadata sai → PyPI không cho xóa version
# → Phải bump version chỉ để fix metadata
# ✅ ĐÚNG: Luôn qua TestPyPI trước
python -m build
twine upload --repository testpypi dist/*
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
my-package==1.0.0
# Verify xong → PyPI
twine upload dist/*2. Hardcode version ở nhiều nơi
python
# ❌ SAI: Version nằm rải rác — quên update 1 chỗ là đủ hỏng
# pyproject.toml → version = "1.2.0"
# __init__.py → __version__ = "1.1.0" ← QUÊN UPDATE!
# docs/conf.py → version = "1.2.0"
# ✅ ĐÚNG: Single source of truth với setuptools-scm
# pyproject.toml
# [project]
# dynamic = ["version"]
# [tool.setuptools_scm]
# → Version derive từ git tag, không hardcode ở đâu cả
# Nếu cần truy cập version trong code:
from importlib.metadata import version
__version__ = version("analytics-sdk")3. Thiếu package data trong distribution
toml
# ❌ SAI: Chỉ include Python files — thiếu data files
[tool.setuptools.packages.find]
where = ["src"]
# → py.typed, JSON schemas, templates KHÔNG có trong wheel!
# ✅ ĐÚNG: Khai báo rõ package data
[tool.setuptools.package-data]
analytics_sdk = [
"py.typed",
"schemas/*.json",
"templates/*.html",
"data/**/*.csv",
]4. Không verify build trước khi publish
bash
# ❌ SAI: Build xong upload ngay
python -m build && twine upload dist/*
# wheel thiếu module → user install OK nhưng import fail!
# ✅ ĐÚNG: Verify chain đầy đủ
python -m build
twine check dist/*
python -m venv /tmp/verify && source /tmp/verify/bin/activate
pip install dist/*.whl
python -c "from analytics_sdk.core import Engine; print('OK')"
deactivate && rm -rf /tmp/verify
twine upload --repository testpypi dist/*5. Lộ secrets trong CI/CD pipeline
yaml
# ❌ SAI: Token hardcode trong workflow
- name: Publish
run: twine upload dist/*
env:
TWINE_PASSWORD: pypi-AgEIcHlwaS5vcmc... # LEAKED trong repo!
# ✅ ĐÚNG: Dùng Trusted Publishing (OIDC) — không cần token
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
# OIDC token tự động — không secret nào cần lưu trữ
# Hoặc nếu buộc phải dùng token: GitHub encrypted secrets
- name: Publish
run: twine upload dist/*
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}Under the Hood
Pip resolve và install wheel như thế nào?
Khi bạn chạy pip install analytics-sdk:
- Query PyPI API —
GET https://pypi.org/simple/analytics-sdk/→ nhận danh sách tất cả distributions - Resolve version — chọn version mới nhất thỏa mãn constraint, resolve dependency conflicts
- Chọn distribution — ưu tiên wheel > sdist, match Python version + ABI + platform
- Download & install — wheel: unzip vào
site-packages/; sdist: build rồi install
PyPI Upload Process
Khi twine upload chạy:
- Đọc metadata từ
PKG-INFO(sdist) hoặcMETADATA(wheel) - Tính hash SHA-256 và MD5 cho mỗi file
- POST multipart/form-data đến
https://upload.pypi.org/legacy/ - PyPI validate: tên package, version chưa tồn tại, metadata hợp lệ
- PyPI index package — có thể mất vài phút để available qua
pip install
Trade-offs: Distribution Strategies
| Chiến lược | Ưu điểm | Nhược điểm | Khi nào dùng |
|---|---|---|---|
| TestPyPI → PyPI | An toàn, verify trước | Thêm bước, chậm hơn | Mọi production release |
| Trusted Publishing (OIDC) | Không token, audit trail | Chỉ hỗ trợ vài CI providers | GitHub/GitLab projects |
| Private Registry (CodeArtifact) | Kiểm soát truy cập, IAM | Chi phí, phức tạp | Internal packages |
| Private Registry (devpi) | Tự host, caching PyPI | Vận hành server | Team nhỏ-trung bình |
| Git tag versioning (scm) | Single source of truth | Phụ thuộc git history | Mọi project nên dùng |
Anatomy của một Wheel file
bash
# Unzip wheel để xem bên trong
unzip -l analytics_sdk-2.1.0-py3-none-any.whl
# analytics_sdk/__init__.py
# analytics_sdk/core.py
# analytics_sdk/py.typed
# analytics_sdk/schemas/event.json
# analytics_sdk-2.1.0.dist-info/METADATA ← Metadata (PEP 566)
# analytics_sdk-2.1.0.dist-info/WHEEL ← Wheel tag info
# analytics_sdk-2.1.0.dist-info/RECORD ← SHA-256 hash mỗi file
# analytics_sdk-2.1.0.dist-info/entry_points.txtRECORD chứa hash SHA-256 mỗi file — pip dùng để verify integrity khi install. Nếu file bị sửa đổi sau khi build, hash không khớp → pip từ chối install.
Checklist ghi nhớ
✅ Checklist triển khai
Build & Verify
- [ ] Luôn dùng
python -m buildđể tạo cả sdist và wheel - [ ] Chạy
twine check dist/*trước mỗi lần publish - [ ] Test install wheel trong clean virtual environment trước khi upload
- [ ] Verify tất cả imports và data files có trong wheel
Publishing
- [ ] Luôn publish TestPyPI trước, PyPI sau
- [ ] Ưu tiên Trusted Publishing (OIDC) thay vì API token
- [ ] Nếu dùng token: chỉ qua CI secrets, không hardcode
- [ ] Bảo mật
~/.pypircvớichmod 600
Versioning
- [ ] Tuân thủ SemVer: MAJOR.MINOR.PATCH
- [ ] Single source of truth cho version (setuptools-scm hoặc
importlib.metadata) - [ ] Không hardcode version ở nhiều file
- [ ] Dùng pre-release versions (alpha/beta/rc) cho testing
CI/CD Pipeline
- [ ] Pipeline tự động: test → build → verify → TestPyPI → PyPI
- [ ] Test matrix trên nhiều Python versions (3.10, 3.11, 3.12)
- [ ] Upload build artifacts giữa các stages
- [ ] Tạo GitHub Release kèm distribution files
Package Data
- [ ] Khai báo rõ package-data trong pyproject.toml
- [ ] Include
py.typednếu package hỗ trợ type checking - [ ] Verify data files có mặt trong wheel (unzip và kiểm tra)
Bài tập luyện tập
Bài 1: Phân tích Wheel Naming — Foundation
Đề bài: Cho các wheel filename dưới đây, xác định package nào compatible với hệ thống CPython 3.11 trên Linux x86_64 (glibc 2.31).
text
A. cryptography-42.0.0-cp312-cp312-manylinux_2_17_x86_64.whl
B. requests-2.31.0-py3-none-any.whl
C. numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.whl
D. pillow-10.2.0-cp311-cp311-win_amd64.whl
E. pandas-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl🧠 Quiz
Wheel nào KHÔNG compatible với CPython 3.11 trên Linux x86_64?
- [ ] A. Chỉ A
- [ ] B. Chỉ D
- [x] C. A và D — A yêu cầu CPython 3.12 (
cp312), D yêu cầu Windows (win_amd64) - [ ] D. A, D và E Giải thích: B compatible vì
py3-none-any= pure Python, chạy mọi nơi. C compatible vì match đúngcp311+manylinux_2_17_x86_64(glibc 2.31 > 2.17). E compatible vìcp311+manylinux_2_17_x86_64. A fail vìcp312≠cp311. D fail vìwin_amd64≠ Linux.
💡 Phân tích chi tiết
| Wheel | Tags | Compatible? | Lý do |
|---|---|---|---|
| A | cp312 / manylinux_2_17_x86_64 | ❌ | Python 3.12 only |
| B | py3 / none / any | ✅ | Pure Python — universal |
| C | cp311 / manylinux_2_17_x86_64 | ✅ | Exact match |
| D | cp311 / win_amd64 | ❌ | Windows only |
| E | cp311 / manylinux_2_17_x86_64 | ✅ | glibc 2.31 ≥ 2.17 |
Bài 2: Thiết kế Release Workflow — Intermediate
Đề bài: Team bạn cần thiết lập CI/CD cho internal package data-pipeline publish lên AWS CodeArtifact. Yêu cầu:
- Trigger khi push tag
v* - Test trên Python 3.11 và 3.12
- Build + verify
- Publish lên CodeArtifact (không phải PyPI)
🧠 Quiz
Trong trusted publishing workflow, tại sao cần permissions: id-token: write?
- [ ] A. Để GitHub Actions có quyền push code
- [x] B. Để GitHub Actions có thể request OIDC token, dùng xác thực với PyPI mà không cần API secret
- [ ] C. Để workflow có thể đọc repository secrets
- [ ] D. Để tạo GitHub Release Giải thích:
id-token: writecho phép workflow request JSON Web Token (JWT) từ GitHub's OIDC provider. PyPI verify token này để xác nhận request đến từ trusted workflow — không cần lưu trữ bất kỳ secret nào. Đây là cơ chế an toàn nhất hiện tại cho automated publishing.
✅ Lời giải
yaml
# .github/workflows/release-codeartifact.yml
name: Release to CodeArtifact
on:
push:
tags: ["v*"]
env:
AWS_REGION: ap-southeast-1
CODEARTIFACT_DOMAIN: my-org
CODEARTIFACT_OWNER: "123456789012"
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -e ".[dev]" && pytest --cov=data_pipeline
build-and-publish:
needs: test
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build, verify & publish
run: |
pip install build twine
python -m build && twine check dist/*
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ env.CODEARTIFACT_OWNER }}:role/github-publish
aws-region: ${{ env.AWS_REGION }}
- name: Publish to CodeArtifact
run: |
aws codeartifact login --tool twine \
--domain $CODEARTIFACT_DOMAIN \
--domain-owner $CODEARTIFACT_OWNER \
--repository internal-pypi
twine upload --repository codeartifact dist/*Điểm chính: Workflow dùng OIDC (configure-aws-credentials với role-to-assume) để authenticate với AWS — không cần AWS access keys trong secrets.
Bài 3: Debug Distribution Problem — Advanced
Đề bài: User báo lỗi sau khi install package:
python
>>> import mylib
>>> mylib.load_config()
FileNotFoundError: [Errno 2] No such file or directory: '.../site-packages/mylib/defaults.yaml'Package structure trên repo:
text
src/mylib/
├── __init__.py
├── core.py
├── defaults.yaml
└── schemas/
└── v1.jsonpyproject.toml hiện tại:
toml
[tool.setuptools.packages.find]
where = ["src"]Tìm nguyên nhân và sửa cấu hình để defaults.yaml và schemas/v1.json có mặt trong wheel.
💡 Gợi ý
setuptools.packages.findchỉ tìm Python packages (thư mục có__init__.py)- Non-Python files (YAML, JSON) cần khai báo riêng qua
package-data - Verify bằng cách unzip wheel và kiểm tra nội dung
✅ Lời giải
Nguyên nhân: setuptools.packages.find chỉ include .py files. Các file .yaml và .json bị bỏ qua khi build wheel.
Sửa pyproject.toml:
toml
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
mylib = [
"defaults.yaml",
"schemas/*.json",
]Verify:
bash
# Rebuild và verify
rm -rf dist/
python -m build
# Kiểm tra wheel contents
python -c "
import zipfile
with zipfile.ZipFile('dist/mylib-1.0.0-py3-none-any.whl') as zf:
for name in sorted(zf.namelist()):
print(name)
"
# mylib/defaults.yaml ← Phải có
# mylib/schemas/v1.json ← Phải có
# Test install
pip install dist/*.whl --force-reinstall
python -c "import mylib; mylib.load_config()" # Không còn FileNotFoundErrorBài học: Luôn verify wheel contents sau khi thay đổi pyproject.toml. Unzip wheel và kiểm tra — đừng giả định file sẽ tự xuất hiện.
Liên kết học tiếp
Từ khóa glossary: Distribution, wheel, sdist, twine, PyPI, TestPyPI, trusted publishing, OIDC, private registry, CodeArtifact, devpi, semantic versioning, SemVer, setuptools-scm, MANIFEST.in, package-data
Tìm kiếm liên quan: đóng gói Python, publish PyPI, wheel vs sdist, CI/CD Python release, private Python registry, semantic versioning Python, trusted publishing GitHub Actions