news 2026/9/10 4:38:13

Python 打包实战精要:基于 pyproject.toml 的现代分发、CLI 与 PyPI 发布全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 打包实战精要:基于 pyproject.toml 的现代分发、CLI 与 PyPI 发布全流程

Python 打包实战精要:基于 pyproject.toml 的现代分发、CLI 与 PyPI 发布全流程

【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents

本文是 python-packaging 技能 的详细模式参考(references/details.md)的技术解读,面向需要将 Python 库、CLI 工具发布到 PyPI 或私有索引的开发者。读完本文,你将掌握一套可直接复制的"全功能 pyproject.toml"配置、动态版本管理、Click/argparse 两种 CLI 注册方式,以及从本地构建到 GitHub Actions 自动化发布的完整链路,并看到这些模式在当前仓库真实工程(如 plugin-eval、yt-design-extractor)中的落地形态。

文档定位:从导航到细节的第三层

该技能采用"导航 → 快速上手 → 细节参考"的三级结构:SKILL.md负责给出包结构、构建后端(setuptools/hatchling/flit/poetry)、PEP 517/518/621/660 等核心概念与最小化示例;references/details.md则是"详细模式与完整示例"(Detailed patterns and worked examples)的承载文件,编号从 Pattern 4 继续向后延伸,覆盖完整配置、动态版本、CLI、构建与发布四大板块。当导航层信息不足以支撑实际落地时,就应读取该细节文档。本文即以此为骨架展开。

Pattern 4:一份可直接落地的全功能 pyproject.toml

details.md给出了从构建系统、项目元数据到工具链配置一应俱全的完整示例。下面按区块逐段拆解其作用与含义。

build-system:声明构建后端

