- 前言
- 創(chuàng)建項目
- 指定庫名稱
- 創(chuàng)建 pyproject.toml
- 配置元數(shù)據(jù)
- 添加包內(nèi)容
- 創(chuàng)建 README.md
- 創(chuàng)建許可證
- 生成分發(fā)歸檔
- 上傳分發(fā)歸檔
- 安裝新上傳的軟件包
- 發(fā)布正式包
- Github Actions
- 小結(jié)
- 參考
前言
本教程以 py_pkg 項目為例,講解如何將一個 Python 項目打包成庫并分發(fā)到 PyPI,方便他人使用 pip install 安裝使用。
創(chuàng)建項目
創(chuàng)建以下文件結(jié)構(gòu)
.
└── py_pkg
├── LICENSE
├── README.md
├── py_pkg
│ ├── __init__.py
│ └── hello.py
├── pyproject.toml
├── setup.cfg
└── tests
指定庫名稱
在 py_pkg/__init__.py 中指定庫的名稱
name = "py_pkg"
創(chuàng)建 pyproject.toml
pyproject.toml 告訴構(gòu)建工具(例如 pip10+ 和 build)的文件,你正在使用什么系統(tǒng)以及構(gòu)建它需要什么。如果缺少此文件,則默認(rèn)為假定使用經(jīng)典的 setuptools 構(gòu)建系統(tǒng),但最好明確一些。如果您有 pyproject.toml 文件,你將可以依賴 wheel 和存在其他軟件包。
對于大多數(shù) setuptools 項目,可參考以下配置:
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"
build-system.requires 提供構(gòu)建軟件包所需的軟件包列表。在此處列出某些內(nèi)容只會使其在構(gòu)建期間可用,而在安裝之后不可用。
build-system.build-backend 在技術(shù)上是可選的,但是如果你忘記包含 setuptools.build_meta:__legacy__ ,則會被替代,因此請始終包含它。如果要使用 flit 或 poetry 之類的其他構(gòu)建系統(tǒng), 將在此處使用,并且配置詳細(xì)信息將與下面描述的 setuptools 配置完全不同。請查看 PEP 517 和 PEP 518 了解背景和詳細(xì)信息。
配置元數(shù)據(jù)
元數(shù)據(jù)分為以下兩種類型:
- 靜態(tài)元數(shù)據(jù)(
setup.cfg):保證每次都相同。這更簡單,更易于閱讀,并且避免了許多常見錯誤,例如編碼錯誤。 - 動態(tài)元數(shù)據(jù)(
setup.py):可能不確定。任何動態(tài)的或在安裝時確定的項目,以及擴(kuò)展模塊或 setuptools 的擴(kuò)展,都需要輸入setup.py。
首選靜態(tài)元數(shù)據(jù),動態(tài)元數(shù)據(jù)僅在絕對必要時才使用。
setup.cfg
setup.cfg 是 setuptools 的配置文件。它告訴 setuptools 有關(guān)你的軟件包(例如名稱和版本)以及要包括的代碼文件的信息。最終,此配置中的大部分都可以移至 pyproject.toml。
打開 setup.cfg 并輸入以下內(nèi)容。確保軟件包名稱具有唯一性,可以到 https://pypi.org/ 搜索你的軟件包名,如果沒有出現(xiàn)那就表明沒有被占用。
[metadata]
name = py_pkg
version = 0.0.1
author = CatchZeng
author_email = catchzenghh@gmail.com
description = A small example package
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/catchzeng/py_pkg
classifiers =
Programming Language :: Python :: 3
License :: OSI Approved :: MIT License
Operating System :: OS Independent
[options]
packages = find:
python_requires = >=3.6
支持的各種元數(shù)據(jù)和選項。這是 configparser 格式;不要在值兩邊加上引號。
name軟件包的分發(fā)名稱。該名稱可以是任何名稱,只可包含字母,數(shù)字,_和-。version包的版本。有關(guān)版本的更多詳細(xì)信息,請參見 PEP 440。你可以使用file:或attr:指令從文件或包屬性中讀?。ê唵螌傩圆恍枰獙?dǎo)入)。author和author_email用于標(biāo)識軟件包的作者。description是該軟件包的簡短,一句話摘要。long_description是包裝的詳細(xì)說明。這在Python軟件包索引的軟件包詳細(xì)信息頁面上顯示。在這種情況下,將README.md使用file:指令加載詳細(xì)描述,這是一種常見的模式。long_description_content_type告訴索引用于長描述的標(biāo)記類型。url是項目主頁的 URL。對于許多項目,這僅是指向GitHub,GitLab,Bitbucket或類似代碼托管服務(wù)的鏈接。classifiers給index和 pip 提供包的一些額外元數(shù)據(jù)。比如,該軟件包僅與Python 3兼容,已獲得MIT許可,并且與操作系統(tǒng)無關(guān)。你應(yīng)始終至少包括軟件包所使用的Python 版本,軟件包所使用的許可證以及軟件包所使用的操作系統(tǒng)。有關(guān)classifiers的完整列表,請參見 https://pypi.org/classifiers/。
在 options 中:
packages是應(yīng)該包含在分發(fā)包中的所有 Python 導(dǎo)入包的列表。無需手動列出每個軟件包,我們可以使用指令自動發(fā)現(xiàn)所有軟件包和子軟件包。在這種情況下,軟件包列表(py_pkg)將是唯一存在的軟件包。python_requires提供項目支持的 Python 版本。像pip這樣的安裝程序?qū)⒒厮菖f版軟件包,直到找到具有匹配 Python 版本的軟件包為止。
除了這里提到的以外,還有很多其他內(nèi)容。有關(guān)更多詳細(xì)信息,請參見打包和分發(fā)項目。
如果創(chuàng)建 setup.py 文件,則將啟用與文件的直接交互 setup.py(通常應(yīng)避免這種交互)和可編輯的安裝。該文件以前是必需的,但在現(xiàn)在的 setuptools 中可以省略。
你設(shè)置的所有內(nèi)容 setup.cfg 都可以通過關(guān)鍵字參數(shù)設(shè)置為 setup(); 這樣就可以使用計算值。你還需要 setup() 設(shè)置擴(kuò)展模塊進(jìn)行編譯。
import setuptools
setuptools.setup()
配置依賴項
如果你的包在用 pip 安裝的時候,依賴其他包(比如 requests),可以在 setup.cfg 的 install_requires 添加。
[options]
packages = find:
python_requires = >=3
install_requires =
requests>=2.24.0
setup.py
注:
setup.py是非必須的,所以在py_pkg實例中已省略。
setup.py 是 setuptools 的構(gòu)建腳本。它告訴 setuptools 有關(guān)軟件包(例如名稱和版本)以及要包括的代碼文件的信息。
打開 setup.py 并輸入以下內(nèi)容。
import setuptools
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setuptools.setup(
name="py-pkg",
version="0.0.1",
author="catchzeng",
author_email="catchzenghh@gmail.com",
description="A small example package",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/catchzeng/py_pkg",
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
packages=setuptools.find_packages(),
python_requires='>=3.6',
)
setup() 的參數(shù)與 setup.cfg 中的類似,這里不再贅述。
添加包內(nèi)容
這里以 hello.py 為例
def hello():
print("hello")
創(chuàng)建 README.md
這里編寫項目的介紹,可以根據(jù)需要自定義。
# py_pkg
> 如何打包并發(fā)布 Python 庫到 PyPI demo
創(chuàng)建許可證
每個上傳到 PyPI 的軟件包都必須包含許可證。這會告訴安裝軟件包的用戶使用條款。有關(guān)選擇許可證的幫助,請參閱 https://choosealicense.com/。選擇許可證后,打開 LICENSE 并輸入許可證文本。例如,如果您選擇了 MIT 許可證:
MIT License
Copyright (c) 2021 The Python Packaging Authority
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
生成分發(fā)歸檔
安裝 PyPA’s build。
? python3 -m pip install --upgrade build
Collecting build
Downloading build-0.3.0-py2.py3-none-any.whl (13 kB)
Collecting pep517>=0.9
Downloading pep517-0.9.1-py2.py3-none-any.whl (18 kB)
Requirement already satisfied, skipping upgrade: toml in /Users/catchzeng/opt/anaconda3/lib/python3.8/site-packages (from build) (0.10.1)
Requirement already satisfied, skipping upgrade: packaging in /Users/catchzeng/opt/anaconda3/lib/python3.8/site-packages (from build) (20.4)
Requirement already satisfied, skipping upgrade: pyparsing>=2.0.2 in /Users/catchzeng/opt/anaconda3/lib/python3.8/site-packages (from packaging->build) (2.4.7)
Requirement already satisfied, skipping upgrade: six in /Users/catchzeng/opt/anaconda3/lib/python3.8/site-packages (from packaging->build) (1.15.0)
Installing collected packages: pep517, build
Successfully installed build-0.3.0 pep517-0.9.1
在 pyproject.toml 所在目錄下,執(zhí)行以下命令生成分法包。
? python3 -m build
完成后會生成許多文件,其中 dist 目錄中生成兩個文件:
.
├── LICENSE
├── README.md
├── build
│ ├── bdist.macosx-10.9-x86_64
│ └── lib
│ └── py_pkg
│ ├── __init__.py
│ └── hello.py
├── dist
│ ├── py_pkg-0.0.1-py3-none-any.whl
│ └── py_pkg-0.0.1.tar.gz
├── py_pkg
│ ├── __init__.py
│ └── hello.py
├── py_pkg.egg-info
│ ├── PKG-INFO
│ ├── SOURCES.txt
│ ├── dependency_links.txt
│ └── top_level.txt
├── pyproject.toml
├── setup.cfg
└── tests
該 tar.gz 文件是源歸檔,而 .whl 文件是內(nèi)置分發(fā)。較新的 pip 版本優(yōu)先安裝內(nèi)置發(fā)行版,但如果需要,將回落到源歸檔文件中。你應(yīng)該始終上傳源檔案,并為項目兼容的平臺提供內(nèi)置檔案。在這種情況下,示例包在任何平臺上都與 Python 兼容,因此僅需要一個內(nèi)置發(fā)行版。
上傳分發(fā)歸檔
第一件事是在 Test PyPI 上注冊一個帳戶。Test PyPI 是用于測試和實驗的包索引的單獨實例。對于像本教程這樣的事情非常有用,我們不必一定要上傳到真實索引。要注冊帳戶,請訪問 https://test.pypi.org/account/register/ 并完成該頁面上的步驟。你還需要先驗證電子郵件地址,然后才能上傳任何軟件包。有關(guān) Test PyPI 的更多詳細(xì)信息,請參閱使用TestPyPI。
現(xiàn)在,需要創(chuàng)建一個 PyPI API 令牌,以便能夠安全地上傳你的項目。
訪問 https://test.pypi.org/manage/account/#api-tokens 并創(chuàng)建一個新的 API 令牌。不要將其范圍限制為特定項目,因為你正在創(chuàng)建一個新項目。
注:在復(fù)制并保存令牌之前,請不要關(guān)閉頁面-您不會再看到該令牌。
現(xiàn)在你已經(jīng)注冊,可以使用 twine 上傳分發(fā)包。需要安裝 Twine:
? python3 -m pip install --user --upgrade twine
安裝完成后,運行 Twine 上傳所有歸檔 dist:
? python3 -m twine upload --repository testpypi dist/*
Uploading distributions to https://test.pypi.org/legacy/
Enter your username: __token__
Enter your password:
Uploading py_pkg-0.0.1-py3-none-any.whl
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 5.43k/5.43k [00:04<00:00, 1.17kB/s]
Uploading py_pkg-0.0.1.tar.gz
100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████| 4.46k/4.46k [00:00<00:00, 4.89kB/s]
View at:
https://test.pypi.org/project/py-pkg/0.0.1/
系統(tǒng)將提示您輸入用戶名和密碼。對于用戶名,請使用 __token__。對于密碼,請使用令牌值,包括 pypi- 前綴。
安裝新上傳的軟件包
你可以使用 pip 來安裝軟件包并驗證其是否有效。創(chuàng)建一個新的 virtualenv,然后從 TestPyPI 安裝軟件包:
? python3 -m pip install --index-url https://test.pypi.org/simple/ --no-deps py-pkg
Looking in indexes: https://test.pypi.org/simple/
Collecting py-pkg
Downloading https://test-files.pythonhosted.org/packages/86/8e/44313ae67863815e0a9afd5e8972e5eb8c9dae5f9ff80d94d3fcfad6310b/py_pkg-0.0.1-py3-none-any.whl (2.4 kB)
Installing collected packages: py-pkg
Successfully installed py-pkg-0.0.1
注:本示例使用
--index-url標(biāo)志指定TestPyPI而不是實時PyPI。另外,它指定--no-deps。由于TestPyPI與實時PyPI沒有相同的軟件包,因此嘗試安裝依賴項可能會失敗或安裝意外的東西。盡管我們的示例包沒有任何依賴關(guān)系,但是最好避免在使用TestPyPI時安裝依賴關(guān)系。所以,如果你的包有依賴項,只需要去掉--no-deps,然后查看日志輸出是否有安裝依賴項即可,即使安裝不成功也不用擔(dān)心。
您可以通過導(dǎo)入軟件包來測試它是否已正確安裝。運行Python解釋器(確保您仍在 virtualenv 中):
? python
Python 3.8.5 (default, Sep 4 2020, 02:22:02)
[Clang 10.0.0 ] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from py_pkg import hello
>>> hello.hello()
hello
發(fā)布正式包
安裝測試完畢后,就可以在 https://pypi.org 注冊賬號,執(zhí)行以下發(fā)布命令即可。
? python3 -m twine upload dist/*
Github Actions
上面,我們使用手動的方式分發(fā)包。下面介紹使用 Github Actions 的方式將分發(fā)包過程自動化。
創(chuàng)建 Secrets
前往項目的設(shè)置中,以 py_pkg 為例 https://github.com/CatchZeng/py_pkg/settings/secrets/actions/new,添加名稱為 PYPI_USERNAME 和 PYPI_PASSWORD 值為 https://pypi.org 用戶名和密碼的秘鑰,用于上傳。

添加 Actions
前往 https://github.com/CatchZeng/py_pkg/actions/new,添加 Publish Python Package Action,并提交。

完成后,可以看到項目增加了 .github 目錄。
.
├── .github
│ └── workflows
│ └── python-publish.yml
├── LICENSE
├── README.md
......
python-publish.yaml 的內(nèi)容如下:
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
由于沒有使用 setup.py,因此需要做適當(dāng)修改。
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.8' # py_pkg 需要大于 3.6
# 依賴安裝換成前面的腳本
- name: Install dependencies
run: |
python3 -m pip install --upgrade build
python3 -m pip install --user --upgrade twine
# 構(gòu)建和發(fā)布換成前面的腳本
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python3 -m build
python3 -m twine upload dist/*
修改完畢后,提交代碼。
觸發(fā) Actions
由于在 Actions 中指定了 on:
on:
release:
types: [created]
所以,創(chuàng)建一個 release 即可觸發(fā) Actions。
前往 https://github.com/CatchZeng/py_pkg/releases/new 創(chuàng)建一個新的 release。

點擊 Action 即可查看具體日志

小結(jié)
本教程通過一個實例,講解了如何將一個 Python 項目打包成庫并分發(fā)到 PyPI,并結(jié)合 Github Actions 實現(xiàn)持續(xù)集成自動化分發(fā),希望能幫助到大家。
如果需要更新庫,也很簡單,修改完代碼后,增加版本號,然后再創(chuàng)建新的 release 即可觸發(fā)分發(fā)。
這里需要注意的是,版本號的管理盡量要遵循語義化版本。
另外,示例代碼倉庫 https://github.com/CatchZeng/py_pkg 和真實項目 https://github.com/CatchZeng/bing_images 有需要的可以參考。