[build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta"

这是 PEP 517/518 所要求的最小声明:requires列出构建时需要的隔离依赖(此处要求 setuptools 不低于 61.0,这是支持 pyproject.toml 元数据的关键版本门槛),build-backend指定由setuptools.build_meta负责实际构建。wheel的加入确保能同时产出 wheel 与源码分发包。

project:PEP 621 标准元数据

[project] name = "my-awesome-package" version = "1.0.0" description = "An awesome Python package" readme = "README.md" requires-python = ">=3.8" license = {text = "MIT"} authors = [ {name = "Your Name", email = "you@example.com"}, ] maintainers = [ {name = "Maintainer Name", email = "maintainer@example.com"}, ] keywords = ["example", "package", "awesome"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] dependencies = [ "requests>=2.28.0,<3.0.0", "click>=8.0.0", "pydantic>=2.0.0", ]

要点说明:

  • readme支持字符串(文件名)或 TOML 表(指定 content-type 与文本内容);license{text = "MIT"}写法适用于未使用 SPDX 表达式作为license字符串的 setuptools 版本。
  • classifiers是 PyPI 页面筛选与展示的核心元数据,务必同时声明License :: OSI Approved :: ...和各Programming Language :: Python :: 3.x条目,并保持与requires-python一致。
  • dependencies使用 PEP 508 规范说明符。示例中的requests>=2.28.0,<3.0.0是典型的"兼容范围"写法:既排除过旧版本,又用上限挡住未来不兼容的大版本。

optional-dependencies:特性开关

[project.optional-dependencies] dev = [ "pytest>=7.0.0", "pytest-cov>=4.0.0", "black>=23.0.0", "ruff>=0.1.0", "mypy>=1.0.0", ] docs = [ "sphinx>=5.0.0", "sphinx-rtd-theme>=1.0.0", ] all = [ "my-awesome-package[dev,docs]", ]

dev/docs分组可让用户按需安装(pip install -e ".[dev]");all分组通过自引用语法my-awesome-package[dev,docs]一次性聚合全部可选依赖,是发布时常用的"全家桶"入口。这一模式在当前仓库的 plugins/plugin-eval/pyproject.toml 中同样可见——它将llm(claude-agent-sdk)、api(anthropic)、dev(pytest/ruff/ty)分别分组,形成依赖分层。

urls 与 scripts:项目链接与命令行入口

[project.urls] Homepage = "https://github.com/username/my-awesome-package" Documentation = "https://my-awesome-package.readthedocs.io" Repository = "https://github.com/username/my-awesome-package" "Bug Tracker" = "https://github.com/username/my-awesome-package/issues" Changelog = "https://github.com/username/my-awesome-package/blob/main/CHANGELOG.md" [project.scripts] my-cli = "my_package.cli:main" awesome-tool = "my_package.tools:run" [project.entry-points."my_package.plugins"] plugin1 = "my_package.plugins:plugin1"

[project.scripts]会在安装时自动生成可执行命令(如my-cli指向my_package.cli:main),这是发布 CLI 工具的标准做法;[project.entry-points]则允许第三方通过"插件发现"机制向你的包注册扩展点。当前仓库中 plugin-eval 的 pyproject.toml 即注册了plugin-eval = "plugin_eval.cli:app"这一脚本入口,指向 cli.py 中由 typer 创建的应用对象。

tool.setuptools:包发现与数据文件

[tool.setuptools] package-dir = {"" = "src"} zip-safe = false [tool.setuptools.packages.find] where = ["src"] include = ["my_package*"] exclude = ["tests*"] [tool.setuptools.package-data] my_package = ["py.typed", "*.pyi", "data/*.json"]
  • package-dir = {"" = "src"}将包根目录指向src/,配合packages.find.where = ["src"]实现 src 布局(source layout)自动发现;include/exclude控制匹配范围。
  • zip-safe = false显式声明包不做 zip 导入,避免资源文件在 zip 环境下失效。
  • package-data声明随包安装的非 Python 文件:py.typed是 PEP 561 类型标记,*.pyi是存根文件,data/*.json是运行时数据。这些资源在运行时可通过importlib.resources读取(见后文高级模式)。

tool.black / tool.ruff / tool.mypy / tool.pytest / tool.coverage:工具链统一配置

[tool.black] line-length = 100 target-version = ["py38", "py39", "py310", "py311"] include = '\.pyi?$' [tool.ruff] line-length = 100 target-version = "py38" [tool.ruff.lint] select = ["E", "F", "I", "N", "W", "UP"] [tool.mypy] python_version = "3.8" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] addopts = "-v --cov=my_package --cov-report=term-missing" [tool.coverage.run] source = ["src"] omit = ["*/tests/*"] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "def __repr__", "raise AssertionError", "raise NotImplementedError", ]

将格式化、lint、类型检查、测试与覆盖率配置全部收拢进 pyproject.toml,实现了"单一配置源"。当前仓库的做法与此一致:plugin-eval 在 pyproject.toml 中配置了[tool.ruff](line-length 100)、[tool.ruff.lint][tool.ty][tool.pytest.ini_options],并且仓库 Makefile 中的lint/test目标均以uv run --extra dev方式在该项目目录内执行,确保使用锁定的工具版本。

Pattern 5:动态版本管理

手工维护version = "1.0.0"容易与 git tag 脱节,动态版本是发布自动化的重要一环:

[build-system] requires = ["setuptools>=61.0", "setuptools-scm>=8.0"] build-backend = "setuptools.build_meta" [project] name = "my-package" dynamic = ["version"] description = "Package with dynamic version" [tool.setuptools.dynamic] version = {attr = "my_package.__version__"} # Or use setuptools-scm for git-based versioning [tool.setuptools_scm] write_to = "src/my_package/_version.py"

两种方式二选一:

  1. 属性读取[tool.setuptools.dynamic] version = {attr = "my_package.__version__"}从源码读取版本字符串,配合:

    # src/my_package/__init__.py __version__ = "1.0.0" # Or with setuptools-scm from importlib.metadata import version __version__ = version("my-package")
  2. git 派生(setuptools-scm)[tool.setuptools_scm]根据 git tag 自动推导版本并写入_version.py,彻底消除手工同步。注意使用dynamic = ["version"]后就不能再在[project]中写静态version

CLI 模式:Click 与 argparse 双方案

Pattern 6:基于 Click 的分组命令

# src/my_package/cli.py import click @click.group() @click.version_option() def cli(): """My awesome CLI tool.""" pass @cli.command() @click.argument("name") @click.option("--greeting", default="Hello", help="Greeting to use") def greet(name: str, greeting: str): """Greet someone.""" click.echo(f"{greeting}, {name}!") @cli.command() @click.option("--count", default=1, help="Number of times to repeat") def repeat(count: int): """Repeat a message.""" for i in range(count): click.echo(f"Message {i + 1}") def main(): """Entry point for CLI.""" cli() if __name__ == "__main__": main()

在 pyproject.toml 中注册并测试:

[project.scripts] my-tool = "my_package.cli:main"
pip install -e . my-tool greet World my-tool greet Alice --greeting="Hi" my-tool repeat --count=3

@click.group()cli变为命令组,子命令通过@cli.command()挂载;@click.version_option()自动提供--versionpip install -e .(PEP 660 可编辑安装)后即可直接以my-tool命令名调用。

Pattern 7:基于 argparse 的子命令解析

# src/my_package/cli.py import argparse import sys def main(): """Main CLI entry point.""" parser = argparse.ArgumentParser( description="My awesome tool", prog="my-tool" ) parser.add_argument( "--version", action="version", version="%(prog)s 1.0.0" ) subparsers = parser.add_subparsers(dest="command", help="Commands") # Add subcommand process_parser = subparsers.add_parser("process", help="Process data") process_parser.add_argument("input_file", help="Input file path") process_parser.add_argument( "--output", "-o", default="output.txt", help="Output file path" ) args = parser.parse_args() if args.command == "process": process_data(args.input_file, args.output) else: parser.print_help() sys.exit(1) def process_data(input_file: str, output_file: str): """Process data from input to output.""" print(f"Processing {input_file} -> {output_file}") if __name__ == "__main__": main()

argparse 是标准库方案,零第三方依赖。核心手法是add_subparsers(dest="command")声明子命令,action="version"提供--version,未匹配命令时print_help()sys.exit(1)。Click 适合命令树复杂、需要丰富交互体验的场景,argparse 适合追求最小依赖的纯标准库工具——选择取决于项目约束。

构建与发布全流程

Pattern 8:本地构建

# Install build tools pip install build twine # Build distribution python -m build # This creates: # dist/ # my-package-1.0.0.tar.gz (source distribution) # my_package-1.0.0-py3-none-any.whl (wheel) # Check the distribution twine check dist/*

python -m build会同时产出 sdist(.tar.gz,源码分发包)与 wheel(.whl,平台无关的纯 Python 轮子,文件名中的py3-none-any即"Python 3、无 ABI、任意平台")。twine check在发布前校验分发包元数据(README 渲染、长描述格式、缺失字段等)是否合规。

Pattern 9:发布到 PyPI

# Install publishing tools pip install twine # Test on TestPyPI first twine upload --repository testpypi dist/* # Install from TestPyPI to test pip install --index-url https://test.pypi.org/simple/ my-package # If all good, publish to PyPI twine upload dist/*

规范流程是"先在 TestPyPI 试上传、试安装,确认无误后再正式发布"。推荐使用 API token 而非账号密码,配置~/.pypirc

# Create ~/.pypirc [distutils] index-servers = pypi testpypi [pypi] username = __token__ password = pypi-...your-token... [testpypi] username = __token__ password = pypi-...your-test-token...

Pattern 10:GitHub Actions 自动化发布

# .github/workflows/publish.yml name: Publish to PyPI on: release: types: [created] jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.11" - name: Install dependencies run: | pip install build twine - name: Build package run: python -m build - name: Check package run: twine check dist/* - name: Publish to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: twine upload dist/*

工作流以release事件(types: [created])为触发器,串起"checkout → 装构建工具 → 构建 → twine check → 用PYPI_API_TOKEN密钥上传"五步。TWINE_USERNAME: __token__配合仓库 Secrets 中的PYPI_API_TOKEN,避免了在 CI 日志中泄露凭据。

仓库实践印证:真实的 pyproject.toml 与 CLI 落地

上述模式并非纸上谈兵,当前仓库内部就有多个可对照的真实工程:

  • plugin-eval(pyproject.toml):采用hatchling构建后端与packages = ["src/plugin_eval"]的 src 布局;[project.scripts]注册plugin-eval = "plugin_eval.cli:app";可选依赖按llm/api/dev分组;[tool.pytest.ini_options][tool.ruff]集中配置。其 CLI 在 cli.py 中基于 typer(Click 的上层封装)实现score/certify/init等命令,与 details.md 中"用[project.scripts]注册入口点"的思路完全一致——这正是my-tool = "my_package.cli:main"模式的工程级印证。
  • yt-design-extractor(pyproject.toml):展示了[project.optional-dependencies]控制重依赖的实践——easyocr分组携带 torch/torchvision(约 2GB),注释明确提示用uv sync --extra easyocr按需安装;[tool.uv] package = false声明其为"仅依赖工程",脚本通过uv run yt-design-extractor.py直接执行而非安装为包。
  • Makefile(Makefile):仓库约定所有 Python 工具链统一走uvlint/test目标都以uv run --extra dev在 plugin-eval 项目目录内执行,保证 ruff/ty 版本与 CI 锁定一致——呼应了 Pattern 4 中"工具链配置入 pyproject.toml"的价值。

延伸阅读:高级模式索引

details.md末尾将更进阶的主题指向 references/advanced-patterns.md,其中覆盖:

  • Pattern 11数据文件打包:[tool.setuptools.package-data]声明data/*.jsontemplates/*.html等资源,运行时用importlib.resources.files("my_package").joinpath(...)读取(Python 3.9+);
  • Pattern 12命名空间包:多仓库共享company/命名空间,命名空间目录不写__init__.py,各包用include = ["company.core*"]限定;
  • Pattern 13C 扩展:pyproject.toml 的[tool.setuptools] ext-modules或传统setup.pyExtension
  • Pattern 14-15语义化版本与 setuptools-scm 的 git 派生版本(1.0.1.dev3+g1234567);
  • Pattern 16-17pip install -e .可编辑安装与隔离环境安装验证;
  • Pattern 18-20README 模板、cibuildwheel 多架构 wheel、私有索引(--index-url/twine upload --repository-url);
  • 以及.gitignoreMANIFEST.in模板与发布前的逐项检查清单(测试、文档、版本号、CHANGELOG、License、TestPyPI 先行、git tag 等)。

至此,从"全功能 pyproject.toml"到"动态版本 + CLI + 构建发布 + 自动化 CI",再到仓库内真实工程的对照印证,你已经拥有了一套可完整覆盖 Python 库/工具打包发布全生命周期的操作指南。

【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 4:36:29

Happy-LLM 教程导读:从零开始构建大模型的学习路线与实践指南

Happy-LLM 教程导读&#xff1a;从零开始构建大模型的学习路线与实践指南 【免费下载链接】happy-llm &#x1f4da; 从零开始构建大模型 项目地址: https://gitcode.com/GitHub_Trending/ha/happy-llm 本篇文章是 Datawhale 开源项目 Happy-LLM&#xff08;仓库路径 doc…

作者头像 李华
网站建设 2026/9/10 4:36:18

WavLM 全栈语音预训练模型解析与 Transformers 实战指南

WavLM 全栈语音预训练模型解析与 Transformers 实战指南 【免费下载链接】transformers &#x1f917; Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and …

作者头像 李华
网站建设 2026/9/10 4:35:56

SpringBoot+Spark打造汽车销售推荐系统:从协同过滤到冷启动实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 4:35:24

TradingAgents-CN 任务执行控制与数据同步功能增强实战解析

TradingAgents-CN 任务执行控制与数据同步功能增强实战解析 【免费下载链接】TradingAgents-CN 基于多智能体LLM的中文金融交易框架 - TradingAgents中文增强版 项目地址: https://gitcode.com/GitHub_Trending/tr/TradingAgents-CN 日期: 2025-11-07 作者: TradingAgen…

作者头像 李华